Comprehensive updates to controllers, models, views, and routes to support enhanced product and inventory management features. ## Controller Updates - ProductController: Enhanced product management with categories, SEO, taglines - BrandController: SEO fields, brand voice, contact emails - BomController: Bill of materials integration - ProductImageController: Improved image handling and validation - CategoryController: Product categorization workflows - ComponentController: Enhanced component management - BatchController: Processing batch improvements - Marketing controllers: Broadcasting, channels, templates - MessagingController: SMS/email conversation updates - SettingsController: New settings tabs and sections ## Model Updates - Product: SEO fields, tagline, category relationships, terpenes - Brand: SEO fields, brand voice, contact emails, social links - Business: Module flags (has_assemblies, hasAiCopilot) - Batch: Enhanced batch tracking - Marketing models: Channel, template, message improvements - User: Permission updates ## View Updates - seller/brands/*: Brand dashboard with SEO, contacts, brand voice - seller/products/*: Enhanced product edit with Copilot integration - seller/marketing/*: Broadcasts, campaigns, channels, templates - seller/menus/*: Menu management panels - seller/promotions/*: Promotion panels and workflows - seller/messaging/*: Conversation views - seller/settings/*: Audit logs and configuration - brands/_storefront.blade.php: Enhanced brand storefront display - buyer/marketplace/*: Improved brand browsing - components/seller-sidebar: Responsive mobile drawer ## Route Updates - routes/seller.php: New routes for products, brands, marketing, contacts - routes/web.php: Public brand/product routes - Route groups for brand-scoped features (menus, promotions, templates) ## Service Updates - BroadcastService: Enhanced marketing broadcast handling - SMS providers: Improved SMS sending (Twilio, Cannabrands, Null) - SmsManager: Multi-provider SMS management ## Infrastructure - Console Kernel: Scheduled tasks for audit pruning - AppServiceProvider: Service bindings and boot logic - Bootstrap providers: Updated provider registration ## UI Improvements - Responsive seller sidebar with mobile drawer - Fixed-height textareas with scroll - Character limit enforcement (taglines, descriptions, SEO) - Copilot button standardization (btn-xs, below fields) - DaisyUI/Tailwind styling consistency ## Benefits - Streamlined product/brand management workflows - Better mobile experience with responsive sidebar - Enhanced SEO capabilities - Improved marketing tools - Consistent UI patterns across seller dashboard
187 lines
6.5 KiB
PHP
187 lines
6.5 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Models\Brand;
|
|
use App\Models\Business;
|
|
use App\Models\Order;
|
|
use App\Models\OrderItem;
|
|
use App\Models\Product;
|
|
|
|
class AnalyticsController extends Controller
|
|
{
|
|
/**
|
|
* Display analytics dashboard with comprehensive business intelligence
|
|
*/
|
|
public function index(Business $business)
|
|
{
|
|
|
|
// Get filtered brand IDs for multi-tenancy
|
|
$brandIds = \App\Http\Controllers\Seller\BrandSwitcherController::getFilteredBrandIds();
|
|
|
|
// Gather all analytics data
|
|
$data = [
|
|
'calendarHeatmap' => $this->getCalendarHeatmapData($brandIds),
|
|
'categoryBreakdown' => $this->getCategoryBreakdown($brandIds),
|
|
'geographicDistribution' => $this->getGeographicDistribution($brandIds),
|
|
'topProducts' => $this->getTopProducts($brandIds, 10),
|
|
'topCustomers' => $this->getTopCustomers($brandIds, 10),
|
|
];
|
|
|
|
return view('seller.analytics.index', [
|
|
'business' => $business,
|
|
'analyticsData' => $data,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Get daily revenue data for calendar heatmap (last 12 months)
|
|
*/
|
|
private function getCalendarHeatmapData(array $brandIds): array
|
|
{
|
|
$brandNames = Brand::whereIn('id', $brandIds)->pluck('name')->toArray();
|
|
|
|
// Get start and end dates (12 months ago to today)
|
|
$start = now()->subYear()->startOfDay();
|
|
$end = now()->endOfDay();
|
|
|
|
// Get all order IDs for the period that contain our brands
|
|
$orderIds = OrderItem::whereIn('brand_name', $brandNames)
|
|
->whereHas('order', fn ($q) => $q->whereBetween('created_at', [$start, $end]))
|
|
->pluck('order_id')
|
|
->unique();
|
|
|
|
// Get daily revenue grouped by date
|
|
$dailyRevenue = Order::whereIn('id', $orderIds)
|
|
->whereBetween('created_at', [$start, $end])
|
|
->selectRaw('DATE(created_at) as date, SUM(total) as revenue')
|
|
->groupBy('date')
|
|
->orderBy('date', 'asc')
|
|
->get()
|
|
->mapWithKeys(function ($item) {
|
|
// Convert date to timestamp (revenue is already in dollars)
|
|
return [strtotime($item->date) => round($item->revenue, 2)];
|
|
})
|
|
->toArray();
|
|
|
|
return $dailyRevenue;
|
|
}
|
|
|
|
/**
|
|
* Get revenue breakdown by product category
|
|
*/
|
|
private function getCategoryBreakdown(array $brandIds): array
|
|
{
|
|
$brandNames = Brand::whereIn('id', $brandIds)->pluck('name')->toArray();
|
|
|
|
// Get order items with product types
|
|
$categoryRevenue = OrderItem::whereIn('brand_name', $brandNames)
|
|
->join('products', 'order_items.product_id', '=', 'products.id')
|
|
->selectRaw('products.type, SUM(order_items.line_total) as revenue, COUNT(order_items.id) as items_sold')
|
|
->groupBy('products.type')
|
|
->orderByDesc('revenue')
|
|
->get()
|
|
->map(function ($item) {
|
|
return [
|
|
'category' => ucfirst(str_replace('_', ' ', $item->type)),
|
|
'revenue' => round($item->revenue, 2),
|
|
'items_sold' => $item->items_sold,
|
|
];
|
|
})
|
|
->toArray();
|
|
|
|
return $categoryRevenue;
|
|
}
|
|
|
|
/**
|
|
* Get revenue distribution by geographic state
|
|
*/
|
|
private function getGeographicDistribution(array $brandIds): array
|
|
{
|
|
$brandNames = Brand::whereIn('id', $brandIds)->pluck('name')->toArray();
|
|
|
|
// Get order IDs for our brands
|
|
$orderIds = OrderItem::whereIn('brand_name', $brandNames)
|
|
->pluck('order_id')
|
|
->unique();
|
|
|
|
// Get revenue by buyer state
|
|
$geoRevenue = Order::whereIn('orders.id', $orderIds)
|
|
->join('businesses', 'orders.business_id', '=', 'businesses.id')
|
|
->selectRaw('businesses.physical_state as state, SUM(orders.total) as revenue, COUNT(orders.id) as order_count')
|
|
->whereNotNull('businesses.physical_state')
|
|
->groupBy('businesses.physical_state')
|
|
->orderByDesc('revenue')
|
|
->get()
|
|
->map(function ($item) {
|
|
return [
|
|
'state' => strtoupper($item->state),
|
|
'revenue' => round($item->revenue, 2),
|
|
'order_count' => $item->order_count,
|
|
];
|
|
})
|
|
->toArray();
|
|
|
|
return $geoRevenue;
|
|
}
|
|
|
|
/**
|
|
* Get top N products by revenue
|
|
*/
|
|
private function getTopProducts(array $brandIds, int $limit = 10): array
|
|
{
|
|
$brandNames = Brand::whereIn('id', $brandIds)->pluck('name')->toArray();
|
|
|
|
$topProducts = OrderItem::whereIn('brand_name', $brandNames)
|
|
->selectRaw('product_name, brand_name, SUM(line_total) as revenue, SUM(quantity) as units_sold')
|
|
->groupBy('product_name', 'brand_name')
|
|
->orderByDesc('revenue')
|
|
->limit($limit)
|
|
->get()
|
|
->map(function ($item) {
|
|
return [
|
|
'name' => $item->product_name,
|
|
'brand' => $item->brand_name,
|
|
'revenue' => round($item->revenue, 2),
|
|
'units_sold' => $item->units_sold,
|
|
];
|
|
})
|
|
->toArray();
|
|
|
|
return $topProducts;
|
|
}
|
|
|
|
/**
|
|
* Get top N customers by total purchase value
|
|
*/
|
|
private function getTopCustomers(array $brandIds, int $limit = 10): array
|
|
{
|
|
$brandNames = Brand::whereIn('id', $brandIds)->pluck('name')->toArray();
|
|
|
|
// Get order IDs for our brands
|
|
$orderIds = OrderItem::whereIn('brand_name', $brandNames)
|
|
->pluck('order_id')
|
|
->unique();
|
|
|
|
$topCustomers = Order::whereIn('orders.id', $orderIds)
|
|
->join('businesses', 'orders.business_id', '=', 'businesses.id')
|
|
->selectRaw('businesses.id, businesses.name, SUM(orders.total) as total_spent, COUNT(orders.id) as order_count')
|
|
->groupBy('businesses.id', 'businesses.name')
|
|
->orderByDesc('total_spent')
|
|
->limit($limit)
|
|
->get()
|
|
->map(function ($item) {
|
|
return [
|
|
'id' => $item->id,
|
|
'name' => $item->name,
|
|
'total_spent' => round($item->total_spent, 2),
|
|
'order_count' => $item->order_count,
|
|
'avg_order_value' => round($item->total_spent / $item->order_count, 2),
|
|
];
|
|
})
|
|
->toArray();
|
|
|
|
return $topCustomers;
|
|
}
|
|
}
|