# Cycle 10: Reporting & Exports

## Goal
Install export packages, build unified ReportService, create PDF templates, add Excel/CSV export, implement dashboard charts, and build role-specific dashboards.

---

## Package Installation

```bash
composer require barryvdh/laravel-dompdf
composer require maatwebsite/excel
npm install chart.js
```

---

## Unified ReportService

Replace the current separate methods with a unified interface:

```php
namespace App\Domain\Shared\Services;

use Illuminate\Http\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;

class ReportService
{
    public function generate(string $reportType, array $filters, string $format = 'view'): mixed
    {
        $data = match ($reportType) {
            'financial_summary' => $this->financialSummary($filters),
            'revenue_detail' => $this->revenueDetail($filters),
            'attendance' => $this->attendanceReport($filters),
            'enrollment' => $this->enrollmentReport($filters),
            'participant_list' => $this->participantList($filters),
            'invoice_aging' => $this->invoiceAging($filters),
            'trainer_payroll' => $this->trainerPayroll($filters),
            'inventory_valuation' => $this->inventoryValuation($filters),
            'program_profitability' => $this->programProfitability($filters),
            'facility_utilization' => $this->facilityUtilization($filters),
            'daily_closing' => $this->dailyClosingReport($filters),
            default => throw new \InvalidArgumentException("Unknown report: {$reportType}"),
        };

        return match ($format) {
            'view' => $data, // Return raw data for Livewire rendering
            'pdf' => $this->toPdf($reportType, $data, $filters),
            'excel' => $this->toExcel($reportType, $data, $filters),
            'csv' => $this->toCsv($reportType, $data, $filters),
            default => $data,
        };
    }

    // ─── Report Methods ──────────────────────────────────────

    private function financialSummary(array $filters): array
    {
        $from = Carbon::parse($filters['from']);
        $to = Carbon::parse($filters['to']);
        $branchId = $filters['branch_id'] ?? null;

        $query = Invoice::whereBetween('issue_date', [$from, $to]);
        if ($branchId) $query->where('branch_id', $branchId);

        return [
            'total_invoiced' => $query->sum('total_amount'),
            'total_collected' => Payment::confirmed()->whereBetween('payment_date', [$from, $to])->sum('amount'),
            'total_outstanding' => Invoice::whereIn('status', ['sent', 'overdue', 'partially_paid'])->sum('balance_due'),
            'total_refunded' => 0, // TODO: from refunds table
            'payment_methods' => Payment::confirmed()
                ->whereBetween('payment_date', [$from, $to])
                ->selectRaw("method, SUM(amount) as total, COUNT(*) as count")
                ->groupBy('method')
                ->get(),
            'revenue_by_source' => InvoiceItem::whereHas('invoice', fn ($q) =>
                $q->whereBetween('issue_date', [$from, $to])->where('status', '!=', 'cancelled'))
                ->selectRaw("itemable_type, SUM(total_amount) as total")
                ->groupBy('itemable_type')
                ->get(),
        ];
    }

    private function invoiceAging(array $filters): array
    {
        $outstanding = Invoice::whereIn('status', ['sent', 'overdue', 'partially_paid'])
            ->get()
            ->groupBy(function ($invoice) {
                $days = now()->diffInDays($invoice->due_date);
                return match (true) {
                    $days <= 30 => '0-30',
                    $days <= 60 => '31-60',
                    $days <= 90 => '61-90',
                    $days <= 120 => '91-120',
                    default => '120+',
                };
            })
            ->map(fn ($group) => [
                'count' => $group->count(),
                'total' => $group->sum('balance_due'),
            ]);

        return ['aging_buckets' => $outstanding];
    }

    private function trainerPayroll(array $filters): array
    {
        // Requires Cycle 4 (trainers table)
        $from = Carbon::parse($filters['from']);
        $to = Carbon::parse($filters['to']);

        // Sessions per trainer in period
        // Compensation calculation based on type (per_session, per_hour, salary)
        // Attendance deductions (absent sessions)
        return [];
    }

    private function inventoryValuation(array $filters): array
    {
        return InventoryLevel::with('product')
            ->get()
            ->map(fn ($level) => [
                'product' => $level->product->name_ar,
                'sku' => $level->product->sku,
                'quantity' => $level->quantity_on_hand,
                'unit_cost' => $level->product->cost_price ?? 0,
                'total_value' => $level->quantity_on_hand * ($level->product->cost_price ?? 0),
            ])
            ->toArray();
    }

    private function programProfitability(array $filters): array
    {
        $from = Carbon::parse($filters['from']);
        $to = Carbon::parse($filters['to']);

        return TrainingProgram::withCount(['enrollments' => fn ($q) =>
            $q->where('status', 'active')])
            ->get()
            ->map(fn ($program) => [
                'program' => $program->name_ar,
                'enrollments' => $program->enrollments_count,
                'revenue' => InvoiceItem::where('itemable_type', TrainingProgram::class)
                    ->where('itemable_id', $program->id)
                    ->whereHas('invoice', fn ($q) => $q->whereBetween('issue_date', [$from, $to]))
                    ->sum('total_amount'),
                // TODO: subtract trainer costs, facility costs
            ])
            ->toArray();
    }

    private function facilityUtilization(array $filters): array
    {
        $from = Carbon::parse($filters['from']);
        $to = Carbon::parse($filters['to']);

        return Facility::where('status', 'active')->get()->map(fn ($facility) => [
            'facility' => $facility->name_ar,
            'total_hours_available' => $this->calculateAvailableHours($facility, $from, $to),
            'hours_booked' => SpaceReservation::where('facility_id', $facility->id)
                ->whereBetween('reservation_date', [$from, $to])
                ->where('status', 'confirmed')
                ->get()
                ->sum(fn ($r) => Carbon::parse($r->start_time)->diffInMinutes($r->end_time) / 60),
            'utilization_percent' => 0, // calculated from above
        ])->toArray();
    }

    // ─── Export Methods ──────────────────────────────────────

    private function toPdf(string $type, array $data, array $filters): Response
    {
        $pdf = Pdf::loadView("reports.pdf.{$type}", [
            'data' => $data,
            'filters' => $filters,
            'generated_at' => now(),
            'academy' => app('current_academy'),
        ])->setPaper('a4', 'portrait');

        return $pdf->download("{$type}-" . date('Y-m-d') . '.pdf');
    }

    private function toExcel(string $type, array $data, array $filters): StreamedResponse
    {
        return (new ReportExport($type, $data, $filters))->download("{$type}-" . date('Y-m-d') . '.xlsx');
    }

    private function toCsv(string $type, array $data, array $filters): StreamedResponse
    {
        return (new ReportExport($type, $data, $filters))->download("{$type}-" . date('Y-m-d') . '.csv');
    }
}
```

