Files
GSB-Construction/Modules/DailyReports/app/Http/Controllers/DailyReportExportController.php
Christopher Boyles 64d4b331a8
Some checks failed
Tests / PHP 8.3 (push) Has been cancelled
Tests / PHP 8.4 (push) Has been cancelled
Tests / PHP 8.5 (push) Has been cancelled
Additional Changes
2026-06-02 22:04:03 +08:00

459 lines
19 KiB
PHP

<?php
namespace Modules\DailyReports\Http\Controllers;
use App\Http\Controllers\Controller;
use Modules\DailyReports\Models\DailyReport;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Style\Border;
use PhpOffice\PhpSpreadsheet\Style\Fill;
class DailyReportExportController extends Controller
{
public function export(DailyReport $dailyReport)
{
$dailyReport->load(['project', 'user', 'labors', 'equipment', 'activities', 'materials', 'issues']);
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->setTitle('Daily Report');
$sheet->setShowGridlines(true);
// Styling helpers
$headerStyle = [
'font' => [
'name' => 'Arial',
'size' => 16,
'bold' => true,
'color' => ['rgb' => 'FFFFFF'],
],
'fill' => [
'fillType' => Fill::FILL_SOLID,
'startColor' => ['rgb' => '334155'], // Slate 700
],
'alignment' => [
'horizontal' => Alignment::HORIZONTAL_CENTER,
'vertical' => Alignment::VERTICAL_CENTER,
],
];
$subHeaderStyle = [
'font' => [
'name' => 'Arial',
'size' => 11,
'bold' => true,
'color' => ['rgb' => '334155'],
],
'fill' => [
'fillType' => Fill::FILL_SOLID,
'startColor' => ['rgb' => 'F1F5F9'], // Slate 100
],
'alignment' => [
'vertical' => Alignment::VERTICAL_CENTER,
],
'borders' => [
'bottom' => [
'borderStyle' => Border::BORDER_MEDIUM,
'color' => ['rgb' => 'CBD5E1'],
],
],
];
$sectionTitleStyle = [
'font' => [
'name' => 'Arial',
'size' => 12,
'bold' => true,
'color' => ['rgb' => '0F172A'], // Slate 900
],
'fill' => [
'fillType' => Fill::FILL_SOLID,
'startColor' => ['rgb' => 'E2E8F0'], // Slate 200
],
'alignment' => [
'vertical' => Alignment::VERTICAL_CENTER,
],
];
$boldLabelStyle = [
'font' => [
'name' => 'Arial',
'size' => 10,
'bold' => true,
'color' => ['rgb' => '475569'],
],
];
$tableHeaderStyle = [
'font' => [
'name' => 'Arial',
'size' => 10,
'bold' => true,
'color' => ['rgb' => '475569'],
],
'fill' => [
'fillType' => Fill::FILL_SOLID,
'startColor' => ['rgb' => 'F8FAFC'],
],
'borders' => [
'bottom' => [
'borderStyle' => Border::BORDER_THIN,
'color' => ['rgb' => 'E2E8F0'],
],
],
];
$borderBottomThin = [
'borders' => [
'bottom' => [
'borderStyle' => Border::BORDER_THIN,
'color' => ['rgb' => 'F1F5F9'],
],
],
];
// 1. Title Block
$sheet->mergeCells('A1:F1');
$sheet->setCellValue('A1', 'GSB CONSTRUCTION - DAILY SITE REPORT');
$sheet->getStyle('A1:F1')->applyFromArray($headerStyle);
$sheet->getRowDimension(1)->setRowHeight(40);
// 2. Metadata Block
$sheet->mergeCells('A3:F3');
$sheet->setCellValue('A3', ' REPORT METADATA');
$sheet->getStyle('A3:F3')->applyFromArray($subHeaderStyle);
$sheet->getRowDimension(3)->setRowHeight(25);
// Row 4
$sheet->setCellValue('A4', 'Report Number:');
$sheet->setCellValue('B4', $dailyReport->report_number);
$sheet->setCellValue('C4', 'Report Date:');
$sheet->setCellValue('D4', $dailyReport->report_date ? $dailyReport->report_date->format('Y-m-d') : 'N/A');
$sheet->setCellValue('E4', 'Prepared By:');
$sheet->setCellValue('F4', $dailyReport->user ? $dailyReport->user->name : 'N/A');
// Row 5
$sheet->setCellValue('A5', 'Project Code:');
$sheet->setCellValue('B5', $dailyReport->project ? $dailyReport->project->code : 'N/A');
$sheet->setCellValue('C5', 'Project Name:');
$sheet->mergeCells('D5:F5');
$sheet->setCellValue('D5', $dailyReport->project ? $dailyReport->project->name : 'N/A');
// Row 6
$sheet->setCellValue('A6', 'Weather:');
$sheet->setCellValue('B6', $dailyReport->weather ?: 'N/A');
$sheet->setCellValue('C6', 'Temperature:');
$sheet->setCellValue('D6', $dailyReport->temperature ? $dailyReport->temperature . ' °C' : 'N/A');
$sheet->setCellValue('E6', 'Precipitation:');
$sheet->setCellValue('F6', $dailyReport->precipitation ?: 'N/A');
// Row 7
$sheet->setCellValue('A7', 'Wind:');
$sheet->setCellValue('B7', $dailyReport->wind ?: 'N/A');
$sheet->getStyle('A4')->applyFromArray($boldLabelStyle);
$sheet->getStyle('C4')->applyFromArray($boldLabelStyle);
$sheet->getStyle('E4')->applyFromArray($boldLabelStyle);
$sheet->getStyle('A5')->applyFromArray($boldLabelStyle);
$sheet->getStyle('C5')->applyFromArray($boldLabelStyle);
$sheet->getStyle('A6')->applyFromArray($boldLabelStyle);
$sheet->getStyle('C6')->applyFromArray($boldLabelStyle);
$sheet->getStyle('E6')->applyFromArray($boldLabelStyle);
$sheet->getStyle('A7')->applyFromArray($boldLabelStyle);
// 2.5. Summary Dashboard Block
$sheet->mergeCells('A9:F9');
$sheet->setCellValue('A9', ' SUMMARY & KEY METRICS');
$sheet->getStyle('A9:F9')->applyFromArray($subHeaderStyle);
$sheet->getRowDimension(9)->setRowHeight(25);
// Calculate summary metrics
$totalWorkers = 0;
$totalManHours = 0;
foreach ($dailyReport->labors as $lab) {
$totalWorkers += $lab->workers_count;
$totalManHours += $lab->workers_count * ($lab->hours ?: 0);
}
$totalEquipmentHours = $dailyReport->equipment->sum('hours_used') ?: 0;
$avgProgress = $dailyReport->activities->isEmpty() ? 0 : ($dailyReport->activities->avg('percentage_completed') ?: 0);
// Row 10
$sheet->setCellValue('A10', 'Tasks Logged:');
$sheet->setCellValue('B10', count($dailyReport->activities));
$sheet->setCellValue('C10', 'Materials Logged:');
$sheet->setCellValue('D10', count($dailyReport->materials));
$sheet->setCellValue('E10', 'Manpower Count:');
$sheet->setCellValue('F10', $totalWorkers);
// Row 11
$sheet->setCellValue('A11', 'Avg. Progress:');
$sheet->setCellValue('B11', number_format($avgProgress, 1) . '%');
$sheet->setCellValue('C11', 'Reported Issues:');
$sheet->setCellValue('D11', count($dailyReport->issues));
$sheet->setCellValue('E11', 'Total Man-Hours:');
$sheet->setCellValue('F11', number_format($totalManHours, 1) . ' hrs');
// Row 12
$sheet->setCellValue('A12', 'Active Equipment:');
$sheet->setCellValue('B12', count($dailyReport->equipment));
$sheet->setCellValue('C12', 'Equipment Hours:');
$sheet->setCellValue('D12', number_format($totalEquipmentHours, 1) . ' hrs');
$sheet->getStyle('A10')->applyFromArray($boldLabelStyle);
$sheet->getStyle('C10')->applyFromArray($boldLabelStyle);
$sheet->getStyle('E10')->applyFromArray($boldLabelStyle);
$sheet->getStyle('A11')->applyFromArray($boldLabelStyle);
$sheet->getStyle('C11')->applyFromArray($boldLabelStyle);
$sheet->getStyle('E11')->applyFromArray($boldLabelStyle);
$sheet->getStyle('A12')->applyFromArray($boldLabelStyle);
$sheet->getStyle('C12')->applyFromArray($boldLabelStyle);
$rowIdx = 14;
// 3. Section A: Tasks
$sheet->mergeCells("A{$rowIdx}:F{$rowIdx}");
$sheet->setCellValue("A{$rowIdx}", ' 1. TASKS');
$sheet->getStyle("A{$rowIdx}:F{$rowIdx}")->applyFromArray($sectionTitleStyle);
$sheet->getRowDimension($rowIdx)->setRowHeight(25);
$rowIdx++;
// Table Headers
$sheet->mergeCells("A{$rowIdx}:C{$rowIdx}");
$sheet->setCellValue("A{$rowIdx}", 'Task Name / Activity');
$sheet->setCellValue("D{$rowIdx}", 'Zone / Area');
$sheet->setCellValue("E{$rowIdx}", 'Qty Completed');
$sheet->setCellValue("F{$rowIdx}", 'Completion %');
$sheet->getStyle("A{$rowIdx}:F{$rowIdx}")->applyFromArray($tableHeaderStyle);
$rowIdx++;
if ($dailyReport->activities->isEmpty()) {
$sheet->mergeCells("A{$rowIdx}:F{$rowIdx}");
$sheet->setCellValue("A{$rowIdx}", 'No work activities reported for this shift.');
$sheet->getStyle("A{$rowIdx}:F{$rowIdx}")->applyFromArray(['font' => ['italic' => true, 'color' => ['rgb' => '94A3B8']]]);
$rowIdx++;
} else {
foreach ($dailyReport->activities as $act) {
$sheet->mergeCells("A{$rowIdx}:C{$rowIdx}");
$sheet->setCellValue("A{$rowIdx}", $act->task_name);
$sheet->setCellValue("D{$rowIdx}", $act->zone_area ?: 'N/A');
$sheet->setCellValue("E{$rowIdx}", $act->quantity_completed ?: 'N/A');
$sheet->setCellValue("F{$rowIdx}", $act->percentage_completed ? $act->percentage_completed . '%' : '0%');
$sheet->getStyle("A{$rowIdx}:F{$rowIdx}")->applyFromArray($borderBottomThin);
$rowIdx++;
}
}
$rowIdx++;
// 4. Section B: Materials
$sheet->mergeCells("A{$rowIdx}:F{$rowIdx}");
$sheet->setCellValue("A{$rowIdx}", ' 2. MATERIALS');
$sheet->getStyle("A{$rowIdx}:F{$rowIdx}")->applyFromArray($sectionTitleStyle);
$sheet->getRowDimension($rowIdx)->setRowHeight(25);
$rowIdx++;
// Table Headers
$sheet->mergeCells("A{$rowIdx}:C{$rowIdx}");
$sheet->setCellValue("A{$rowIdx}", 'Material Name');
$sheet->setCellValue("D{$rowIdx}", 'Quantity Received / Used');
$sheet->mergeCells("E{$rowIdx}:F{$rowIdx}");
$sheet->setCellValue("E{$rowIdx}", 'Condition / Remarks');
$sheet->getStyle("A{$rowIdx}:F{$rowIdx}")->applyFromArray($tableHeaderStyle);
$rowIdx++;
if ($dailyReport->materials->isEmpty()) {
$sheet->mergeCells("A{$rowIdx}:F{$rowIdx}");
$sheet->setCellValue("A{$rowIdx}", 'No materials logged for this date.');
$sheet->getStyle("A{$rowIdx}:F{$rowIdx}")->applyFromArray(['font' => ['italic' => true, 'color' => ['rgb' => '94A3B8']]]);
$rowIdx++;
} else {
foreach ($dailyReport->materials as $mat) {
$sheet->mergeCells("A{$rowIdx}:C{$rowIdx}");
$sheet->setCellValue("A{$rowIdx}", $mat->material_name);
$sheet->setCellValue("D{$rowIdx}", $mat->quantity_received);
$sheet->mergeCells("E{$rowIdx}:F{$rowIdx}");
$sheet->setCellValue("E{$rowIdx}", $mat->condition ?: 'Good');
$sheet->getStyle("A{$rowIdx}:F{$rowIdx}")->applyFromArray($borderBottomThin);
$rowIdx++;
}
}
$rowIdx++;
// 5. Section C: Labor & Manpower
$sheet->mergeCells("A{$rowIdx}:F{$rowIdx}");
$sheet->setCellValue("A{$rowIdx}", ' 3. MANPOWER/LABOR');
$sheet->getStyle("A{$rowIdx}:F{$rowIdx}")->applyFromArray($sectionTitleStyle);
$sheet->getRowDimension($rowIdx)->setRowHeight(25);
$rowIdx++;
// Table Headers
$sheet->mergeCells("A{$rowIdx}:B{$rowIdx}");
$sheet->setCellValue("A{$rowIdx}", 'Labor Trade / Profession');
$sheet->setCellValue("C{$rowIdx}", 'Workers Count');
$sheet->setCellValue("D{$rowIdx}", 'Hours Rendered');
$sheet->mergeCells("E{$rowIdx}:F{$rowIdx}");
$sheet->setCellValue("E{$rowIdx}", 'Notes / Details');
$sheet->getStyle("A{$rowIdx}:F{$rowIdx}")->applyFromArray($tableHeaderStyle);
$rowIdx++;
if ($dailyReport->labors->isEmpty()) {
$sheet->mergeCells("A{$rowIdx}:F{$rowIdx}");
$sheet->setCellValue("A{$rowIdx}", 'No labor resources logged.');
$sheet->getStyle("A{$rowIdx}:F{$rowIdx}")->applyFromArray(['font' => ['italic' => true, 'color' => ['rgb' => '94A3B8']]]);
$rowIdx++;
} else {
foreach ($dailyReport->labors as $lab) {
$sheet->mergeCells("A{$rowIdx}:B{$rowIdx}");
$sheet->setCellValue("A{$rowIdx}", $lab->trade);
$sheet->setCellValue("C{$rowIdx}", $lab->workers_count);
$sheet->setCellValue("D{$rowIdx}", $lab->hours);
$sheet->mergeCells("E{$rowIdx}:F{$rowIdx}");
$sheet->setCellValue("E{$rowIdx}", $lab->notes ?: '');
$sheet->getStyle("A{$rowIdx}:F{$rowIdx}")->applyFromArray($borderBottomThin);
$rowIdx++;
}
}
$rowIdx++;
// 6. Section D: Equipment Usage
$sheet->mergeCells("A{$rowIdx}:F{$rowIdx}");
$sheet->setCellValue("A{$rowIdx}", ' 4. EQUIPMENT');
$sheet->getStyle("A{$rowIdx}:F{$rowIdx}")->applyFromArray($sectionTitleStyle);
$sheet->getRowDimension($rowIdx)->setRowHeight(25);
$rowIdx++;
// Table Headers
$sheet->mergeCells("A{$rowIdx}:C{$rowIdx}");
$sheet->setCellValue("A{$rowIdx}", 'Equipment Name');
$sheet->setCellValue("D{$rowIdx}", 'Hours Used');
$sheet->mergeCells("E{$rowIdx}:F{$rowIdx}");
$sheet->setCellValue("E{$rowIdx}", 'Status (Active / Idle)');
$sheet->getStyle("A{$rowIdx}:F{$rowIdx}")->applyFromArray($tableHeaderStyle);
$rowIdx++;
if ($dailyReport->equipment->isEmpty()) {
$sheet->mergeCells("A{$rowIdx}:F{$rowIdx}");
$sheet->setCellValue("A{$rowIdx}", 'No equipment usage logged.');
$sheet->getStyle("A{$rowIdx}:F{$rowIdx}")->applyFromArray(['font' => ['italic' => true, 'color' => ['rgb' => '94A3B8']]]);
$rowIdx++;
} else {
foreach ($dailyReport->equipment as $eq) {
$sheet->mergeCells("A{$rowIdx}:C{$rowIdx}");
$sheet->setCellValue("A{$rowIdx}", $eq->equipment_name);
$sheet->setCellValue("D{$rowIdx}", $eq->hours_used);
$sheet->mergeCells("E{$rowIdx}:F{$rowIdx}");
$sheet->setCellValue("E{$rowIdx}", ucfirst($eq->status));
$sheet->getStyle("A{$rowIdx}:F{$rowIdx}")->applyFromArray($borderBottomThin);
$rowIdx++;
}
}
$rowIdx++;
// 7. Section E: Reported Issues & Delays
$sheet->mergeCells("A{$rowIdx}:F{$rowIdx}");
$sheet->setCellValue("A{$rowIdx}", ' 5. ISSUES & CONCERN');
$sheet->getStyle("A{$rowIdx}:F{$rowIdx}")->applyFromArray($sectionTitleStyle);
$sheet->getRowDimension($rowIdx)->setRowHeight(25);
$rowIdx++;
// Table Headers
$sheet->mergeCells("A{$rowIdx}:B{$rowIdx}");
$sheet->setCellValue("A{$rowIdx}", 'Issue / Delay Type');
$sheet->mergeCells("C{$rowIdx}:E{$rowIdx}");
$sheet->setCellValue("C{$rowIdx}", 'Description');
$sheet->setCellValue("F{$rowIdx}", 'Delay Impact (Hrs)');
$sheet->getStyle("A{$rowIdx}:F{$rowIdx}")->applyFromArray($tableHeaderStyle);
$rowIdx++;
if ($dailyReport->issues->isEmpty()) {
$sheet->mergeCells("A{$rowIdx}:F{$rowIdx}");
$sheet->setCellValue("A{$rowIdx}", 'No major issues or delays reported.');
$sheet->getStyle("A{$rowIdx}:F{$rowIdx}")->applyFromArray(['font' => ['italic' => true, 'color' => ['rgb' => '94A3B8']]]);
$rowIdx++;
} else {
foreach ($dailyReport->issues as $iss) {
$sheet->mergeCells("A{$rowIdx}:B{$rowIdx}");
$sheet->setCellValue("A{$rowIdx}", ucfirst(str_replace('_', ' ', $iss->issue_type)));
$sheet->mergeCells("C{$rowIdx}:E{$rowIdx}");
$sheet->setCellValue("C{$rowIdx}", $iss->description);
$sheet->setCellValue("F{$rowIdx}", $iss->delay_impact ?: '0');
$sheet->getStyle("A{$rowIdx}:F{$rowIdx}")->applyFromArray($borderBottomThin);
$rowIdx++;
}
}
$rowIdx++;
// 8. General Remarks
$sheet->mergeCells("A{$rowIdx}:F{$rowIdx}");
$sheet->setCellValue("A{$rowIdx}", ' GENERAL REMARKS & NOTES');
$sheet->getStyle("A{$rowIdx}:F{$rowIdx}")->applyFromArray($subHeaderStyle);
$rowIdx++;
$sheet->mergeCells("A{$rowIdx}:F" . ($rowIdx + 3));
$sheet->setCellValue("A{$rowIdx}", $dailyReport->remarks ?: 'No additional remarks entered.');
$sheet->getStyle("A{$rowIdx}:F" . ($rowIdx + 3))->applyFromArray([
'alignment' => [
'vertical' => Alignment::VERTICAL_TOP,
'wrapText' => true
],
'font' => ['italic' => !$dailyReport->remarks]
]);
// Auto-fit column widths
foreach (range('A', 'F') as $col) {
$sheet->getColumnDimension($col)->setAutoSize(true);
}
// Output Excel response
$filename = 'DailyReport_' . ($dailyReport->project ? $dailyReport->project->code : '') . '_' . $dailyReport->report_date->format('Ymd') . '.xlsx';
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Content-Disposition: attachment;filename="' . $filename . '"');
header('Cache-Control: max-age=0');
$writer = new Xlsx($spreadsheet);
$writer->save('php://output');
exit;
}
public function pdf(DailyReport $dailyReport)
{
$dailyReport->load(['project', 'user', 'labors', 'equipment', 'activities', 'materials', 'issues']);
// Calculate summary metrics
$totalWorkers = 0;
$totalManHours = 0;
foreach ($dailyReport->labors as $lab) {
$totalWorkers += $lab->workers_count;
$totalManHours += $lab->workers_count * ($lab->hours ?: 0);
}
$totalEquipmentHours = 0;
foreach ($dailyReport->equipment as $eq) {
$totalEquipmentHours += $eq->hours_used ?: 0;
}
$avgProgress = 0;
if (!$dailyReport->activities->isEmpty()) {
$avgProgress = $dailyReport->activities->avg('percentage_completed') ?: 0;
}
$summary = [
'total_workers' => $totalWorkers,
'total_man_hours' => $totalManHours,
'total_equipment_hours' => $totalEquipmentHours,
'avg_progress' => $avgProgress,
];
$pdf = \Barryvdh\DomPDF\Facade\Pdf::loadView('dailyreports::pdf.daily-report', [
'report' => $dailyReport,
'summary' => $summary,
]);
$filename = 'DailyReport_' . ($dailyReport->project ? $dailyReport->project->code : '') . '_' . $dailyReport->report_date->format('Ymd') . '.pdf';
return $pdf->download($filename);
}
}