initial commit

This commit is contained in:
I-Fun
2026-04-01 08:04:56 +07:00
commit e09c8a5fa9
111 changed files with 18022 additions and 0 deletions

View File

@@ -0,0 +1,137 @@
<?php
declare(strict_types=1);
namespace App\Services;
use App\Models\Client;
use App\Models\ContentBrief;
use App\Models\Event;
use App\Models\Invoice;
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(),
];
}
/**
* 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();
}
}