---

## Report Export Class (Laravel Excel)

```php
namespace App\Exports;

use Maatwebsite\Excel\Concerns\FromArray;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\Exportable;
use Maatwebsite\Excel\Concerns\ShouldAutoSize;

class ReportExport implements FromArray, WithHeadings, ShouldAutoSize
{
    use Exportable;

    public function __construct(
        private string $type,
        private array $data,
        private array $filters,
    ) {}

    public function headings(): array
    {
        return match ($this->type) {
            'participant_list' => ['الاسم', 'الرقم', 'الحالة', 'تاريخ التسجيل', 'النشاط', 'المجموعة'],
            'attendance' => ['المشارك', 'المجموعة', 'الحصص الكلية', 'الحضور', 'الغياب', 'نسبة الحضور'],
            'inventory_valuation' => ['المنتج', 'SKU', 'الكمية', 'تكلفة الوحدة', 'القيمة الإجمالية'],
            'invoice_aging' => ['الفترة', 'عدد الفواتير', 'المبلغ'],
            default => [],
        };
    }

    public function array(): array
    {
        return match ($this->type) {
            'participant_list' => $this->formatParticipantList(),
            'attendance' => $this->formatAttendance(),
            'inventory_valuation' => $this->formatInventory(),
            default => $this->data,
        };
    }

    private function formatParticipantList(): array { /* format for export */ }
    private function formatAttendance(): array { /* format for export */ }
    private function formatInventory(): array { /* format for export */ }
}
```

