diff --git a/.env.production.example b/.env.production.example new file mode 100644 index 0000000..48f347f --- /dev/null +++ b/.env.production.example @@ -0,0 +1,31 @@ +APP_NAME="GSB Construction ERP" +APP_ENV=production +APP_KEY= +APP_DEBUG=false +APP_URL=https://gsb-construction.local + +LOG_CHANNEL=stack +LOG_DEPRECATIONS_CHANNEL=null +LOG_LEVEL=info + +DB_CONNECTION=pgsql +DB_HOST=127.0.0.1 +DB_PORT=5432 +DB_DATABASE=gsb_construction_prod +DB_USERNAME=postgres +DB_PASSWORD= + +BROADCAST_DRIVER=log +CACHE_DRIVER=file +FILESYSTEM_DISK=local +QUEUE_CONNECTION=database +SESSION_DRIVER=database +SESSION_LIFETIME=120 +SESSION_ENCRYPT=true +SESSION_PATH=/ +SESSION_DOMAIN=null +SESSION_SECURE_COOKIE=true +SESSION_HTTP_ONLY=true +SESSION_SAME_SITE=lax + +SECURITY_HEADERS_ENABLED=true diff --git a/Modules/ApprovalWorkflow/routes/api.php b/Modules/ApprovalWorkflow/routes/api.php index f8b2929..e37493f 100644 --- a/Modules/ApprovalWorkflow/routes/api.php +++ b/Modules/ApprovalWorkflow/routes/api.php @@ -1,8 +1,6 @@ prefix('v1')->group(function () { - Route::apiResource('approvalworkflows', ApprovalWorkflowController::class)->names('approvalworkflow'); -}); +// Scaffold API routes disabled until REST client contracts are authored. + diff --git a/Modules/BiddingManagement/app/Http/Controllers/BidSubmissionController.php b/Modules/BiddingManagement/app/Http/Controllers/BidSubmissionController.php index 3a59c50..a2f5207 100644 --- a/Modules/BiddingManagement/app/Http/Controllers/BidSubmissionController.php +++ b/Modules/BiddingManagement/app/Http/Controllers/BidSubmissionController.php @@ -41,7 +41,7 @@ class BidSubmissionController extends Controller 'validity_date' => 'nullable|date|after_or_equal:today', 'technical_notes' => 'nullable|string', 'documents' => 'nullable|array', - 'documents.*' => 'file|max:20480|mimes:pdf,doc,docx,xls,xlsx,png,jpg,jpeg', + 'documents.*' => 'file|max:20480|mimes:pdf', ]); // Accept invitation if still invited diff --git a/Modules/BiddingManagement/resources/js/Pages/Bids/Contractor/Submit.tsx b/Modules/BiddingManagement/resources/js/Pages/Bids/Contractor/Submit.tsx index fb92e64..c019c0e 100644 --- a/Modules/BiddingManagement/resources/js/Pages/Bids/Contractor/Submit.tsx +++ b/Modules/BiddingManagement/resources/js/Pages/Bids/Contractor/Submit.tsx @@ -189,7 +189,7 @@ export default function Submit({ invitation }: Props) { ref={fileInputRef} type="file" multiple - accept=".pdf,.doc,.docx,.xls,.xlsx,.png,.jpg,.jpeg" + accept=".pdf,application/pdf" onChange={handleFileAdd} className="hidden" /> @@ -200,7 +200,7 @@ export default function Submit({ invitation }: Props) { >

Click to attach BOQ, technical proposal, or other supporting documents

-

PDF, Word, Excel, Images — max 20MB per file

+

