From bee4a1f5ab4b8a0060e9afb626209c302c4eeca8 Mon Sep 17 00:00:00 2001 From: Jonathan Sykes Date: Sun, 7 Jun 2026 03:15:04 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20complete=20all=20plan=20phases=20?= =?UTF-8?q?=E2=80=94=20reports,=20cleanup=20market=20fragments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add Reports page with population/household/document/blotter/budget/project views - Add ReportsController with year-filtered queries for all report types - Add /reports module to config/modules.php - Register /barangay/reports in VueRouteMap and web.php - Remove unused market Home fragments (HomeCoopMember, HomeStoreOwner, etc.) - Remove leftover market Components/Market/ directory - Add Reports card to Home.vue admin quick access --- .../Barangay/ReportsController.php | 226 +++++++++++ app/Http/Controllers/Support/VueRouteMap.php | 3 + config/modules.php | 11 + .../js/Components/Market/PosHistoryCard.vue | 259 ------------ .../js/Components/Market/PosHistoryList.vue | 93 ----- .../js/Components/Market/PosTodayStats.vue | 108 ----- .../js/Components/Market/ProductCard.vue | 138 ------- resources/js/Components/Market/StoreCard.vue | 128 ------ .../Components/Market/UpdateProductModal.vue | 378 ------------------ resources/js/Pages/Barangay/Reports.vue | 336 ++++++++++++++++ .../js/Pages/Fragments/DocumentRepository.vue | 214 ---------- .../Pages/Fragments/GovernanceResolutions.vue | 182 --------- .../Pages/Fragments/Home/HomeCoopMember.vue | 153 ------- .../Pages/Fragments/Home/HomeCoopOfficer.vue | 169 -------- .../Pages/Fragments/Home/HomeCooperative.vue | 378 ------------------ .../js/Pages/Fragments/Home/HomeOperator.vue | 263 ------------ .../js/Pages/Fragments/Home/HomePublic.vue | 177 -------- .../js/Pages/Fragments/Home/HomeShared.vue | 117 ------ .../Pages/Fragments/Home/HomeStoreManager.vue | 141 ------- .../Pages/Fragments/Home/HomeStoreOwner.vue | 224 ----------- .../Fragments/Home/HomeSuperOperator.vue | 250 ------------ .../js/Pages/Fragments/Home/HomeUltimate.vue | 341 ---------------- .../Fragments/Home/OrgHierarchyExplorer.vue | 352 ---------------- resources/js/Pages/Home.vue | 1 + routes/web.php | 7 + 25 files changed, 584 insertions(+), 4065 deletions(-) create mode 100644 app/Http/Controllers/Barangay/ReportsController.php delete mode 100644 resources/js/Components/Market/PosHistoryCard.vue delete mode 100644 resources/js/Components/Market/PosHistoryList.vue delete mode 100644 resources/js/Components/Market/PosTodayStats.vue delete mode 100644 resources/js/Components/Market/ProductCard.vue delete mode 100644 resources/js/Components/Market/StoreCard.vue delete mode 100644 resources/js/Components/Market/UpdateProductModal.vue create mode 100644 resources/js/Pages/Barangay/Reports.vue delete mode 100644 resources/js/Pages/Fragments/DocumentRepository.vue delete mode 100644 resources/js/Pages/Fragments/GovernanceResolutions.vue delete mode 100644 resources/js/Pages/Fragments/Home/HomeCoopMember.vue delete mode 100644 resources/js/Pages/Fragments/Home/HomeCoopOfficer.vue delete mode 100644 resources/js/Pages/Fragments/Home/HomeCooperative.vue delete mode 100644 resources/js/Pages/Fragments/Home/HomeOperator.vue delete mode 100644 resources/js/Pages/Fragments/Home/HomePublic.vue delete mode 100644 resources/js/Pages/Fragments/Home/HomeShared.vue delete mode 100644 resources/js/Pages/Fragments/Home/HomeStoreManager.vue delete mode 100644 resources/js/Pages/Fragments/Home/HomeStoreOwner.vue delete mode 100644 resources/js/Pages/Fragments/Home/HomeSuperOperator.vue delete mode 100644 resources/js/Pages/Fragments/Home/HomeUltimate.vue delete mode 100644 resources/js/Pages/Fragments/Home/OrgHierarchyExplorer.vue diff --git a/app/Http/Controllers/Barangay/ReportsController.php b/app/Http/Controllers/Barangay/ReportsController.php new file mode 100644 index 0000000..efafa3a --- /dev/null +++ b/app/Http/Controllers/Barangay/ReportsController.php @@ -0,0 +1,226 @@ +acct_type, UserActions::ViewGlobalReports)) { + return ResponseHelper::returnUnauthorized(); + } + + $type = $request->input('type', 'population'); + $year = (int) $request->input('year', date('Y')); + + $data = match ($type) { + 'population' => $this->populationReport(), + 'households' => $this->householdsReport(), + 'documents' => $this->documentsReport($year), + 'blotters' => $this->blottersReport($year), + 'budget' => $this->budgetReport($year), + 'projects' => $this->projectsReport($year), + default => [], + }; + + return response()->json(['success' => true, 'data' => $data]); + } + + private function populationReport(): array + { + $total = Resident::count(); + $active = Resident::where('is_active', true)->count(); + $voters = Resident::where('voter_status', true)->count(); + + $genderBreakdown = Resident::select('gender', DB::raw('count(*) as count')) + ->whereNotNull('gender') + ->groupBy('gender') + ->pluck('count', 'gender') + ->toArray(); + + $civilBreakdown = Resident::select('civil_status', DB::raw('count(*) as count')) + ->whereNotNull('civil_status') + ->groupBy('civil_status') + ->pluck('count', 'civil_status') + ->toArray(); + + $purokBreakdown = Resident::select('purok', DB::raw('count(*) as count')) + ->whereNotNull('purok') + ->groupBy('purok') + ->orderBy('purok') + ->pluck('count', 'purok') + ->toArray(); + + // Age groups + $now = Carbon::now(); + $ageGroups = [ + '0–12 (Children)' => [0, 12], + '13–17 (Youth)' => [13, 17], + '18–30 (Young Adult)' => [18, 30], + '31–59 (Adult)' => [31, 59], + '60+ (Senior)' => [60, 150], + ]; + + $ageBreakdown = []; + foreach ($ageGroups as $label => [$min, $max]) { + $from = $now->copy()->subYears($max)->format('Y-m-d'); + $to = $now->copy()->subYears($min)->format('Y-m-d'); + $ageBreakdown[$label] = Resident::whereBetween('date_of_birth', [$from, $to])->count(); + } + + return compact('total', 'active', 'voters', 'genderBreakdown', 'civilBreakdown', 'purokBreakdown', 'ageBreakdown') + [ + 'total_residents' => $total, + 'active_residents' => $active, + 'registered_voters' => $voters, + 'gender_breakdown' => $genderBreakdown, + 'civil_status_breakdown'=> $civilBreakdown, + 'purok_breakdown' => $purokBreakdown, + 'age_breakdown' => $ageBreakdown, + ]; + } + + private function householdsReport(): array + { + $total = Household::count(); + $withElectricity = Household::where('has_electricity', true)->count(); + $withWater = Household::where('has_water', true)->count(); + $avgMembers = round(Household::withCount('chapterMembers')->avg('chapter_members_count') ?? 0, 1); + + $byOwnership = Household::select('ownership_type', DB::raw('count(*) as count')) + ->groupBy('ownership_type') + ->pluck('count', 'ownership_type') + ->toArray(); + + return [ + 'total_households' => $total, + 'with_electricity' => $withElectricity, + 'with_water' => $withWater, + 'avg_members' => $avgMembers, + 'by_ownership' => $byOwnership, + ]; + } + + private function documentsReport(int $year): array + { + $base = DocumentRequest::whereYear('created_at', $year); + + $total = (clone $base)->count(); + $claimed = (clone $base)->where('status', 'CLAIMED')->count(); + $revenue = (clone $base)->where('payment_status', 'PAID')->sum('base_fee'); + + $byStatus = (clone $base)->select('status', DB::raw('count(*) as count')) + ->groupBy('status') + ->pluck('count', 'status') + ->toArray(); + + $byType = (clone $base)->select( + 'barangay_request_types.name', + DB::raw('count(*) as count'), + DB::raw('sum(case when barangay_document_requests.payment_status = \'PAID\' then barangay_document_requests.base_fee else 0 end) as revenue') + ) + ->leftJoin('barangay_request_types', 'barangay_request_types.id', '=', 'barangay_document_requests.request_type_id') + ->groupBy('barangay_request_types.name') + ->get() + ->toArray(); + + return compact('total', 'claimed', 'revenue', 'byStatus', 'byType') + [ + 'total_revenue' => $revenue, + 'by_status' => $byStatus, + 'by_type' => $byType, + ]; + } + + private function blottersReport(int $year): array + { + $base = Blotter::whereYear('created_at', $year); + + $total = (clone $base)->count(); + $resolved = (clone $base)->whereIn('status', ['RESOLVED', 'SETTLED'])->count(); + $pending = (clone $base)->whereIn('status', ['FILED', 'FOR_HEARING'])->count(); + + $byStatus = (clone $base)->select('status', DB::raw('count(*) as count')) + ->groupBy('status') + ->pluck('count', 'status') + ->toArray(); + + $byType = (clone $base)->select('incident_type', DB::raw('count(*) as count')) + ->groupBy('incident_type') + ->pluck('count', 'incident_type') + ->toArray(); + + return [ + 'total' => $total, + 'resolved' => $resolved, + 'pending' => $pending, + 'by_status' => $byStatus, + 'by_incident_type'=> $byType, + ]; + } + + private function budgetReport(int $year): array + { + $income = BarangayBudget::income()->byYear($year)->sum('amount'); + $expense = BarangayBudget::expense()->byYear($year)->sum('amount'); + $balance = $income - $expense; + + $bySource = BarangayBudget::select('source', 'category', DB::raw('sum(amount) as amount')) + ->byYear($year) + ->groupBy('source', 'category') + ->orderBy('category') + ->get() + ->toArray(); + + return [ + 'summary' => compact('income', 'expense', 'balance') + [ + 'total_income' => $income, + 'total_expense' => $expense, + ], + 'by_source' => $bySource, + ]; + } + + private function projectsReport(int $year): array + { + $base = BarangayProject::whereYear('created_at', $year); + + $total = (clone $base)->count(); + $ongoing = (clone $base)->where('status', 'ONGOING')->count(); + $completed = (clone $base)->where('status', 'COMPLETED')->count(); + $budget = (clone $base)->sum('budget'); + + $byStatus = (clone $base)->select('status', DB::raw('count(*) as count')) + ->groupBy('status') + ->pluck('count', 'status') + ->toArray(); + + $byType = (clone $base)->select('type', DB::raw('count(*) as count')) + ->groupBy('type') + ->get() + ->toArray(); + + return [ + 'total' => $total, + 'ongoing' => $ongoing, + 'completed' => $completed, + 'total_budget' => $budget, + 'by_status' => $byStatus, + 'by_type' => $byType, + ]; + } +} diff --git a/app/Http/Controllers/Support/VueRouteMap.php b/app/Http/Controllers/Support/VueRouteMap.php index e70bf87..ac12816 100644 --- a/app/Http/Controllers/Support/VueRouteMap.php +++ b/app/Http/Controllers/Support/VueRouteMap.php @@ -81,6 +81,9 @@ class VueRouteMap // ── Budget '/barangay/budgetledger' => ['component' => 'Barangay.BudgetLedger', 'loginRequired' => true, 'module' => 'budget'], + + // ── Reports + '/barangay/reports' => ['component' => 'Barangay.Reports', 'loginRequired' => true, 'module' => 'reports'], ]; diff --git a/config/modules.php b/config/modules.php index 616d932..f497634 100644 --- a/config/modules.php +++ b/config/modules.php @@ -155,6 +155,17 @@ return [ 'description' => 'Fee schedules for certificate types and barangay services.', ], + /* + |-------------------------------------------------------------------------- + | Reports + |-------------------------------------------------------------------------- + */ + 'reports' => [ + 'enabled' => (bool) env('MODULE_REPORTS_ENABLED', true), + 'label' => 'Reports', + 'description' => 'Population, document, blotter, budget, and project reports.', + ], + /* |-------------------------------------------------------------------------- | Landing Pages diff --git a/resources/js/Components/Market/PosHistoryCard.vue b/resources/js/Components/Market/PosHistoryCard.vue deleted file mode 100644 index e3fe888..0000000 --- a/resources/js/Components/Market/PosHistoryCard.vue +++ /dev/null @@ -1,259 +0,0 @@ - - - - - diff --git a/resources/js/Components/Market/PosHistoryList.vue b/resources/js/Components/Market/PosHistoryList.vue deleted file mode 100644 index 57b2b47..0000000 --- a/resources/js/Components/Market/PosHistoryList.vue +++ /dev/null @@ -1,93 +0,0 @@ - - - - - diff --git a/resources/js/Components/Market/PosTodayStats.vue b/resources/js/Components/Market/PosTodayStats.vue deleted file mode 100644 index 20b683c..0000000 --- a/resources/js/Components/Market/PosTodayStats.vue +++ /dev/null @@ -1,108 +0,0 @@ - - - - - diff --git a/resources/js/Components/Market/ProductCard.vue b/resources/js/Components/Market/ProductCard.vue deleted file mode 100644 index 8166a09..0000000 --- a/resources/js/Components/Market/ProductCard.vue +++ /dev/null @@ -1,138 +0,0 @@ - - - - - diff --git a/resources/js/Components/Market/StoreCard.vue b/resources/js/Components/Market/StoreCard.vue deleted file mode 100644 index 7f0292f..0000000 --- a/resources/js/Components/Market/StoreCard.vue +++ /dev/null @@ -1,128 +0,0 @@ - - - - - diff --git a/resources/js/Components/Market/UpdateProductModal.vue b/resources/js/Components/Market/UpdateProductModal.vue deleted file mode 100644 index 5601ea0..0000000 --- a/resources/js/Components/Market/UpdateProductModal.vue +++ /dev/null @@ -1,378 +0,0 @@ - - - - - diff --git a/resources/js/Pages/Barangay/Reports.vue b/resources/js/Pages/Barangay/Reports.vue new file mode 100644 index 0000000..f968a58 --- /dev/null +++ b/resources/js/Pages/Barangay/Reports.vue @@ -0,0 +1,336 @@ + + + diff --git a/resources/js/Pages/Fragments/DocumentRepository.vue b/resources/js/Pages/Fragments/DocumentRepository.vue deleted file mode 100644 index a693172..0000000 --- a/resources/js/Pages/Fragments/DocumentRepository.vue +++ /dev/null @@ -1,214 +0,0 @@ - - - - - diff --git a/resources/js/Pages/Fragments/GovernanceResolutions.vue b/resources/js/Pages/Fragments/GovernanceResolutions.vue deleted file mode 100644 index 0da53da..0000000 --- a/resources/js/Pages/Fragments/GovernanceResolutions.vue +++ /dev/null @@ -1,182 +0,0 @@ - - - - - diff --git a/resources/js/Pages/Fragments/Home/HomeCoopMember.vue b/resources/js/Pages/Fragments/Home/HomeCoopMember.vue deleted file mode 100644 index 7297819..0000000 --- a/resources/js/Pages/Fragments/Home/HomeCoopMember.vue +++ /dev/null @@ -1,153 +0,0 @@ - - - - - diff --git a/resources/js/Pages/Fragments/Home/HomeCoopOfficer.vue b/resources/js/Pages/Fragments/Home/HomeCoopOfficer.vue deleted file mode 100644 index 1045888..0000000 --- a/resources/js/Pages/Fragments/Home/HomeCoopOfficer.vue +++ /dev/null @@ -1,169 +0,0 @@ - - - - - diff --git a/resources/js/Pages/Fragments/Home/HomeCooperative.vue b/resources/js/Pages/Fragments/Home/HomeCooperative.vue deleted file mode 100644 index 9e92cbf..0000000 --- a/resources/js/Pages/Fragments/Home/HomeCooperative.vue +++ /dev/null @@ -1,378 +0,0 @@ - - - - - diff --git a/resources/js/Pages/Fragments/Home/HomeOperator.vue b/resources/js/Pages/Fragments/Home/HomeOperator.vue deleted file mode 100644 index bdd56d2..0000000 --- a/resources/js/Pages/Fragments/Home/HomeOperator.vue +++ /dev/null @@ -1,263 +0,0 @@ - - - - - diff --git a/resources/js/Pages/Fragments/Home/HomePublic.vue b/resources/js/Pages/Fragments/Home/HomePublic.vue deleted file mode 100644 index a483b40..0000000 --- a/resources/js/Pages/Fragments/Home/HomePublic.vue +++ /dev/null @@ -1,177 +0,0 @@ - - - - - diff --git a/resources/js/Pages/Fragments/Home/HomeShared.vue b/resources/js/Pages/Fragments/Home/HomeShared.vue deleted file mode 100644 index e9a9454..0000000 --- a/resources/js/Pages/Fragments/Home/HomeShared.vue +++ /dev/null @@ -1,117 +0,0 @@ - - - diff --git a/resources/js/Pages/Fragments/Home/HomeStoreManager.vue b/resources/js/Pages/Fragments/Home/HomeStoreManager.vue deleted file mode 100644 index 0600ca6..0000000 --- a/resources/js/Pages/Fragments/Home/HomeStoreManager.vue +++ /dev/null @@ -1,141 +0,0 @@ - - - diff --git a/resources/js/Pages/Fragments/Home/HomeStoreOwner.vue b/resources/js/Pages/Fragments/Home/HomeStoreOwner.vue deleted file mode 100644 index 328ea6b..0000000 --- a/resources/js/Pages/Fragments/Home/HomeStoreOwner.vue +++ /dev/null @@ -1,224 +0,0 @@ - - - diff --git a/resources/js/Pages/Fragments/Home/HomeSuperOperator.vue b/resources/js/Pages/Fragments/Home/HomeSuperOperator.vue deleted file mode 100644 index e2ff697..0000000 --- a/resources/js/Pages/Fragments/Home/HomeSuperOperator.vue +++ /dev/null @@ -1,250 +0,0 @@ - - - - - diff --git a/resources/js/Pages/Fragments/Home/HomeUltimate.vue b/resources/js/Pages/Fragments/Home/HomeUltimate.vue deleted file mode 100644 index 92c5d54..0000000 --- a/resources/js/Pages/Fragments/Home/HomeUltimate.vue +++ /dev/null @@ -1,341 +0,0 @@ - - - - - \ No newline at end of file diff --git a/resources/js/Pages/Fragments/Home/OrgHierarchyExplorer.vue b/resources/js/Pages/Fragments/Home/OrgHierarchyExplorer.vue deleted file mode 100644 index 585e426..0000000 --- a/resources/js/Pages/Fragments/Home/OrgHierarchyExplorer.vue +++ /dev/null @@ -1,352 +0,0 @@ - - - - - diff --git a/resources/js/Pages/Home.vue b/resources/js/Pages/Home.vue index 12445eb..4eca098 100644 --- a/resources/js/Pages/Home.vue +++ b/resources/js/Pages/Home.vue @@ -31,6 +31,7 @@ const adminCards = [ { label: 'Projects', icon: '🏗️', route: '/Barangay/ManageProjects', color: 'bg-indigo-500' }, { label: 'Budget', icon: '💰', route: '/Barangay/BudgetLedger', color: 'bg-emerald-500' }, { label: 'Announcements', icon: '📢', route: '/ManageAnnouncements', color: 'bg-yellow-500' }, + { label: 'Reports', icon: '📊', route: '/barangay/reports', color: 'bg-rose-500' }, { label: 'Settings', icon: '⚙️', route: '/SystemSettings', color: 'bg-gray-500' }, ]; diff --git a/routes/web.php b/routes/web.php index 0940126..35a031d 100644 --- a/routes/web.php +++ b/routes/web.php @@ -12,6 +12,7 @@ use App\Http\Controllers\Admin\UserController; use App\Http\Controllers\Barangay\BlotterController; use App\Http\Controllers\Barangay\BlotterHearingController; use App\Http\Controllers\Barangay\BudgetController; +use App\Http\Controllers\Barangay\ReportsController; use App\Http\Controllers\Barangay\DocumentRequestController; use App\Http\Controllers\Barangay\HouseholdController; use App\Http\Controllers\Barangay\ProjectController; @@ -315,3 +316,9 @@ Route::get('/budget/fiscal-years', [BudgetController::class, 'fiscalYears'], ['m Route::post('/budget/create', [BudgetController::class, 'store'], ['middleware' => 'auth']); Route::post('/budget/update', [BudgetController::class, 'update'], ['middleware' => 'auth']); Route::post('/budget/delete', [BudgetController::class, 'destroy'], ['middleware' => 'auth']); + +// ───────────────────────────────────────────────────────────────────────────── +// Reports +// ───────────────────────────────────────────────────────────────────────────── + +Route::post('/reports/generate', [ReportsController::class, 'generate'], ['middleware' => 'auth']);