From 936ae1c6b269a32bb30dbc9b33c2b2f7c84d1acd Mon Sep 17 00:00:00 2001 From: Ajjj Date: Mon, 3 Aug 2026 18:28:52 +0800 Subject: [PATCH] feat: implement comprehensive dashboard controller and modular administrative and project management interfaces --- .../Http/Controllers/ApprovalController.php | 23 +- .../app/Services/ApprovalService.php | 22 +- .../js/Components/ApprovalTimeline.tsx | 6 +- .../resources/js/Pages/Approvals/Index.tsx | 6 +- .../resources/js/Pages/Approvals/Show.tsx | 11 +- .../Http/Controllers/ContractorController.php | 8 + .../resources/js/Pages/Contractors/Form.tsx | 2 +- .../Http/Controllers/DocumentController.php | 46 +-- .../seeders/DocumentCategorySeeder.php | 29 ++ .../resources/js/Pages/Documents/Index.tsx | 167 +++++---- .../Http/Controllers/FinanceController.php | 102 +++++- .../resources/js/Pages/CashAdvances/Index.tsx | 266 ++++++++++++++ .../resources/js/Pages/Invoices/Create.tsx | 4 +- .../resources/js/Pages/Invoices/Show.tsx | 86 +++-- Modules/FinancialManagement/routes/web.php | 7 + .../database/factories/LaborFactory.php | 2 +- .../Http/Controllers/MaterialController.php | 10 +- .../js/Pages/Inventory/OperationsForm.tsx | 44 ++- .../js/Pages/PurchaseOrders/Show.tsx | 4 +- .../resources/js/Pages/Requisitions/Form.tsx | 1 + .../resources/js/Pages/Requisitions/Show.tsx | 4 +- .../Http/Controllers/ProjectController.php | 74 +++- Modules/ProjectManagement/app/Models/Task.php | 2 +- .../resources/js/Components/ProjectForm.tsx | 34 +- .../resources/js/Pages/Projects/Index.tsx | 8 +- .../js/Pages/Projects/Modules/Documents.tsx | 54 ++- .../resources/js/Pages/Projects/Show.tsx | 2 +- .../resources/js/Pages/Projects/Wizard.tsx | 35 +- .../RolesPermissionsController.php | 4 +- .../RolesPermissionsDatabaseSeeder.php | 2 + .../app/Http/Controllers/TaskController.php | 34 +- .../resources/js/Components/KanbanBoard.tsx | 63 +++- .../resources/js/Pages/Tasks/Index.tsx | 62 ++-- .../Feature/TaskStatusAndProgressTest.php | 130 +++++++ .../app/Http/Controllers/UserController.php | 12 +- .../resources/js/Pages/Users/Create.tsx | 14 +- .../resources/js/Pages/Users/Index.tsx | 2 + app/Http/Controllers/DashboardController.php | 90 +++++ app/Providers/AppServiceProvider.php | 10 +- assign_admin_role.php | 15 + check_db.php | 19 + database/seeders/DatabaseSeeder.php | 1 + package-lock.json | 20 +- resources/js/Components/AppSidebar.tsx | 11 +- .../Dashboard/RoleAnalyticsBanner.tsx | 320 +++++++++++++++++ resources/js/Pages/Dashboard.tsx | 8 +- resources/js/lib/nav-config.ts | 15 +- tests/Feature/RoleBasedActionTest.php | 326 ++++++++++++++++++ 48 files changed, 1832 insertions(+), 385 deletions(-) create mode 100644 Modules/DocumentManagement/database/seeders/DocumentCategorySeeder.php create mode 100644 Modules/FinancialManagement/resources/js/Pages/CashAdvances/Index.tsx create mode 100644 Modules/TaskManagement/tests/Feature/TaskStatusAndProgressTest.php create mode 100644 assign_admin_role.php create mode 100644 check_db.php create mode 100644 resources/js/Components/Dashboard/RoleAnalyticsBanner.tsx create mode 100644 tests/Feature/RoleBasedActionTest.php diff --git a/Modules/ApprovalWorkflow/app/Http/Controllers/ApprovalController.php b/Modules/ApprovalWorkflow/app/Http/Controllers/ApprovalController.php index 59b2050..1f8217b 100644 --- a/Modules/ApprovalWorkflow/app/Http/Controllers/ApprovalController.php +++ b/Modules/ApprovalWorkflow/app/Http/Controllers/ApprovalController.php @@ -23,16 +23,27 @@ class ApprovalController extends Controller $user = $request->user(); $tab = $request->get('tab', 'pending'); + $isAdmin = $user->user_type === 'admin' || $user->hasRole(['Super Admin', 'admin', 'Main Contractor Admin']); + $isApprovingRole = $isAdmin || $user->hasRole(['Project Manager', 'Contractor Admin']); + $query = ApprovalChain::query(); if ($tab === 'history') { - $query->whereHas('steps', function ($q) use ($user) { - $q->where('approver_id', $user->id)->where('status', '!=', 'pending'); - }); + if ($isApprovingRole) { + $query->whereIn('status', ['approved', 'rejected']); + } else { + $query->whereHas('steps', function ($q) use ($user) { + $q->where('approver_id', $user->id)->where('status', '!=', 'pending'); + }); + } } else { - $query->whereHas('steps', function ($q) use ($user) { - $q->where('approver_id', $user->id)->where('status', 'pending'); - })->whereIn('status', ['pending', 'in_review']); + if ($isApprovingRole) { + $query->whereIn('status', ['pending', 'in_review']); + } else { + $query->whereHas('steps', function ($q) use ($user) { + $q->where('approver_id', $user->id)->where('status', 'pending'); + })->whereIn('status', ['pending', 'in_review']); + } } $chains = $query->with(['steps.approver:id,name', 'initiator:id,name']) diff --git a/Modules/ApprovalWorkflow/app/Services/ApprovalService.php b/Modules/ApprovalWorkflow/app/Services/ApprovalService.php index d3e81a0..37d1aa6 100644 --- a/Modules/ApprovalWorkflow/app/Services/ApprovalService.php +++ b/Modules/ApprovalWorkflow/app/Services/ApprovalService.php @@ -63,6 +63,7 @@ class ApprovalService $step = $this->getCurrentStepForApprover($chain, $approver); $step->update([ + 'approver_id' => $approver->id, 'status' => ApprovalStepStatus::Approved, 'notes' => $notes, 'acted_at' => now(), @@ -70,17 +71,12 @@ class ApprovalService StepApproved::dispatch($chain, $step); - // Check if all steps are approved - if ($chain->isFullyApproved()) { - $chain->transitionTo(ApprovalChainStatus::Approved); - ApprovalCompleted::dispatch($chain); - } else { - // Notify next approver - $nextStep = $chain->currentStep(); - if ($nextStep) { - ApprovalRequired::dispatch($chain, $nextStep); - } - } + // One admin/manager approval completes the chain for estimations, requisitions, and single-step requests + $chain->steps()->where('status', ApprovalStepStatus::Pending)->update([ + 'status' => ApprovalStepStatus::Skipped, + ]); + $chain->transitionTo(ApprovalChainStatus::Approved); + ApprovalCompleted::dispatch($chain); return $chain->fresh('steps.approver'); } @@ -126,7 +122,9 @@ class ApprovalService throw new \InvalidArgumentException('No pending step found in this approval chain.'); } - if ($step->approver_id !== $approver->id) { + $isAdmin = $approver->user_type === 'admin' || $approver->hasRole(['Super Admin', 'admin', 'Main Contractor Admin']); + + if ($step->approver_id !== $approver->id && !$isAdmin) { throw new \InvalidArgumentException('You are not the current approver for this step.'); } diff --git a/Modules/ApprovalWorkflow/resources/js/Components/ApprovalTimeline.tsx b/Modules/ApprovalWorkflow/resources/js/Components/ApprovalTimeline.tsx index 15ef6e7..e454078 100644 --- a/Modules/ApprovalWorkflow/resources/js/Components/ApprovalTimeline.tsx +++ b/Modules/ApprovalWorkflow/resources/js/Components/ApprovalTimeline.tsx @@ -22,12 +22,14 @@ const statusConfig: Record s.status !== 'skipped'); + return (
- {steps.map((step, idx) => { + {visibleSteps.map((step, idx) => { const config = statusConfig[step.status] || statusConfig.pending; const Icon = config.icon; - const isLast = idx === steps.length - 1; + const isLast = idx === visibleSteps.length - 1; return (
diff --git a/Modules/ApprovalWorkflow/resources/js/Pages/Approvals/Index.tsx b/Modules/ApprovalWorkflow/resources/js/Pages/Approvals/Index.tsx index 1af8ebf..2e6c386 100644 --- a/Modules/ApprovalWorkflow/resources/js/Pages/Approvals/Index.tsx +++ b/Modules/ApprovalWorkflow/resources/js/Pages/Approvals/Index.tsx @@ -78,7 +78,6 @@ export default function Index({ approvals, tab }: Props) { Type Initiated By Status - Steps Submitted Actions @@ -86,7 +85,7 @@ export default function Index({ approvals, tab }: Props) { {approvals.data.length === 0 ? ( - + {isHistory ? 'No past approvals found.' : "No pending approvals. You're all caught up! 🎉"} @@ -100,9 +99,6 @@ export default function Index({ approvals, tab }: Props) { {typeLabel(chain.status)} - - {chain.steps.filter(s => s.status === 'approved').length}/{chain.steps.length} - {new Date(chain.created_at).toLocaleDateString()} diff --git a/Modules/ApprovalWorkflow/resources/js/Pages/Approvals/Show.tsx b/Modules/ApprovalWorkflow/resources/js/Pages/Approvals/Show.tsx index 3b1f2c6..d098cae 100644 --- a/Modules/ApprovalWorkflow/resources/js/Pages/Approvals/Show.tsx +++ b/Modules/ApprovalWorkflow/resources/js/Pages/Approvals/Show.tsx @@ -40,7 +40,8 @@ export default function Show({ chain, breakdownData }: Props) { const [rejectNotes, setRejectNotes] = useState(''); const currentStep = chain.steps.find(s => s.status === 'pending'); - const isCurrentApprover = currentStep && currentStep.approver.id === auth.user.id; + const isAdmin = auth.user.user_type === 'admin' || auth.user.roles?.some((r: any) => ['Super Admin', 'admin', 'Main Contractor Admin'].includes(r.name)); + const isCurrentApprover = currentStep && (currentStep.approver.id === auth.user.id || isAdmin); const handleApprove = () => { router.patch(route('approvals.approve', chain.ulid), { notes: approveNotes || undefined }); @@ -113,14 +114,6 @@ export default function Show({ chain, breakdownData }: Props) { {/* Approvable Breakdown */} - {/* Timeline */} - - Approval Progress - - - - - {/* Action Buttons */} {isCurrentApprover && chain.status === 'in_review' && ( diff --git a/Modules/ContractorManagement/app/Http/Controllers/ContractorController.php b/Modules/ContractorManagement/app/Http/Controllers/ContractorController.php index fefdc75..97a816f 100644 --- a/Modules/ContractorManagement/app/Http/Controllers/ContractorController.php +++ b/Modules/ContractorManagement/app/Http/Controllers/ContractorController.php @@ -246,6 +246,14 @@ class ContractorController extends Controller public function approveInvoice(ContractorInvoice $invoice) { + $user = auth()->user(); + $isHigherUp = $user->user_type === 'admin' || + $user->roles()->whereIn('name', ['Super Admin', 'admin', 'Main Contractor Admin'])->exists(); + + if (!$isHigherUp) { + return back()->with('error', 'Unauthorized. Only Main Contractor Admin or Super Admin can approve subcontractor invoices.'); + } + try { $invoice->transitionTo(InvoiceStatus::Approved); } catch (\InvalidArgumentException $e) { diff --git a/Modules/ContractorManagement/resources/js/Pages/Contractors/Form.tsx b/Modules/ContractorManagement/resources/js/Pages/Contractors/Form.tsx index c444a43..e4af3f1 100644 --- a/Modules/ContractorManagement/resources/js/Pages/Contractors/Form.tsx +++ b/Modules/ContractorManagement/resources/js/Pages/Contractors/Form.tsx @@ -153,7 +153,7 @@ export default function Form({ contractor }: Props) { form.setData('create_admin', e.target.checked)} + onChange={(e: React.ChangeEvent) => form.setData('create_admin', e.target.checked)} />