PDF documents only — max 20MB per file

) : (
diff --git a/Modules/BiddingManagement/resources/js/Pages/Bids/Show.tsx b/Modules/BiddingManagement/resources/js/Pages/Bids/Show.tsx index 79ef9c6..d203edc 100644 --- a/Modules/BiddingManagement/resources/js/Pages/Bids/Show.tsx +++ b/Modules/BiddingManagement/resources/js/Pages/Bids/Show.tsx @@ -472,9 +472,17 @@ export default function Show({ package: pkg, contractors }: Props) {

Attached Documents

{inv.submission.documents.map(doc => ( - - {doc.title} - + + + {doc.title} + ))}
diff --git a/Modules/BiddingManagement/routes/api.php b/Modules/BiddingManagement/routes/api.php index 41181f1..e37493f 100644 --- a/Modules/BiddingManagement/routes/api.php +++ b/Modules/BiddingManagement/routes/api.php @@ -2,4 +2,5 @@ use Illuminate\Support\Facades\Route; -// API routes placeholder for future use +// Scaffold API routes disabled until REST client contracts are authored. + diff --git a/Modules/ContractorManagement/routes/api.php b/Modules/ContractorManagement/routes/api.php index 991eecc..e37493f 100644 --- a/Modules/ContractorManagement/routes/api.php +++ b/Modules/ContractorManagement/routes/api.php @@ -1,8 +1,6 @@ prefix('v1')->group(function () { - Route::apiResource('contractormanagements', ContractorManagementController::class)->names('contractormanagement'); -}); +// Scaffold API routes disabled until REST client contracts are authored. + diff --git a/Modules/DailyReports/routes/api.php b/Modules/DailyReports/routes/api.php index 434243c..e37493f 100644 --- a/Modules/DailyReports/routes/api.php +++ b/Modules/DailyReports/routes/api.php @@ -1,19 +1,6 @@ prefix('v1')->group(function () { - Route::apiResource('dailyreports', DailyReportsController::class)->names('dailyreports'); -}); diff --git a/Modules/DocumentManagement/app/Http/Controllers/DocumentController.php b/Modules/DocumentManagement/app/Http/Controllers/DocumentController.php index 6da77fa..4776d89 100644 --- a/Modules/DocumentManagement/app/Http/Controllers/DocumentController.php +++ b/Modules/DocumentManagement/app/Http/Controllers/DocumentController.php @@ -53,7 +53,7 @@ class DocumentController extends Controller 'description' => 'nullable|string', 'documentable_type' => 'nullable|string', 'documentable_id' => 'nullable|integer', - 'file' => 'required|file|max:51200|mimes:pdf,jpg,jpeg,png,doc,docx,xls,xlsx,dwg', + 'file' => 'required|file|max:51200|mimes:pdf', ]); $file = $request->file('file'); @@ -93,7 +93,7 @@ class DocumentController extends Controller public function uploadVersion(Request $request, Document $document) { $validated = $request->validate([ - 'file' => 'required|file|max:51200|mimes:pdf,jpg,jpeg,png,doc,docx,xls,xlsx,dwg', + 'file' => 'required|file|max:51200|mimes:pdf', 'change_notes' => 'nullable|string|max:500', ]); diff --git a/Modules/DocumentManagement/resources/js/Pages/Documents/Index.tsx b/Modules/DocumentManagement/resources/js/Pages/Documents/Index.tsx index 2e3415f..d0c443e 100644 --- a/Modules/DocumentManagement/resources/js/Pages/Documents/Index.tsx +++ b/Modules/DocumentManagement/resources/js/Pages/Documents/Index.tsx @@ -507,7 +507,7 @@ export default function Index({ documents, categories, projects, filters }: Prop
- { if (e.target.files?.[0]) form.setData('file', e.target.files[0]); }} /> + { if (e.target.files?.[0]) form.setData('file', e.target.files[0]); }} /> {form.errors.file &&

{form.errors.file}

}
diff --git a/Modules/DocumentManagement/resources/js/Pages/Documents/Versions.tsx b/Modules/DocumentManagement/resources/js/Pages/Documents/Versions.tsx index 8e95221..4e12193 100644 --- a/Modules/DocumentManagement/resources/js/Pages/Documents/Versions.tsx +++ b/Modules/DocumentManagement/resources/js/Pages/Documents/Versions.tsx @@ -67,7 +67,7 @@ export default function Versions({ document: doc }: Props) {
- { if (e.target.files?.[0]) form.setData('file', e.target.files[0]); }} /> + { if (e.target.files?.[0]) form.setData('file', e.target.files[0]); }} />
form.setData('change_notes', e.target.value)} placeholder="What changed?" />
diff --git a/Modules/DocumentManagement/routes/api.php b/Modules/DocumentManagement/routes/api.php index 5af81c1..e37493f 100644 --- a/Modules/DocumentManagement/routes/api.php +++ b/Modules/DocumentManagement/routes/api.php @@ -1,8 +1,6 @@ prefix('v1')->group(function () { - Route::apiResource('documentmanagements', DocumentManagementController::class)->names('documentmanagement'); -}); +// Scaffold API routes disabled until REST client contracts are authored. + diff --git a/Modules/Equipments/routes/api.php b/Modules/Equipments/routes/api.php index b867acc..e37493f 100644 --- a/Modules/Equipments/routes/api.php +++ b/Modules/Equipments/routes/api.php @@ -1,19 +1,6 @@ prefix('v1')->group(function () { - Route::apiResource('equipments', EquipmentController::class)->names('equipments'); -}); diff --git a/Modules/FinancialManagement/app/Http/Controllers/FinanceController.php b/Modules/FinancialManagement/app/Http/Controllers/FinanceController.php index 02b0601..fb2bd5f 100644 --- a/Modules/FinancialManagement/app/Http/Controllers/FinanceController.php +++ b/Modules/FinancialManagement/app/Http/Controllers/FinanceController.php @@ -71,7 +71,7 @@ class FinanceController extends Controller $projects = Project::whereIn('projects.id', $this->availableProjectIdsQuery()) ->select('id', 'ulid', 'name', 'code', 'contract_value', 'last_billed_percentage') ->where('current_wizard_step', '>=', 7) - ->whereNotIn('status', ['completed', 'closed']) + ->where('status', '!=', 'closed') ->get(); return Inertia::render('FinancialManagement::Invoices/Create', [ @@ -280,14 +280,14 @@ class FinanceController extends Controller $hasFileInfo = extension_loaded('fileinfo'); $rules = ['media' => 'required|file|max:10240']; if ($hasFileInfo) { - $rules['media'] .= '|mimes:pdf,jpg,jpeg,png'; + $rules['media'] .= '|mimes:pdf'; } $validated = $request->validate($rules); $file = $request->file('media'); if (!$hasFileInfo) { $extension = strtolower($file->getClientOriginalExtension() ?: pathinfo($file->getClientOriginalName(), PATHINFO_EXTENSION)); - if (!in_array($extension, ['pdf', 'jpg', 'jpeg', 'png'])) { + if ($extension !== 'pdf') { return back()->withErrors(['media' => 'The proof must be a file of type: pdf, jpg, jpeg, png.']); } } @@ -307,19 +307,31 @@ class FinanceController extends Controller { abort_unless($this->canAccessProject($request->user(), $retentionEntry->project_id), 403); $isApprover = $request->user()->user_type === 'admin' - || $request->user()->roles()->whereIn('name', ['Super Admin', 'admin'])->exists(); + || $request->user()->roles()->whereIn('name', ['Super Admin', 'admin', 'Admin', 'Project Manager', 'project_manager'])->exists(); if (!$isApprover) { - return back()->with('error', 'Only Admin or Super Admin can mark retention as paid.'); + return back()->with('error', 'Only Project Manager, Admin, or Super Admin can mark retention as paid.'); + } + + $request->validate([ + 'media' => ['nullable', 'file', 'mimes:pdf', 'max:10240'], + ]); + + $mediaPath = null; + $mediaName = null; + if ($request->hasFile('media')) { + $file = $request->file('media'); + $mediaPath = $file->store('retention_proofs', 'public'); + $mediaName = $file->getClientOriginalName(); } try { - $this->billingService->markRetentionAsPaid($retentionEntry); + $this->billingService->markRetentionAsPaid($retentionEntry, $mediaPath, $mediaName); } catch (\InvalidArgumentException $e) { return back()->with('error', $e->getMessage()); } - return back()->with('success', 'Retention marked as paid.'); + return back()->with('success', 'Retention marked as paid with proof attached.'); } public function viewRetentionMedia(Request $request, RetentionEntry $retentionEntry) @@ -376,7 +388,9 @@ class FinanceController extends Controller 'reason' => 'required|string|max:1000', ]); - $project = Project::where('ulid', $validated['project_ulid'])->firstOrFail(); + $project = Project::whereIn('id', $this->availableProjectIdsQuery()) + ->where('ulid', $validated['project_ulid']) + ->firstOrFail(); $cashAdvance = \Modules\FinancialManagement\Models\CashAdvance::create([ 'project_id' => $project->id, diff --git a/Modules/FinancialManagement/app/Services/ProgressBillingService.php b/Modules/FinancialManagement/app/Services/ProgressBillingService.php index f7211c2..a72e756 100644 --- a/Modules/FinancialManagement/app/Services/ProgressBillingService.php +++ b/Modules/FinancialManagement/app/Services/ProgressBillingService.php @@ -159,17 +159,31 @@ class ProgressBillingService ]); } - public function markRetentionAsPaid(RetentionEntry $release): void + public function markRetentionAsPaid(RetentionEntry $entry, ?string $mediaPath = null, ?string $mediaOriginalName = null): void { - if ($release->type !== 'credit' || $release->status !== 'submitted') { - throw new \InvalidArgumentException('Only submitted retention releases can be marked as paid.'); - } - - $release->update([ + $data = [ 'status' => 'paid', 'paid_by' => auth()->id(), 'paid_at' => now(), - 'description' => 'Retention released and paid to contractor', - ]); + ]; + + if ($mediaPath) { + $data['media_path'] = $mediaPath; + $data['media_original_name'] = $mediaOriginalName; + } + + if ($entry->type === 'debit') { + $data['type'] = 'credit'; + $data['description'] = 'Retention released and paid'; + $entry->update($data); + return; + } + + if ($entry->type !== 'credit' || !in_array($entry->status, ['submitted', 'posted', 'pending', null], true)) { + throw new \InvalidArgumentException('Retention entry cannot be marked as paid.'); + } + + $data['description'] = 'Retention released and paid to contractor'; + $entry->update($data); } } diff --git a/Modules/FinancialManagement/resources/js/Pages/Invoices/Show.tsx b/Modules/FinancialManagement/resources/js/Pages/Invoices/Show.tsx index 9c4cdb6..0b96c5e 100644 --- a/Modules/FinancialManagement/resources/js/Pages/Invoices/Show.tsx +++ b/Modules/FinancialManagement/resources/js/Pages/Invoices/Show.tsx @@ -159,14 +159,28 @@ export default function Show({ invoice }: Props) { {/* Retention Entries */} {invoice.retention_entries.length > 0 && ( - Retention Entries + +
+ Retention Entries +

