- Created a new Blade view for managing brand contracts with forms for adding and editing contracts, including client and category selection. - Implemented success message notifications for contract actions. - Added a new Blade view for invoice settings, allowing customization of invoice number format, header branding, and default values for new invoices. - Introduced a new PDF view for generating rate cards with customizable styles based on user profile settings. - Developed a profile management page for rate cards, enabling users to update their identity, appearance, and visibility settings.
146 lines
5.4 KiB
PHP
146 lines
5.4 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace App\Services;
|
||
|
||
use App\Models\BrandContract;
|
||
use App\Models\Client;
|
||
use App\Models\ClientCategory;
|
||
use App\Models\ContentBrief;
|
||
use App\Models\Event;
|
||
use App\Models\Invoice;
|
||
use App\Models\MediaFile;
|
||
use App\Models\SocialStat;
|
||
use Illuminate\Support\Collection;
|
||
|
||
final class DashboardService
|
||
{
|
||
public function getStats(): array
|
||
{
|
||
return [
|
||
'total_clients' => Client::count(),
|
||
'active_clients' => Client::where('status', 'active')->count(),
|
||
'upcoming_events' => Event::upcoming(24 * 90)->count(), // 3 months
|
||
'pending_invoices' => Invoice::where('status', 'sent')->count(),
|
||
'total_revenue' => Invoice::where('status', 'paid')->sum('total'),
|
||
'content_in_progress' => ContentBrief::whereNotIn('status', ['posted', 'archived'])->count(),
|
||
'total_categories' => ClientCategory::whereNull('parent_id')->count(),
|
||
'total_subcategories' => ClientCategory::whereNotNull('parent_id')->count(),
|
||
'total_files' => MediaFile::count(),
|
||
'active_contracts' => BrandContract::where('is_active', true)->where('end_date', '>=', today())->count(),
|
||
'total_contracts' => BrandContract::count(),
|
||
];
|
||
}
|
||
|
||
/**
|
||
* Combined today's activities: events (today or ongoing), invoices due, content targets.
|
||
*/
|
||
public function getTodayActivities(): Collection
|
||
{
|
||
$activities = collect();
|
||
|
||
// Events starting today OR currently ongoing (started before today, ends today or later)
|
||
$events = Event::with('client')
|
||
->where(function ($q) {
|
||
$q->whereDate('start_time', today())
|
||
->orWhere(function ($q2) {
|
||
$q2->whereDate('start_time', '<', today())
|
||
->where(function ($q3) {
|
||
$q3->whereDate('end_time', '>=', today())
|
||
->orWhereNull('end_time');
|
||
})
|
||
->whereNotIn('status', ['completed', 'cancelled']);
|
||
});
|
||
})
|
||
->orderBy('start_time')
|
||
->limit(15)
|
||
->get();
|
||
|
||
foreach ($events as $e) {
|
||
$timeLabel = $e->is_all_day
|
||
? 'All day'
|
||
: ($e->start_time->format('H:i') . ($e->end_time ? ' – ' . $e->end_time->format('H:i') : ''));
|
||
|
||
$activities->push([
|
||
'type' => 'event',
|
||
'icon' => '📅',
|
||
'title' => $e->title,
|
||
'subtitle' => $timeLabel . ($e->client ? ' · ' . $e->client->name : ''),
|
||
'label' => $e->event_type,
|
||
'badge' => $e->status->label(),
|
||
'badge_class' => $e->status->badgeClass(),
|
||
'color' => $e->color ?? '#FFB7C5',
|
||
'priority' => $e->is_priority,
|
||
'sort_key' => $e->start_time->timestamp,
|
||
]);
|
||
}
|
||
|
||
// Invoices due today (not yet paid or cancelled)
|
||
$invoices = Invoice::with('client')
|
||
->whereDate('due_date', today())
|
||
->whereNotIn('status', ['paid', 'cancelled'])
|
||
->get();
|
||
|
||
foreach ($invoices as $inv) {
|
||
$activities->push([
|
||
'type' => 'invoice',
|
||
'icon' => '🧾',
|
||
'title' => $inv->title ?? $inv->invoice_number,
|
||
'subtitle' => ($inv->client?->name ?? 'No client') . ' · ' . ($inv->formatted_total ?? ''),
|
||
'label' => 'Invoice Due',
|
||
'badge' => $inv->status->label(),
|
||
'badge_class' => $inv->status->badgeClass(),
|
||
'color' => '#FDFD96',
|
||
'priority' => false,
|
||
'sort_key' => today()->setTime(23, 59, 0)->timestamp,
|
||
]);
|
||
}
|
||
|
||
// Content briefs with target date today (not posted/archived)
|
||
$content = ContentBrief::with('client')
|
||
->whereDate('target_date', today())
|
||
->whereNotIn('status', ['posted', 'archived'])
|
||
->get();
|
||
|
||
foreach ($content as $c) {
|
||
$activities->push([
|
||
'type' => 'content',
|
||
'icon' => $c->platform_icon,
|
||
'title' => $c->title,
|
||
'subtitle' => ucfirst($c->platform) . ($c->client ? ' · ' . $c->client->name : ''),
|
||
'label' => 'Content Target',
|
||
'badge' => $c->status->label(),
|
||
'badge_class' => 'badge-ghost',
|
||
'color' => '#E8D5F5',
|
||
'priority' => false,
|
||
'sort_key' => today()->setTime(12, 0, 0)->timestamp,
|
||
]);
|
||
}
|
||
|
||
return $activities->sortBy('sort_key')->values();
|
||
}
|
||
|
||
public function getAlertCount(): int
|
||
{
|
||
return Event::upcoming(24)->count();
|
||
}
|
||
|
||
public function getLatestInvoices(): Collection
|
||
{
|
||
return Invoice::with('client')
|
||
->latest()
|
||
->limit(5)
|
||
->get();
|
||
}
|
||
|
||
public function getSocialStats(): Collection
|
||
{
|
||
return SocialStat::whereIn('id', function ($query): void {
|
||
$query->selectRaw('MAX(id)')
|
||
->from('social_stats')
|
||
->groupBy('platform');
|
||
})->get();
|
||
}
|
||
}
|