Files
construprogress/app/Livewire/Reports/ReportBuilder.php
T
Javier Braña ee32544525 feat(reports): complete reporting system with deviations, progress curves, multi-format export
- Added planned/actual/baseline dates to Phase and Feature models
- Created ProgressSnapshot model + migration for historical tracking
- Implemented CaptureDailyProgressSnapshot job (scheduled daily at 06:30)
- Added DeviationCalculator logic (SPI, planned progress, deviation days)
- ReportGenerator service with complete data aggregation
- ReportController with builder, preview, generate (HTML/Excel)
- ReportBuilder Livewire component with date range, entity selection, format
- ProjectReportExport with 10 sheets (Summary, Phases, Features, Inspections, Issues, Tasks, Deviations, Media, Progress Curve, Parameters)
- Blade partials for all sections (header, summary, phases, features, inspections, issues, tasks, media, deviations, progress curve)
- New routes: /projects/{project}/reports/*
- Added 'generate reports' permission
- All 101 tests passing
2026-08-24 12:32:08 +02:00

127 lines
4.0 KiB
PHP

<?php
namespace App\Livewire\Reports;
use App\DTO\ReportFilters;
use App\Models\Project;
use App\Services\ReportGenerator;
use Livewire\Component;
use Livewire\Attributes\Layout;
#[Layout('layouts.app')]
class ReportBuilder extends Component
{
public Project $project;
public array $filters = [
'date_from' => null,
'date_to' => null,
'entity_types' => ['phases', 'features', 'inspections', 'issues', 'tasks'],
'include_photos' => false,
'include_charts' => false,
'format' => 'html',
];
public bool $showPreview = false;
public ?array $previewData = null;
public function mount(Project $project)
{
$this->project = $project;
$this->authorizeAccess();
}
public function authorizeAccess(): void
{
$user = auth()->guard()->user();
if (!$user->can('manage all') && !$this->project->users()->where('user_id', $user->id)->exists()) {
abort(403);
}
}
public function setDateRange(string $preset): void
{
$now = now();
match ($preset) {
'week' => [
$this->filters['date_from'] = $now->copy()->startOfWeek()->toDateString(),
$this->filters['date_to'] = $now->copy()->endOfWeek()->toDateString(),
],
'month' => [
$this->filters['date_from'] = $now->copy()->startOfMonth()->toDateString(),
$this->filters['date_to'] = $now->copy()->endOfMonth()->toDateString(),
],
'quarter' => [
$this->filters['date_from'] = $now->copy()->startOfQuarter()->toDateString(),
$this->filters['date_to'] = $now->copy()->endOfQuarter()->toDateString(),
],
'year' => [
$this->filters['date_from'] = $now->copy()->startOfYear()->toDateString(),
$this->filters['date_to'] = $now->copy()->endOfYear()->toDateString(),
],
};
}
public function clearDateRange(): void
{
$this->filters['date_from'] = null;
$this->filters['date_to'] = null;
}
public function generateReport(): void
{
$this->validate([
'filters.date_from' => 'nullable|date',
'filters.date_to' => 'nullable|date|after_or_equal:filters.date_from',
'filters.entity_types' => 'required|array|min:1',
'filters.format' => 'required|in:html,excel',
]);
$reportFilters = ReportFilters::fromRequest($this->filters);
$generator = new ReportGenerator($this->project, $reportFilters);
$data = $generator->generate();
if ($this->filters['format'] === 'excel') {
// Redirect to download route
return redirect()->route('reports.project.excel', [
'project' => $this->project,
...$reportFilters->toArray(),
]);
}
// For HTML, we'll render the complete report view
// The view will be returned by the controller
$this->redirectRoute('reports.project.generate', [
'project' => $this->project,
...$reportFilters->toArray(),
]);
}
public function previewReport(): void
{
$this->validate([
'filters.date_from' => 'nullable|date',
'filters.date_to' => 'nullable|date|after_or_equal:filters.date_from',
'filters.entity_types' => 'required|array|min:1',
]);
$reportFilters = ReportFilters::fromRequest($this->filters);
$generator = new ReportGenerator($this->project, $reportFilters);
$this->previewData = $generator->generate();
$this->showPreview = true;
}
public function closePreview(): void
{
$this->showPreview = false;
$this->previewData = null;
}
public function render()
{
return view('livewire.reports.report-builder', [
'project' => $this->project,
'availableEntities' => (new ReportFilters)->getAvailableEntities(),
]);
}
}