Manage project retention releases and payment authorizations

+
+ + + +
TypeAmountDescriptionDate {invoice.retention_entries.map(re => ( - {re.type} + + + {re.type === 'debit' ? 'Retention' : 'Released'} + + {formatCurrency(re.amount)} {re.description} {new Date(re.created_at).toLocaleDateString()} diff --git a/Modules/FinancialManagement/resources/js/Pages/Retention/Index.tsx b/Modules/FinancialManagement/resources/js/Pages/Retention/Index.tsx index 57f5388..c7817a7 100644 --- a/Modules/FinancialManagement/resources/js/Pages/Retention/Index.tsx +++ b/Modules/FinancialManagement/resources/js/Pages/Retention/Index.tsx @@ -29,12 +29,18 @@ interface Props extends PageProps { const formatCurrency = (v: number | string) => new Intl.NumberFormat('en-PH', { style: 'currency', currency: 'PHP' }).format(Number(v)); export default function Index({ entries, projects, projectTotals, filters }: Props) { - const { flash } = usePage().props; + const { flash, auth } = usePage().props; const [projectFilter, setProjectFilter] = useState(filters.project_id || 'all'); const [submitProject, setSubmitProject] = useState<{ ulid: string; name: string } | null>(null); const [submitting, setSubmitting] = useState(false); const [mediaViewerEntry, setMediaViewerEntry] = useState(null); + const [selectedEntry, setSelectedEntry] = useState(null); + const [markPaidEntry, setMarkPaidEntry] = useState(null); const mediaRef = useRef(null); + const markPaidMediaRef = useRef(null); + + const isApprover = auth?.user?.user_type === 'admin' || + (auth?.user as any)?.roles?.some((r: any) => ['Super Admin', 'admin', 'Admin', 'Project Manager', 'project_manager'].includes(r.name)); // Items arrays for Select label lookup const projectFilterItems = useMemo(() => [{ value: 'all', label: 'All Projects' }, ...projects.map(p => ({ value: p.ulid, label: p.name }))], [projects]); @@ -59,18 +65,34 @@ export default function Index({ entries, projects, projectTotals, filters }: Pro }); }; + const handleMarkPaidWithProof = (event: FormEvent) => { + event.preventDefault(); + const file = markPaidMediaRef.current?.files?.[0]; + if (!file || !markPaidEntry) return; + setSubmitting(true); + const formData = new FormData(); + formData.append('media', file); + formData.append('_method', 'PATCH'); + router.post(route('retention.paid', markPaidEntry.ulid), formData, { + forceFormData: true, + onSuccess: () => setMarkPaidEntry(null), + onFinish: () => setSubmitting(false), + }); + }; + return ( -

Retention Ledger

+

Retention Management

} > - +
{flash?.success &&
{flash.success}
} + {flash?.error &&
{flash.error}
} {/* Per-project totals */} {Object.keys(projectTotals).length > 0 && ( @@ -102,9 +124,11 @@ export default function Index({ entries, projects, projectTotals, filters }: Pro View Proof )} - + {isApprover && ( + + )} )}
@@ -138,18 +162,28 @@ export default function Index({ entries, projects, projectTotals, filters }: Pro {entries.data.length === 0 ? ( No retention entries. ) : entries.data.map((e) => ( - + setSelectedEntry(e)} + > {e.project?.name || 'Project unavailable'} {e.invoice?.invoice_number || '-'} - {e.type} - {formatCurrency(e.amount)} + + + {e.type === 'debit' ? 'Retention' : 'Released'} + + + {formatCurrency(e.amount)} {e.description || '-'} {new Date(e.created_at).toLocaleDateString()} - - {e.media_path && ( + e.stopPropagation()}> + {e.media_path ? ( + ) : ( + )} @@ -160,6 +194,80 @@ export default function Index({ entries, projects, projectTotals, filters }: Pro
+ {/* Retention Entry Details Modal */} + { if (!open) setSelectedEntry(null); }}> + + + Retention Entry Details + + {selectedEntry && ( +
+
+
+ Project + {selectedEntry.project?.name || 'N/A'} +
+ {selectedEntry.invoice && ( +
+ Invoice Number + {selectedEntry.invoice.invoice_number} +
+ )} +
+ Entry Type + + {selectedEntry.type === 'debit' ? 'Retention' : 'Released'} + +
+
+ Amount + {formatCurrency(selectedEntry.amount)} +
+
+ Date Created + {new Date(selectedEntry.created_at).toLocaleString()} +
+
+ + {selectedEntry.description && ( +
+

Description

+

{selectedEntry.description}

+
+ )} + +
+
+ {selectedEntry.media_path && ( + + )} +
+
+ {isApprover && (selectedEntry.type === 'debit' || (selectedEntry.type === 'credit' && selectedEntry.status === 'submitted')) && ( + + )} + +
+
+
+ )} +
+
+ { if (!open) setSubmitProject(null); }}> Submit Retention Release @@ -169,8 +277,8 @@ export default function Index({ entries, projects, projectTotals, filters }: Pro
- -

PDF, JPG, or PNG — max 10MB

+ +

PDF documents only — max 10MB

+ {/* Mark as Paid PDF Proof Modal */} + { if (!open) setMarkPaidEntry(null); }}> + + + Mark Retention as Paid + + +
+ Upload the Bank Transfer Receipt, Official Receipt, or Voucher PDF proof to complete payment. +
+
+ + +

PDF documents only — max 10MB

+
+
+ + +
+ +
+
+ { if (!open) setMediaViewerEntry(null); }}> Uploaded Retention Payment Proof diff --git a/Modules/FinancialManagement/routes/api.php b/Modules/FinancialManagement/routes/api.php index 1c17c08..e37493f 100644 --- a/Modules/FinancialManagement/routes/api.php +++ b/Modules/FinancialManagement/routes/api.php @@ -1,8 +1,6 @@ prefix('v1')->group(function () { - Route::apiResource('financialmanagements', FinancialManagementController::class)->names('financialmanagement'); -}); +// Scaffold API routes disabled until REST client contracts are authored. + diff --git a/Modules/Labors/routes/api.php b/Modules/Labors/routes/api.php index 9f00830..e37493f 100644 --- a/Modules/Labors/routes/api.php +++ b/Modules/Labors/routes/api.php @@ -1,19 +1,6 @@ prefix('v1')->group(function () { - Route::apiResource('labors', LaborController::class)->names('labors'); -}); diff --git a/Modules/MasterData/routes/api.php b/Modules/MasterData/routes/api.php index 1a134ba..e37493f 100644 --- a/Modules/MasterData/routes/api.php +++ b/Modules/MasterData/routes/api.php @@ -1,8 +1,6 @@ prefix('v1')->group(function () { - Route::apiResource('masterdatas', MasterDataController::class)->names('masterdata'); -}); +// Scaffold API routes disabled until REST client contracts are authored. + diff --git a/Modules/MaterialLogistics/app/Http/Controllers/PurchaseOrderController.php b/Modules/MaterialLogistics/app/Http/Controllers/PurchaseOrderController.php index 48513b2..63e461a 100644 --- a/Modules/MaterialLogistics/app/Http/Controllers/PurchaseOrderController.php +++ b/Modules/MaterialLogistics/app/Http/Controllers/PurchaseOrderController.php @@ -468,7 +468,7 @@ class PurchaseOrderController extends Controller ]; if ($hasFileInfo) { - $rules['receipt'] .= '|mimes:pdf,jpg,jpeg,png'; + $rules['receipt'] .= '|mimes:pdf'; } $validated = $request->validate($rules); @@ -477,7 +477,7 @@ class PurchaseOrderController extends Controller if (!$hasFileInfo) { $extension = strtolower($file->getClientOriginalExtension() ?: pathinfo($file->getClientOriginalName(), PATHINFO_EXTENSION)); - if (!in_array($extension, ['pdf', 'jpg', 'jpeg', 'png'])) { + if ($extension !== 'pdf') { return back()->withErrors(['receipt' => 'The receipt must be a file of type: pdf, jpg, jpeg, png.']); } } diff --git a/Modules/MaterialLogistics/resources/js/Pages/PurchaseOrders/Index.tsx b/Modules/MaterialLogistics/resources/js/Pages/PurchaseOrders/Index.tsx index 823183e..fb412af 100644 --- a/Modules/MaterialLogistics/resources/js/Pages/PurchaseOrders/Index.tsx +++ b/Modules/MaterialLogistics/resources/js/Pages/PurchaseOrders/Index.tsx @@ -193,8 +193,8 @@ export default function Index({ purchaseOrders, warehouses }: Props) {
- -

PDF, JPG, or PNG — max 10MB

+ +

PDF documents only — max 10MB

{errors.receipt && (

{errors.receipt}

)} diff --git a/Modules/MaterialLogistics/resources/js/Pages/PurchaseOrders/Show.tsx b/Modules/MaterialLogistics/resources/js/Pages/PurchaseOrders/Show.tsx index 79a5a3f..960a541 100644 --- a/Modules/MaterialLogistics/resources/js/Pages/PurchaseOrders/Show.tsx +++ b/Modules/MaterialLogistics/resources/js/Pages/PurchaseOrders/Show.tsx @@ -343,8 +343,8 @@ export default function Show({ purchaseOrder, warehouses, budgetAnalysis }: Prop
- -

PDF, JPG, or PNG — max 10MB

+ +

PDF documents only — max 10MB

{errors.receipt && (

{errors.receipt}

)} diff --git a/Modules/MaterialLogistics/routes/api.php b/Modules/MaterialLogistics/routes/api.php index 8d61b54..e37493f 100644 --- a/Modules/MaterialLogistics/routes/api.php +++ b/Modules/MaterialLogistics/routes/api.php @@ -1,8 +1,6 @@ prefix('v1')->group(function () { - Route::apiResource('materiallogistics', MaterialLogisticsController::class)->names('materiallogistics'); -}); +// Scaffold API routes disabled until REST client contracts are authored. + diff --git a/Modules/MaterialsCatalog/routes/api.php b/Modules/MaterialsCatalog/routes/api.php index 6ffe299..e37493f 100644 --- a/Modules/MaterialsCatalog/routes/api.php +++ b/Modules/MaterialsCatalog/routes/api.php @@ -1,8 +1,6 @@ prefix('v1')->group(function () { - Route::apiResource('materialscatalogs', MaterialsCatalogController::class)->names('materialscatalog'); -}); +// Scaffold API routes disabled until REST client contracts are authored. + diff --git a/Modules/ProjectDocuments/routes/api.php b/Modules/ProjectDocuments/routes/api.php index e084f7f..e37493f 100644 --- a/Modules/ProjectDocuments/routes/api.php +++ b/Modules/ProjectDocuments/routes/api.php @@ -1,19 +1,6 @@ prefix('v1')->group(function () { - Route::apiResource('projectdocuments', ProjectDocumentsController::class)->names('projectdocuments'); -}); diff --git a/Modules/ProjectManagement/resources/js/Pages/Projects/Modules/Documents.tsx b/Modules/ProjectManagement/resources/js/Pages/Projects/Modules/Documents.tsx index 0ac28b3..f6c08b6 100644 --- a/Modules/ProjectManagement/resources/js/Pages/Projects/Modules/Documents.tsx +++ b/Modules/ProjectManagement/resources/js/Pages/Projects/Modules/Documents.tsx @@ -455,7 +455,7 @@ export default function Documents({ project, currentTab, documents, categories,
- { if (e.target.files?.[0]) form.setData('file', e.target.files[0]); }} /> + { if (e.target.files?.[0]) form.setData('file', e.target.files[0]); }} /> {form.errors.file &&

{form.errors.file}

}
diff --git a/Modules/ProjectManagement/routes/api.php b/Modules/ProjectManagement/routes/api.php index fcd4b49..e37493f 100644 --- a/Modules/ProjectManagement/routes/api.php +++ b/Modules/ProjectManagement/routes/api.php @@ -1,8 +1,6 @@ prefix('v1')->group(function () { - Route::apiResource('projectmanagements', ProjectManagementController::class)->names('projectmanagement'); -}); +// Scaffold API routes disabled until REST client contracts are authored. + diff --git a/Modules/ProjectProgress/routes/api.php b/Modules/ProjectProgress/routes/api.php index 39e0f17..e37493f 100644 --- a/Modules/ProjectProgress/routes/api.php +++ b/Modules/ProjectProgress/routes/api.php @@ -1,19 +1,6 @@ prefix('v1')->group(function () { - Route::apiResource('projectprogress', ProjectProgressController::class)->names('projectprogress'); -}); diff --git a/Modules/ProjectQualityControl/routes/api.php b/Modules/ProjectQualityControl/routes/api.php index 898f417..e37493f 100644 --- a/Modules/ProjectQualityControl/routes/api.php +++ b/Modules/ProjectQualityControl/routes/api.php @@ -1,19 +1,6 @@ prefix('v1')->group(function () { - Route::apiResource('projectqualitycontrol', ProjectQualityControlController::class)->names('projectqualitycontrol'); -}); diff --git a/Modules/ProjectRFIs/routes/api.php b/Modules/ProjectRFIs/routes/api.php index b98f4a7..e37493f 100644 --- a/Modules/ProjectRFIs/routes/api.php +++ b/Modules/ProjectRFIs/routes/api.php @@ -1,19 +1,6 @@ prefix('v1')->group(function () { - Route::apiResource('projectrfis', ProjectRFIsController::class)->names('projectrfis'); -}); diff --git a/Modules/ProjectReports/routes/api.php b/Modules/ProjectReports/routes/api.php index e660e3b..e37493f 100644 --- a/Modules/ProjectReports/routes/api.php +++ b/Modules/ProjectReports/routes/api.php @@ -1,19 +1,6 @@ prefix('v1')->group(function () { - Route::apiResource('projectreports', ProjectReportsController::class)->names('projectreports'); -}); diff --git a/Modules/ProjectSafety/routes/api.php b/Modules/ProjectSafety/routes/api.php index 3200046..e37493f 100644 --- a/Modules/ProjectSafety/routes/api.php +++ b/Modules/ProjectSafety/routes/api.php @@ -1,19 +1,6 @@ prefix('v1')->group(function () { - Route::apiResource('projectsafety', ProjectSafetyController::class)->names('projectsafety'); -}); diff --git a/Modules/RolesPermissions/routes/api.php b/Modules/RolesPermissions/routes/api.php index fa4b1f0..e37493f 100644 --- a/Modules/RolesPermissions/routes/api.php +++ b/Modules/RolesPermissions/routes/api.php @@ -1,19 +1,6 @@ prefix('v1')->group(function () { - Route::apiResource('rolespermissions', RolesPermissionsController::class)->names('rolespermissions'); -}); diff --git a/Modules/TaskManagement/resources/js/Pages/Tasks/Index.tsx b/Modules/TaskManagement/resources/js/Pages/Tasks/Index.tsx index 7e1cc4b..7569de8 100644 --- a/Modules/TaskManagement/resources/js/Pages/Tasks/Index.tsx +++ b/Modules/TaskManagement/resources/js/Pages/Tasks/Index.tsx @@ -235,22 +235,6 @@ export default function Tasks({ project, employees, availableMaterials, delayRea ))} - - -
@@ -423,17 +407,22 @@ export default function Tasks({ project, employees, availableMaterials, delayRea {/* Task Details Dialog for Kanban Mode */} !open && setDetailsTaskUlid(null)}> - - - {detailsTask?.name} - Details + + + + {detailsTask?.name} + · Task Details + {detailsTask && ( -
+
{/* Status and transition actions bar */} -
-
-

Status

-
{renderStatusBadge(detailsTask.status)}
+
+
+
+

Status

+
{renderStatusBadge(detailsTask.status)}
+
{canManageTaskStatus && ( @@ -489,7 +478,7 @@ export default function Tasks({ project, employees, availableMaterials, delayRea This task is closed and cannot be moved. )} - @@ -498,43 +487,45 @@ export default function Tasks({ project, employees, availableMaterials, delayRea
{detailsTask.description && ( -
-

Description

-

{detailsTask.description}

+
+

Description

+

{detailsTask.description}

)} {/* Task details grid */} -
+
-

Assignees

-

{detailsTask.users?.map((u: any) => u.name).join(', ') || 'Unassigned'}

+

Assignees

+

u.name).join(', ') || 'Unassigned'}> + {detailsTask.users?.map((u: any) => u.name).join(', ') || 'Unassigned'} +

-

Total Cost

-

{formatCurrency(detailsTask.total_cost)}

+

Total Cost

+

{formatCurrency(detailsTask.total_cost)}

-

Labor Cost (Est.)

-

{formatCurrency(detailsTask.labor_cost)}

+

Labor Cost (Est.)

+

{formatCurrency(detailsTask.labor_cost)}

-

Equipment Cost (Est.)

-

{formatCurrency(detailsTask.equipment_cost)}

+

Equipment Cost (Est.)

+

{formatCurrency(detailsTask.equipment_cost)}

-

Estimated Hours

-

{detailsTask.estimated_hours || 0}h

+

Estimated Hours

+

{detailsTask.estimated_hours || 0}h

-

Milestone

-

+

Milestone

+
{detailsTask.milestone ? ( - + {detailsTask.milestone.name} - ) : 'None'} -

+ ) : None} +
@@ -688,22 +679,6 @@ export default function Tasks({ project, employees, availableMaterials, delayRea ))} - - -
diff --git a/Modules/TaskManagement/routes/api.php b/Modules/TaskManagement/routes/api.php index ec954c7..e37493f 100644 --- a/Modules/TaskManagement/routes/api.php +++ b/Modules/TaskManagement/routes/api.php @@ -1,19 +1,6 @@ prefix('v1')->group(function () { - Route::apiResource('taskmanagement', TaskManagementController::class)->names('taskmanagement'); -}); diff --git a/Modules/TimelineScheduling/resources/js/Pages/Timeline/Index.tsx b/Modules/TimelineScheduling/resources/js/Pages/Timeline/Index.tsx index 1c76d27..f37ef3c 100644 --- a/Modules/TimelineScheduling/resources/js/Pages/Timeline/Index.tsx +++ b/Modules/TimelineScheduling/resources/js/Pages/Timeline/Index.tsx @@ -344,7 +344,6 @@ export default function Timeline({ project, milestones, milestoneStats, weatherC {m.actual_date && ( Done: {new Date(m.actual_date).toLocaleDateString('en-PH', { month: 'short', day: 'numeric', year: 'numeric' })} - {m.days_delayed > 0 && (+{m.days_delayed}d late)} )}
diff --git a/Modules/TimelineScheduling/routes/api.php b/Modules/TimelineScheduling/routes/api.php index 469721b..e37493f 100644 --- a/Modules/TimelineScheduling/routes/api.php +++ b/Modules/TimelineScheduling/routes/api.php @@ -1,8 +1,6 @@ prefix('v1')->group(function () { - Route::apiResource('timelineschedulings', TimelineSchedulingController::class)->names('timelinescheduling'); -}); +// Scaffold API routes disabled until REST client contracts are authored. + diff --git a/Modules/UserManagement/routes/api.php b/Modules/UserManagement/routes/api.php index 528f511..e37493f 100644 --- a/Modules/UserManagement/routes/api.php +++ b/Modules/UserManagement/routes/api.php @@ -1,8 +1,6 @@ prefix('v1')->group(function () { - Route::apiResource('usermanagements', UserManagementController::class)->names('usermanagement'); -}); +// Scaffold API routes disabled until REST client contracts are authored. + diff --git a/app/Http/Middleware/SecurityHeadersMiddleware.php b/app/Http/Middleware/SecurityHeadersMiddleware.php new file mode 100644 index 0000000..e4c85c6 --- /dev/null +++ b/app/Http/Middleware/SecurityHeadersMiddleware.php @@ -0,0 +1,32 @@ +environment('testing') || ! env('SECURITY_HEADERS_ENABLED', true)) { + return $response; + } + + $response->headers->set('X-Frame-Options', 'SAMEORIGIN'); + $response->headers->set('X-Content-Type-Options', 'nosniff'); + $response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin'); + $response->headers->set('Permissions-Policy', 'camera=(), microphone=(), geolocation=()'); + $cspScriptSrc = env('APP_ENV') === 'local' ? "'self' 'unsafe-inline' 'unsafe-eval'" : "'self'"; + $response->headers->set('Content-Security-Policy', "default-src 'self'; script-src {$cspScriptSrc}; style-src 'self' 'unsafe-inline' https://fonts.bunny.net https://fonts.googleapis.com; font-src 'self' https://fonts.bunny.net https://fonts.gstatic.com data:; img-src 'self' data: blob:; connect-src 'self';"); + + if ($request->isSecure() || env('APP_ENV') === 'production') { + $response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains'); + } + + return $response; + } +} diff --git a/check_db.php b/check_db.php deleted file mode 100644 index 82d7e74..0000000 --- a/check_db.php +++ /dev/null @@ -1,19 +0,0 @@ -make(Illuminate\Contracts\Console\Kernel::class); -$kernel->bootstrap(); - -echo "--- CONTRACTORS ---" . PHP_EOL; -$contractors = \Modules\ContractorManagement\Models\Contractor::all(); -foreach ($contractors as $c) { - echo "ID: {$c->id} | Company: {$c->company_name} | Email: {$c->email}" . PHP_EOL; -} - -echo PHP_EOL . "--- USERS ---" . PHP_EOL; -$users = \App\Models\User::with('roles')->get(); -foreach ($users as $u) { - $roles = $u->roles->pluck('name')->implode(', '); - echo "ID: {$u->id} | Name: {$u->name} | Email: {$u->email} | Type: {$u->user_type} | Role: {$roles} | Contractor ID: {$u->contractor_id}" . PHP_EOL; -} diff --git a/check_user.php b/check_user.php deleted file mode 100644 index d638272..0000000 --- a/check_user.php +++ /dev/null @@ -1 +0,0 @@ -make(Illuminate\Contracts\Console\Kernel::class)->bootstrap(); foreach (App\Models\User::with('roles')->get() as \) { echo \->name . ' | ' . \->email . ' | Roles: ' . \->roles->pluck('name')->implode(',') . PHP_EOL; } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index df871c7..b775a81 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,7 @@ "requires": true, "packages": { "": { + "name": "GSB-Construction", "dependencies": { "@base-ui/react": "^1.3.0", "@fontsource-variable/geist": "^5.2.8", @@ -1226,12 +1227,12 @@ } }, "node_modules/@hono/node-server": { - "version": "1.19.17", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.17.tgz", - "integrity": "sha512-dSneS5qhiauZWGDCeK4o695Xd9nUNjviSZCMQrj10eetr8Uln1ucn6bbphOM6UynAMMtNIzZNSpL9vnASJwrPQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.0.tgz", + "integrity": "sha512-XovyyCCnBzW+zKu+z/zq8hwNs4KOR5rEMAOxo2f40Q5xoOI37IMm6MIg2COOUtUApo0i6850MTBKH2u4QLGIqg==", "license": "MIT", "engines": { - "node": ">=18.14.1" + "node": ">=20" }, "peerDependencies": { "hono": "^4" @@ -1417,12 +1418,12 @@ } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.27.1", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz", - "integrity": "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==", + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.30.0.tgz", + "integrity": "sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==", "license": "MIT", "dependencies": { - "@hono/node-server": "^1.19.9", + "@hono/node-server": "^1.19.9 || ^2.0.5", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", @@ -3937,9 +3938,9 @@ } }, "node_modules/fast-uri": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz", - "integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", + "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", "funding": [ { "type": "github", @@ -4366,9 +4367,9 @@ "license": "MIT" }, "node_modules/hono": { - "version": "4.12.32", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.32.tgz", - "integrity": "sha512-XcuyW9qE2kJn07PkecMOBd5Vq/hMy7mmGw+idz1yblbg9N17ijJODrvPkn7/dwL3Kulj8LcRJ69DLOWf91dRUg==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.0.tgz", + "integrity": "sha512-jhunvfHWxd7J5EFfSgH4xsYJzSe/lfqbUCxiyyeaQasUsXeEHXtzVid+7EOGByc5JnFa23SSFL3Y2RV/z1T+eQ==", "license": "MIT", "engines": { "node": ">=16.9.0" diff --git a/resources/js/Components/ui/form.tsx b/resources/js/Components/ui/form.tsx index 63ac2f2..94356b2 100644 --- a/resources/js/Components/ui/form.tsx +++ b/resources/js/Components/ui/form.tsx @@ -36,8 +36,7 @@ function FormField({ }: FormFieldProps) { return (