- 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
127 lines
4.0 KiB
PHP
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(),
|
|
]);
|
|
}
|
|
} |