Files
hub/app/Http/Controllers/AnalyticsController.php
kelly c9b99efbe0 feat: Controller, model, view, and route updates for product/inventory management
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
2025-11-23 00:57:50 -07:00

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;
}
}