---

## PDF Report Templates

### `resources/views/reports/pdf/financial_summary.blade.php`
```blade
<x-pdf-layout title="التقرير المالي" :filters="$filters">
    <table class="w-full">
        <tr>
            <td class="stat-box">
                <div class="stat-label">إجمالي الفواتير</div>
                <div class="stat-value">{{ format_money($data['total_invoiced']) }}</div>
            </td>
            <td class="stat-box">
                <div class="stat-label">إجمالي التحصيل</div>
                <div class="stat-value">{{ format_money($data['total_collected']) }}</div>
            </td>
            <td class="stat-box">
                <div class="stat-label">المستحق</div>
                <div class="stat-value">{{ format_money($data['total_outstanding']) }}</div>
            </td>
        </tr>
    </table>

    <h3>طرق الدفع</h3>
    <table class="data-table">
        <thead>
            <tr><th>الطريقة</th><th>المبلغ</th><th>العدد</th></tr>
        </thead>
        <tbody>
            @foreach($data['payment_methods'] as $method)
                <tr>
                    <td>{{ __($method->method) }}</td>
                    <td>{{ format_money($method->total) }}</td>
                    <td>{{ $method->count }}</td>
                </tr>
            @endforeach
        </tbody>
    </table>
</x-pdf-layout>
```

### PDF Layout Component: `resources/views/components/pdf-layout.blade.php`
```blade
@props(['title', 'filters' => []])
<!DOCTYPE html>
<html dir="rtl" lang="ar">
<head>
    <meta charset="utf-8">
    <style>
        @font-face { font-family: 'Cairo'; src: url({{ storage_path('fonts/Cairo-Regular.ttf') }}); }
        body { font-family: 'Cairo', sans-serif; font-size: 12px; direction: rtl; color: #1f2937; }
        .header { border-bottom: 2px solid #3b82f6; padding-bottom: 10px; margin-bottom: 20px; }
        .header h1 { font-size: 18px; color: #1e40af; }
        .stat-box { text-align: center; padding: 15px; background: #f9fafb; border-radius: 8px; }
        .stat-label { font-size: 11px; color: #6b7280; }
        .stat-value { font-size: 20px; font-weight: bold; color: #111827; }
        .data-table { width: 100%; border-collapse: collapse; margin-top: 15px; }
        .data-table th { background: #f3f4f6; padding: 8px; text-align: start; border-bottom: 1px solid #e5e7eb; }
        .data-table td { padding: 8px; border-bottom: 1px solid #f3f4f6; }
        .footer { position: fixed; bottom: 0; width: 100%; text-align: center; font-size: 10px; color: #9ca3af; }
    </style>
</head>
<body>
    <div class="header">
        <h1>{{ $title }}</h1>
        <p>{{ $academy->name_ar ?? '' }} | {{ $generated_at->format('Y/m/d H:i') }}</p>
        @if(!empty($filters))
            <p>الفترة: {{ $filters['from'] ?? '' }} — {{ $filters['to'] ?? '' }}</p>
        @endif
    </div>

    {{ $slot }}

    <div class="footer">
        تم إنشاؤه بواسطة نظام الكابتن | {{ url('/') }}
    </div>
</body>
</html>
```

---

## Dashboard Charts

### Enhanced Dashboard Livewire:

Add to `Dashboard.php`:
```php
public function getRevenueChartData(): array
{
    $months = collect(range(5, 0))->map(fn ($i) => now()->subMonths($i));

    return [
        'labels' => $months->map(fn ($m) => $m->translatedFormat('M Y'))->toArray(),
        'datasets' => [[
            'label' => 'الإيرادات',
            'data' => $months->map(fn ($m) =>
                Payment::where('status', 'confirmed')
                    ->whereMonth('payment_date', $m->month)
                    ->whereYear('payment_date', $m->year)
                    ->sum('amount')
            )->toArray(),
            'borderColor' => '#3b82f6',
            'backgroundColor' => 'rgba(59, 130, 246, 0.1)',
            'fill' => true,
        ]],
    ];
}

public function getPaymentMethodsData(): array
{
    $methods = Payment::where('status', 'confirmed')
        ->whereMonth('payment_date', now()->month)
        ->selectRaw("method, SUM(amount) as total")
        ->groupBy('method')
        ->get();

    return [
        'labels' => $methods->pluck('method')->map(fn ($m) => __($m))->toArray(),
        'datasets' => [[
            'data' => $methods->pluck('total')->toArray(),
            'backgroundColor' => ['#22c55e', '#3b82f6', '#f59e0b', '#8b5cf6', '#ec4899'],
        ]],
    ];
}
```

### Dashboard View Addition:
```blade
<!-- Revenue Trend Chart -->
<div class="bg-white rounded-xl p-6 shadow-sm" x-data="chartComponent({
    type: 'line',
    data: @js($this->getRevenueChartData()),
    options: { responsive: true, plugins: { legend: { display: false } } }
})">
    <h3 class="text-sm font-medium text-gray-600 mb-4">اتجاه الإيرادات</h3>
    <canvas x-ref="canvas" class="h-48"></canvas>
</div>

<!-- Payment Methods Pie -->
<div class="bg-white rounded-xl p-6 shadow-sm" x-data="chartComponent({
    type: 'doughnut',
    data: @js($this->getPaymentMethodsData()),
    options: { responsive: true }
})">
    <h3 class="text-sm font-medium text-gray-600 mb-4">طرق الدفع</h3>
    <canvas x-ref="canvas" class="h-48"></canvas>
</div>
```

---

## Role-Specific Dashboards

### Trainer Dashboard: `app/Livewire/Dashboards/TrainerDashboard.php`
- My sessions today (list with times)
- My groups (with enrollment counts)
- Upcoming sessions this week
- My attendance record
- Pending evaluations to complete

### Parent Dashboard: `app/Livewire/Dashboards/ParentDashboard.php`
- My children (participant cards with status)
- Attendance this week per child
- Outstanding invoices
- Recent evaluations shared
- Upcoming sessions schedule

### Route logic in Dashboard component:
```php
public function render()
{
    $user = auth()->user();

    if ($user->hasRole('trainer')) {
        return view('livewire.dashboards.trainer-dashboard');
    }

    if ($user->hasRole('parent')) {
        return view('livewire.dashboards.parent-dashboard');
    }

    // Default: admin dashboard
    return view('livewire.dashboard');
}
```

---

## Reports Page Enhancement

Update `ReportsPage.php` to add export buttons:
```php
public function exportPdf(): StreamedResponse
{
    return app(ReportService::class)->generate(
        $this->activeTab, $this->getFilters(), 'pdf'
    );
}

public function exportExcel(): StreamedResponse
{
    return app(ReportService::class)->generate(
        $this->activeTab, $this->getFilters(), 'excel'
    );
}
```

---

## Verification

- [ ] PDF generates with Arabic text correctly (Cairo font)
- [ ] Excel download has proper column headers in Arabic
- [ ] Revenue chart shows 6-month trend on dashboard
- [ ] Payment methods pie chart renders
- [ ] Trainer sees their dashboard (not admin's)
- [ ] Parent sees children's info
- [ ] Invoice aging report shows correct buckets
- [ ] Export buttons appear on all report views
