diff --git a/Modules/Dashboard/Actions/.gitkeep b/Modules/Dashboard/Actions/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/Modules/Dashboard/Config/.gitkeep b/Modules/Dashboard/Config/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/Modules/Dashboard/Config/config.php b/Modules/Dashboard/Config/config.php deleted file mode 100644 index 2c58cb0..0000000 --- a/Modules/Dashboard/Config/config.php +++ /dev/null @@ -1,5 +0,0 @@ - 'Dashboard', -]; diff --git a/Modules/Dashboard/Console/.gitkeep b/Modules/Dashboard/Console/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/Modules/Dashboard/Database/Factories/.gitkeep b/Modules/Dashboard/Database/Factories/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/Modules/Dashboard/Database/Migrations/.gitkeep b/Modules/Dashboard/Database/Migrations/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/Modules/Dashboard/Database/Seeders/.gitkeep b/Modules/Dashboard/Database/Seeders/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/Modules/Dashboard/Database/Seeders/DashboardDatabaseSeeder.php b/Modules/Dashboard/Database/Seeders/DashboardDatabaseSeeder.php deleted file mode 100644 index 3cbe23c..0000000 --- a/Modules/Dashboard/Database/Seeders/DashboardDatabaseSeeder.php +++ /dev/null @@ -1,16 +0,0 @@ -call([]); - } -} diff --git a/Modules/Dashboard/Emails/.gitkeep b/Modules/Dashboard/Emails/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/Modules/Dashboard/Entities/.gitkeep b/Modules/Dashboard/Entities/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/Modules/Dashboard/Helpers/.gitkeep b/Modules/Dashboard/Helpers/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/Modules/Dashboard/Http/Controllers/.gitkeep b/Modules/Dashboard/Http/Controllers/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/Modules/Dashboard/Http/Controllers/DashboardController.php b/Modules/Dashboard/Http/Controllers/DashboardController.php deleted file mode 100644 index 341e9a2..0000000 --- a/Modules/Dashboard/Http/Controllers/DashboardController.php +++ /dev/null @@ -1,109 +0,0 @@ -kjcDashboardService = $kjcDashboardService; - $this->pkjDashboardService = $pkjDashboardService; - } - - /** - * Get dashboard overview data - */ - public function getOverview(Request $request): JsonResponse - { - try { - $filters = $request->only(['date_from', 'date_to', 'unit_id', 'camp_id', 'formation_id', 'government_id']); - $user = $request->user(); // Get authenticated user - - $overview = [ - 'total_units' => $this->kjcDashboardService->getTotalUnits($filters), - 'total_camps' => $this->kjcDashboardService->getTotalCamps($filters), - 'total_formations' => $this->kjcDashboardService->getTotalFormations($filters), - 'kjc' => [ - 'total_assets' => $this->kjcDashboardService->getTotalAssets($filters, $user), - 'operational_assets' => $this->kjcDashboardService->getOperationalAssets($filters, $user), - 'under_repair' => $this->kjcDashboardService->getUnderRepairAssets($filters, $user), - 'maintenance_due' => $this->kjcDashboardService->getMaintenanceDueAssets($filters, $user), - 'active_repairs' => $this->kjcDashboardService->getActiveRepairs($filters), - 'pending_reports' => $this->kjcDashboardService->getPendingReports($filters) - ], - 'pkj' => [ - 'total_assets' => $this->pkjDashboardService->getTotalAssets($filters, $user), - 'operational_assets' => $this->pkjDashboardService->getOperationalAssets($filters, $user), - 'under_repair' => $this->pkjDashboardService->getUnderRepairAssets($filters, $user), - 'maintenance_due' => $this->pkjDashboardService->getMaintenanceDueAssets($filters, $user), - 'active_repairs' => $this->pkjDashboardService->getActiveRepairs($filters), - 'pending_reports' => $this->pkjDashboardService->getPendingReports($filters) - ] - ]; - - return response()->json([ - 'success' => true, - 'data' => $overview - ]); - } catch (\Exception $e) { - return response()->json([ - 'success' => false, - 'message' => 'Failed to fetch dashboard overview', - 'error' => $e->getMessage() - ], 500); - } - } - - /** - * Get stat cards data - */ - public function getStatCards(Request $request): JsonResponse - { - try { - $filters = $request->only(['date_from', 'date_to', 'unit_id', 'camp_id', 'formation_id', 'government_id']); - $user = $request->user(); // Get authenticated user - - $statCards = [ - 'kjc' => [ - 'total_assets' => $this->kjcDashboardService->getTotalAssets($filters, $user), - 'operational_assets' => $this->kjcDashboardService->getOperationalAssets($filters, $user), - 'under_repair' => $this->kjcDashboardService->getUnderRepairAssets($filters, $user), - 'maintenance_due' => $this->kjcDashboardService->getMaintenanceDueAssets($filters, $user), - ], - 'pkj' => [ - 'total_assets' => $this->pkjDashboardService->getTotalAssets($filters, $user), - 'operational_assets' => $this->pkjDashboardService->getOperationalAssets($filters, $user), - 'under_repair' => $this->pkjDashboardService->getUnderRepairAssets($filters, $user), - 'maintenance_due' => $this->pkjDashboardService->getMaintenanceDueAssets($filters, $user), - ], - 'overview' => [ - 'total_units' => $this->kjcDashboardService->getTotalUnits($filters), - 'active_repairs' => $this->kjcDashboardService->getActiveRepairs($filters) + $this->pkjDashboardService->getActiveRepairs($filters), - 'pending_reports' => $this->kjcDashboardService->getPendingReports($filters) + $this->pkjDashboardService->getPendingReports($filters), - ] - ]; - - return response()->json([ - 'success' => true, - 'data' => $statCards - ]); - } catch (\Exception $e) { - return response()->json([ - 'success' => false, - 'message' => 'Failed to fetch stat cards', - 'error' => $e->getMessage() - ], 500); - } - } -} \ No newline at end of file diff --git a/Modules/Dashboard/Http/Controllers/KJC/KJCDashboardController.php b/Modules/Dashboard/Http/Controllers/KJC/KJCDashboardController.php deleted file mode 100644 index 7aeb2dd..0000000 --- a/Modules/Dashboard/Http/Controllers/KJC/KJCDashboardController.php +++ /dev/null @@ -1,302 +0,0 @@ -kjcDashboardService = $kjcDashboardService; - $this->kjcDashboardRepository = $kjcDashboardRepository; - } - - /** - * Get KJC entitlement vs holdings pie chart - */ - public function getEntitlementVsHoldingsPieChart(Request $request): JsonResponse - { - try { - $filters = $request->only(['date_from', 'date_to', 'unit_id', 'camp_id', 'formation_id', 'government_id']); - $user = $request->user(); // Get authenticated user - $chartData = $this->kjcDashboardService->getEntitlementVsHoldingsPieChart($filters, $user); - - return response()->json([ - 'success' => true, - 'data' => $chartData - ]); - } catch (\Exception $e) { - return response()->json([ - 'success' => false, - 'message' => 'Failed to fetch KJC entitlement vs holdings chart', - 'error' => $e->getMessage() - ], 500); - } - } - - /** - * Get KJC holdings status breakdown pie chart - */ - public function getHoldingsStatusBreakdownPieChart(Request $request): JsonResponse - { - try { - $filters = $request->only(['date_from', 'date_to', 'unit_id', 'camp_id', 'formation_id', 'government_id']); - $user = $request->user(); // Get authenticated user - $chartData = $this->kjcDashboardService->getHoldingsStatusBreakdownPieChart($filters, $user); - - return response()->json([ - 'success' => true, - 'data' => $chartData - ]); - } catch (\Exception $e) { - return response()->json([ - 'success' => false, - 'message' => 'Failed to fetch KJC holdings status breakdown chart', - 'error' => $e->getMessage() - ], 500); - } - } - - /** - * Get KJC unit performance chart - */ - public function getUnitPerformanceChart(Request $request): JsonResponse - { - try { - $filters = $request->only(['date_from', 'date_to', 'unit_id', 'camp_id', 'formation_id', 'government_id']); - $user = $request->user(); // Get authenticated user - $chartData = $this->kjcDashboardService->getUnitPerformanceChart($filters, $user); - - return response()->json([ - 'success' => true, - 'data' => $chartData - ]); - } catch (\Exception $e) { - return response()->json([ - 'success' => false, - 'message' => 'Failed to fetch KJC unit performance chart', - 'error' => $e->getMessage() - ], 500); - } - } - - /** - * Get KJC monthly trends chart - */ - public function getMonthlyTrendsChart(Request $request): JsonResponse - { - try { - $filters = $request->only(['date_from', 'date_to', 'unit_id', 'camp_id', 'formation_id', 'government_id']); - $user = $request->user(); // Get authenticated user - $chartData = $this->kjcDashboardService->getMonthlyTrendsChart($filters, $user); - - return response()->json([ - 'success' => true, - 'data' => $chartData - ]); - } catch (\Exception $e) { - return response()->json([ - 'success' => false, - 'message' => 'Failed to fetch KJC monthly trends chart', - 'error' => $e->getMessage() - ], 500); - } - } - - /** - * Get KJC historical metrics bar chart - */ - public function getHistoricalMetricsBarChart(Request $request): JsonResponse - { - try { - $filters = $request->only(['date_from', 'date_to', 'unit_id', 'camp_id', 'formation_id', 'government_id']); - $user = $request->user(); // Get authenticated user - $chartData = $this->kjcDashboardService->getHistoricalMetricsBarChart($filters, $user); - - return response()->json([ - 'success' => true, - 'data' => $chartData - ]); - } catch (\Exception $e) { - return response()->json([ - 'success' => false, - 'message' => 'Failed to fetch KJC historical metrics chart', - 'error' => $e->getMessage() - ], 500); - } - } - - /** - * Get drill-down data for entitlement vs holdings - */ - public function getEntitlementVsHoldingsDetails(Request $request): JsonResponse - { - try { - $filters = $request->only(['type', 'date_from', 'date_to', 'unit_id', 'camp_id', 'formation_id', 'government_id']); - - $type = $filters['type'] ?? 'entitlement'; - - if ($type === 'entitlement') { - // Return entitlement details - $entitlements = KJCAssetEntitlement::with(['kjcSubcategory']) - ->paginate(20); - - $data = [ - 'title' => 'KJC Asset Entitlements', - 'filters' => array_merge($filters, ['total_count' => $entitlements->total()]), - 'items' => $entitlements->items(), - 'pagination' => [ - 'current_page' => $entitlements->currentPage(), - 'per_page' => $entitlements->perPage(), - 'total' => $entitlements->total(), - 'last_page' => $entitlements->lastPage(), - 'from' => $entitlements->firstItem(), - 'to' => $entitlements->lastItem() - ] - ]; - } else { - // Return holdings details - $query = KJCAssetHolding::with(['unit', 'kjcCategory', 'kjcSubcategory', 'kjcModel']); - - if (isset($filters['date_from'])) { - $query->where('created_at', '>=', $filters['date_from']); - } - - if (isset($filters['date_to'])) { - $query->where('created_at', '<=', $filters['date_to']); - } - - if (isset($filters['unit_id'])) { - $query->where('unit_id', $filters['unit_id']); - } - - $holdings = $query->paginate(20); - - $data = [ - 'title' => 'KJC Asset Holdings', - 'filters' => array_merge($filters, ['total_count' => $holdings->total()]), - 'items' => $holdings->items(), - 'pagination' => [ - 'current_page' => $holdings->currentPage(), - 'per_page' => $holdings->perPage(), - 'total' => $holdings->total(), - 'last_page' => $holdings->lastPage(), - 'from' => $holdings->firstItem(), - 'to' => $holdings->lastItem() - ] - ]; - } - - return response()->json([ - 'success' => true, - 'data' => $data - ]); - } catch (\Exception $e) { - return response()->json([ - 'success' => false, - 'message' => 'Failed to fetch entitlement vs holdings details', - 'error' => $e->getMessage() - ], 500); - } - } - - /** - * Get drill-down data for holdings status - */ - public function getHoldingsStatusDetails(Request $request): JsonResponse - { - try { - $filters = $request->only(['status', 'date_from', 'date_to', 'unit_id', 'camp_id', 'formation_id', 'government_id']); - - $query = KJCAssetHolding::with(['unit', 'kjcCategory', 'kjcSubcategory', 'kjcModel']); - - if (isset($filters['status'])) { - $query->where('status', $filters['status']); - } - - if (isset($filters['date_from'])) { - $query->where('created_at', '>=', $filters['date_from']); - } - - if (isset($filters['date_to'])) { - $query->where('created_at', '<=', $filters['date_to']); - } - - if (isset($filters['unit_id'])) { - $query->where('unit_id', $filters['unit_id']); - } - - $holdings = $query->paginate(20); - - // Map status codes to readable names - $statusNames = [ - 'BP' => 'Beroperasi (Operational)', - 'BDG' => 'Baik Diselenggara (Well Maintained)', - 'TBDG' => 'Tidak Baik Diselenggara (Poorly Maintained)' - ]; - - $statusName = $statusNames[$filters['status']] ?? $filters['status']; - - return response()->json([ - 'success' => true, - 'data' => [ - 'title' => "KJC Assets with Status: {$statusName}", - 'filters' => array_merge($filters, ['total_count' => $holdings->total()]), - 'items' => $holdings->items(), - 'pagination' => [ - 'current_page' => $holdings->currentPage(), - 'per_page' => $holdings->perPage(), - 'total' => $holdings->total(), - 'last_page' => $holdings->lastPage(), - 'from' => $holdings->firstItem(), - 'to' => $holdings->lastItem() - ] - ] - ]); - } catch (\Exception $e) { - return response()->json([ - 'success' => false, - 'message' => 'Failed to fetch holdings status details', - 'error' => $e->getMessage() - ], 500); - } - } - - /** - * Get KJC asset details by status (drill-down table, paginated). - * Status can be 'null' for assets not yet assigned a status (DB column NULL). - */ - public function getAssetDetailsByStatus(Request $request, string $status): JsonResponse - { - try { - $filters = $request->only(['date_from', 'date_to', 'unit_id', 'camp_id', 'formation_id', 'government_id', 'page', 'per_page']); - $user = $request->user(); - - $status = trim($status); - $status = $status === 'null' ? '' : $status; - - $details = $this->kjcDashboardRepository->getAssetDetailsByStatus($status, $filters, $user); - - return response()->json([ - 'success' => true, - 'data' => $details - ]); - } catch (\Exception $e) { - return response()->json([ - 'success' => false, - 'message' => 'Failed to fetch KJC asset details', - 'error' => $e->getMessage() - ], 500); - } - } -} diff --git a/Modules/Dashboard/Http/Controllers/PKJ/PKJDashboardController.php b/Modules/Dashboard/Http/Controllers/PKJ/PKJDashboardController.php deleted file mode 100644 index 67ca286..0000000 --- a/Modules/Dashboard/Http/Controllers/PKJ/PKJDashboardController.php +++ /dev/null @@ -1,188 +0,0 @@ -pkjDashboardService = $pkjDashboardService; - $this->pkjDashboardRepository = $pkjDashboardRepository; - } - - /** - * Get PKJ entitlement vs holdings pie chart - */ - public function getEntitlementVsHoldingsPieChart(Request $request): JsonResponse - { - try { - $filters = $request->only(['date_from', 'date_to', 'unit_id', 'camp_id', 'formation_id', 'government_id']); - $user = $request->user(); // Get authenticated user - $chartData = $this->pkjDashboardService->getEntitlementVsHoldingsPieChart($filters, $user); - - return response()->json([ - 'success' => true, - 'data' => $chartData - ]); - } catch (\Exception $e) { - return response()->json([ - 'success' => false, - 'message' => 'Failed to fetch PKJ entitlement vs holdings chart', - 'error' => $e->getMessage() - ], 500); - } - } - - /** - * Get PKJ holdings status breakdown pie chart - */ - public function getHoldingsStatusBreakdownPieChart(Request $request): JsonResponse - { - try { - $filters = $request->only(['date_from', 'date_to', 'unit_id', 'camp_id', 'formation_id', 'government_id']); - $user = $request->user(); // Get authenticated user - $chartData = $this->pkjDashboardService->getHoldingsStatusBreakdownPieChart($filters, $user); - - return response()->json([ - 'success' => true, - 'data' => $chartData - ]); - } catch (\Exception $e) { - return response()->json([ - 'success' => false, - 'message' => 'Failed to fetch PKJ holdings status breakdown chart', - 'error' => $e->getMessage() - ], 500); - } - } - - /** - * Get PKJ monthly trends chart - */ - public function getMonthlyTrendsChart(Request $request): JsonResponse - { - try { - $filters = $request->only(['date_from', 'date_to', 'unit_id', 'camp_id', 'formation_id', 'government_id']); - $user = $request->user(); // Get authenticated user - $chartData = $this->pkjDashboardService->getMonthlyTrendsChart($filters, $user); - - return response()->json([ - 'success' => true, - 'data' => $chartData - ]); - } catch (\Exception $e) { - return response()->json([ - 'success' => false, - 'message' => 'Failed to fetch PKJ monthly trends chart', - 'error' => $e->getMessage() - ], 500); - } - } - - /** - * Get PKJ holdings by category bar chart - */ - public function getHoldingsByCategoryBarChart(Request $request): JsonResponse - { - try { - $filters = $request->only(['date_from', 'date_to', 'unit_id', 'camp_id', 'formation_id', 'government_id']); - $user = $request->user(); // Get authenticated user - $chartData = $this->pkjDashboardService->getHoldingsByCategoryBarChart($filters, $user); - - return response()->json([ - 'success' => true, - 'data' => $chartData - ]); - } catch (\Exception $e) { - return response()->json([ - 'success' => false, - 'message' => 'Failed to fetch PKJ holdings by category chart', - 'error' => $e->getMessage() - ], 500); - } - } - - /** - * Get PKJ category metrics bar chart (drill-down) - */ - public function getCategoryMetricsBarChart(Request $request, int $categoryId): JsonResponse - { - try { - $filters = $request->only(['date_from', 'date_to', 'unit_id', 'camp_id', 'formation_id', 'government_id']); - $user = $request->user(); // Get authenticated user - $chartData = $this->pkjDashboardService->getCategoryMetricsBarChart($categoryId, $filters, $user); - - return response()->json([ - 'success' => true, - 'data' => $chartData - ]); - } catch (\Exception $e) { - return response()->json([ - 'success' => false, - 'message' => 'Failed to fetch PKJ category metrics chart', - 'error' => $e->getMessage() - ], 500); - } - } - - /** - * Get PKJ category status breakdown pie chart (drill-down) - */ - public function getCategoryStatusBreakdownPieChart(Request $request, int $categoryId): JsonResponse - { - try { - $filters = $request->only(['date_from', 'date_to', 'unit_id', 'camp_id', 'formation_id', 'government_id']); - $user = $request->user(); // Get authenticated user - $chartData = $this->pkjDashboardService->getCategoryStatusBreakdownPieChart($categoryId, $filters, $user); - - return response()->json([ - 'success' => true, - 'data' => $chartData - ]); - } catch (\Exception $e) { - return response()->json([ - 'success' => false, - 'message' => 'Failed to fetch PKJ category status breakdown chart', - 'error' => $e->getMessage() - ], 500); - } - } - - /** - * Get PKJ asset details by category and status (drill-down) - */ - public function getAssetDetailsByCategoryAndStatus(Request $request, int $categoryId, string $status): JsonResponse - { - try { - $filters = $request->only(['date_from', 'date_to', 'unit_id', 'camp_id', 'formation_id', 'government_id', 'page', 'per_page']); - $user = $request->user(); // Get authenticated user - - // Convert 'null' string to empty string for null status handling - // Trim whitespace to ensure exact matching - $status = trim($status); - $status = $status === 'null' ? '' : $status; - - $details = $this->pkjDashboardRepository->getAssetDetailsByCategoryAndStatus($categoryId, $status, $filters, $user); - - return response()->json([ - 'success' => true, - 'data' => $details - ]); - } catch (\Exception $e) { - return response()->json([ - 'success' => false, - 'message' => 'Failed to fetch PKJ asset details', - 'error' => $e->getMessage() - ], 500); - } - } -} diff --git a/Modules/Dashboard/Http/Requests/.gitkeep b/Modules/Dashboard/Http/Requests/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/Modules/Dashboard/Jobs/.gitkeep b/Modules/Dashboard/Jobs/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/Modules/Dashboard/Notifications/.gitkeep b/Modules/Dashboard/Notifications/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/Modules/Dashboard/Policies/.gitkeep b/Modules/Dashboard/Policies/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/Modules/Dashboard/Providers/.gitkeep b/Modules/Dashboard/Providers/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/Modules/Dashboard/Providers/DashboardServiceProvider.php b/Modules/Dashboard/Providers/DashboardServiceProvider.php deleted file mode 100644 index c132690..0000000 --- a/Modules/Dashboard/Providers/DashboardServiceProvider.php +++ /dev/null @@ -1,169 +0,0 @@ -registerCommands(); - $this->registerCommandSchedules(); - $this->registerTranslations(); - $this->registerConfig(); - $this->registerViews(); - $this->loadMigrationsFrom(module_path($this->name, 'Database/Migrations')); - } - - /** - * Register the service provider. - */ - public function register(): void - { - $this->app->register(EventServiceProvider::class); - $this->app->register(RouteServiceProvider::class); - - // Register dashboard repositories - $this->app->bind( - \Modules\Dashboard\Repositories\Contracts\KJCDashboardRepositoryInterface::class, - \Modules\Dashboard\Repositories\KJCDashboardRepository::class - ); - - $this->app->bind( - \Modules\Dashboard\Repositories\Contracts\PKJDashboardRepositoryInterface::class, - \Modules\Dashboard\Repositories\PKJDashboardRepository::class - ); - - // Register dashboard services - $this->app->singleton(\Modules\Dashboard\Services\KJC\KJCDashboardService::class); - $this->app->singleton(\Modules\Dashboard\Services\PKJ\PKJDashboardService::class); - } - - /** - * Register commands in the format of Command::class - */ - protected function registerCommands(): void - { - // $this->commands([]); - } - - /** - * Register command Schedules. - */ - protected function registerCommandSchedules(): void - { - // $this->app->booted(function () { - // $schedule = $this->app->make(Schedule::class); - // $schedule->command('inspire')->hourly(); - // }); - } - - /** - * Register translations. - */ - public function registerTranslations(): void - { - $langPath = resource_path('lang/modules/'.$this->nameLower); - - if (is_dir($langPath)) { - $this->loadTranslationsFrom($langPath, $this->nameLower); - $this->loadJsonTranslationsFrom($langPath); - } else { - $this->loadTranslationsFrom(module_path($this->name, 'Lang'), $this->nameLower); - $this->loadJsonTranslationsFrom(module_path($this->name, 'Lang')); - } - } - - /** - * Register config. - */ - protected function registerConfig(): void - { - $configPath = module_path($this->name, config('modules.paths.generator.config.path')); - - if (is_dir($configPath)) { - $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($configPath)); - - foreach ($iterator as $file) { - if ($file->isFile() && $file->getExtension() === 'php') { - $config = str_replace($configPath.DIRECTORY_SEPARATOR, '', $file->getPathname()); - $config_key = str_replace([DIRECTORY_SEPARATOR, '.php'], ['.', ''], $config); - $segments = explode('.', $this->nameLower.'.'.$config_key); - - // Remove duplicated adjacent segments - $normalized = []; - foreach ($segments as $segment) { - if (end($normalized) !== $segment) { - $normalized[] = $segment; - } - } - - $key = ($config === 'config.php') ? $this->nameLower : implode('.', $normalized); - - $this->publishes([$file->getPathname() => config_path($config)], 'config'); - $this->merge_config_from($file->getPathname(), $key); - } - } - } - } - - /** - * Merge config from the given path recursively. - */ - protected function merge_config_from(string $path, string $key): void - { - $existing = config($key, []); - $module_config = require $path; - - config([$key => array_replace_recursive($existing, $module_config)]); - } - - /** - * Register views. - */ - public function registerViews(): void - { - $viewPath = resource_path('views/modules/'.$this->nameLower); - $sourcePath = module_path($this->name, 'Resources/Views'); - - $this->publishes([$sourcePath => $viewPath], ['views', $this->nameLower.'-module-views']); - - $this->loadViewsFrom(array_merge($this->getPublishableViewPaths(), [$sourcePath]), $this->nameLower); - - Blade::componentNamespace(config('modules.namespace').'\\' . $this->name . '\\View\\Components', $this->nameLower); - } - - /** - * Get the services provided by the provider. - */ - public function provides(): array - { - return []; - } - - private function getPublishableViewPaths(): array - { - $paths = []; - foreach (config('view.paths') as $path) { - if (is_dir($path.'/modules/'.$this->nameLower)) { - $paths[] = $path.'/modules/'.$this->nameLower; - } - } - - return $paths; - } -} diff --git a/Modules/Dashboard/Providers/EventServiceProvider.php b/Modules/Dashboard/Providers/EventServiceProvider.php deleted file mode 100644 index 29e6487..0000000 --- a/Modules/Dashboard/Providers/EventServiceProvider.php +++ /dev/null @@ -1,27 +0,0 @@ -> - */ - protected $listen = []; - - /** - * Indicates if events should be discovered. - * - * @var bool - */ - protected static $shouldDiscoverEvents = true; - - /** - * Configure the proper event listeners for email verification. - */ - protected function configureEmailVerification(): void {} -} diff --git a/Modules/Dashboard/Providers/RouteServiceProvider.php b/Modules/Dashboard/Providers/RouteServiceProvider.php deleted file mode 100644 index 3a50d7c..0000000 --- a/Modules/Dashboard/Providers/RouteServiceProvider.php +++ /dev/null @@ -1,49 +0,0 @@ -mapApiRoutes(); - } - - /** - * Define the "web" routes for the application. - * - * These routes all receive session state, CSRF protection, etc. - */ - protected function mapWebRoutes(): void - { - Route::middleware('web')->group(module_path($this->name, '/Routes/web.php')); - } - - /** - * Define the "api" routes for the application. - * - * These routes are typically stateless. - */ - protected function mapApiRoutes(): void - { - Route::middleware('api')->name('api.')->group(module_path($this->name, '/Routes/api.php')); - } -} diff --git a/Modules/Dashboard/Repositories/.gitkeep b/Modules/Dashboard/Repositories/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/Modules/Dashboard/Repositories/Contracts/.gitkeep b/Modules/Dashboard/Repositories/Contracts/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/Modules/Dashboard/Repositories/Contracts/KJCDashboardRepositoryInterface.php b/Modules/Dashboard/Repositories/Contracts/KJCDashboardRepositoryInterface.php deleted file mode 100644 index 3506236..0000000 --- a/Modules/Dashboard/Repositories/Contracts/KJCDashboardRepositoryInterface.php +++ /dev/null @@ -1,57 +0,0 @@ -visibleTo($user); - } - - $this->applyFilters($query, $filters, excludeDateFilter: true); - - return $query->sum('entitlement'); - } - - /** - * Get total KJC asset holdings - */ - public function getTotalHoldings(array $filters = [], $user = null): int - { - $query = KJCAssetEntitlement::query(); - - // Apply visibility scoping if user is provided - if ($user) { - $query->visibleTo($user); - } - - $this->applyFilters($query, $filters, excludeDateFilter: true); - - return $query->sum('holding'); - } - - /** - * Get KJC asset holdings by status. - * Does not filter by date so totals match full KJCAssetHolding counts (unit/government/formation filters still apply). - */ - public function getHoldingsByStatus(array $filters = [], $user = null): array - { - $query = KJCAssetHolding::query(); - - // Apply visibility scoping if user is provided - if ($user) { - $query->visibleTo($user); - } - - $this->applyFilters($query, $filters, excludeDateFilter: true); - - return $query->selectRaw('status, COUNT(*) as count') - ->groupBy('status') - ->get() - ->toArray(); - } - - /** - * Get KJC asset holdings with pagination - */ - public function getHoldingsPaginated(array $filters = [], int $perPage = 20, $user = null): array - { - $query = KJCAssetHolding::with(['unit', 'kjcCategory', 'kjcSubcategory', 'kjcModel']); - - // Apply visibility scoping if user is provided - if ($user) { - $query->visibleTo($user); - } - - $this->applyFilters($query, $filters); - - $holdings = $query->paginate($perPage); - - return [ - 'items' => $holdings->items(), - 'pagination' => [ - 'current_page' => $holdings->currentPage(), - 'per_page' => $holdings->perPage(), - 'total' => $holdings->total(), - 'last_page' => $holdings->lastPage(), - 'from' => $holdings->firstItem(), - 'to' => $holdings->lastItem() - ] - ]; - } - - /** - * Get KJC asset entitlements with pagination - */ - public function getEntitlementsPaginated(array $filters = [], int $perPage = 20, $user = null): array - { - $query = KJCAssetEntitlement::with(['kjcSubcategory']); - - // Apply visibility scoping if user is provided - if ($user) { - $query->visibleTo($user); - } - - $this->applyFilters($query, $filters); - - $entitlements = $query->paginate($perPage); - - return [ - 'items' => $entitlements->items(), - 'pagination' => [ - 'current_page' => $entitlements->currentPage(), - 'per_page' => $entitlements->perPage(), - 'total' => $entitlements->total(), - 'last_page' => $entitlements->lastPage(), - 'from' => $entitlements->firstItem(), - 'to' => $entitlements->lastItem() - ] - ]; - } - - /** - * Get KJC asset holdings by specific status - */ - public function getHoldingsBySpecificStatus(string $status, array $filters = [], $user = null): array - { - $query = KJCAssetHolding::with(['unit', 'kjcCategory', 'kjcSubcategory', 'kjcModel']) - ->where('status', $status); - - // Apply visibility scoping if user is provided - if ($user) { - $query->visibleTo($user); - } - - $this->applyFilters($query, $filters); - - $holdings = $query->paginate(20); - - return [ - 'items' => $holdings->items(), - 'pagination' => [ - 'current_page' => $holdings->currentPage(), - 'per_page' => $holdings->perPage(), - 'total' => $holdings->total(), - 'last_page' => $holdings->lastPage(), - 'from' => $holdings->firstItem(), - 'to' => $holdings->lastItem() - ] - ]; - } - - /** - * Get KJC asset details by status (paginated). - * When status is '' or 'null', returns assets where status column is NULL (not yet assigned). - */ - public function getAssetDetailsByStatus(string $status, array $filters = [], $user = null): array - { - $status = trim($status ?? ''); - - $query = KJCAssetHolding::query() - ->with(['kjcCategory', 'kjcSubcategory', 'kjcModel', 'unit']) - ->select([ - 'id', - 'uuid', - 'kjc_category_id', - 'kjc_subcategory_id', - 'kjc_model_id', - 'registration_number', - 'kewpa_registration_number', - 'status', - 'unit_id', - 'purchase_date', - 'receipt_date', - 'purchase_price', - 'contract_reference', - 'economic_year', - 'economic_distance', - 'note', - 'engine_number', - 'chassis_number', - 'created_at', - 'updated_at' - ]); - - if ($status === '' || $status === 'null') { - $query->whereNull('status'); - } else { - $query->whereNotNull('status') - ->where('status', '=', $status); - } - - if ($user) { - $query->visibleTo($user); - } - - $this->applyFilters($query, $filters, excludeDateFilter: true); - - $perPage = isset($filters['per_page']) ? (int) $filters['per_page'] : 10; - $page = isset($filters['page']) ? (int) $filters['page'] : null; - $assets = $query->paginate($perPage, ['*'], 'page', $page); - - return [ - 'data' => $assets->items(), - 'pagination' => [ - 'current_page' => $assets->currentPage(), - 'per_page' => $assets->perPage(), - 'total' => $assets->total(), - 'last_page' => $assets->lastPage(), - 'from' => $assets->firstItem(), - 'to' => $assets->lastItem(), - 'has_more_pages' => $assets->hasMorePages() - ] - ]; - } - - /** - * Get KJC asset holdings grouped by unit - */ - public function getHoldingsByUnit(array $filters = [], $user = null): array - { - $query = KJCAssetHolding::query(); - - // Apply visibility scoping if user is provided - if ($user) { - $query->visibleTo($user); - } - - $this->applyFilters($query, $filters); - - return $query->join('units', 'kjc_asset_holdings.unit_id', '=', 'units.id') - ->selectRaw('units.name as unit_name, COUNT(*) as count') - ->groupBy('units.name') - ->get() - ->toArray(); - } - - /** - * Get KJC asset holdings monthly trends - */ - public function getMonthlyTrends(array $filters = [], $user = null): array - { - $dateRange = $this->getDateRange($filters); - - $query = KJCAssetHolding::query(); - - // Apply visibility scoping if user is provided - if ($user) { - $query->visibleTo($user); - } - - $this->applyFilters($query, $filters); - - return $query->selectRaw(' - DATE_FORMAT(created_at, "%Y-%m") as month, - status, - COUNT(*) as count - ') - ->whereBetween('created_at', [$dateRange['from'], $dateRange['to']]) - ->groupBy('month', 'status') - ->orderBy('month') - ->get() - ->toArray(); - } - - /** - * Get KJC historical metrics (KEUPAYAAN, KESIAGAAN, SERVISIBILITI) by month - * 1. KEUPAYAAN (%) = (PEGANGAN/PERJAWATAN) * 100 - * 2. SIAPSIAGA or KESIAGAAN (%) = (BDG/perjawatan) * 100 - * 3. SERVISIBILITI (%) = (BDG/pegangan) * 100 - */ - public function getHistoricalMetricsByMonth(array $filters = [], $user = null): array - { - $dateRange = $this->getDateRange($filters); - $currentMonth = Carbon::now()->format('Y-m'); - - // Get entitlement and holding data by month from historical tables using Eloquent - $entitlementQuery = KJCHistoricalEntitlement::query() - ->selectRaw(' - TO_CHAR(kjc_historical_entitlement.date, \'YYYY-MM\') as month, - SUM(kjc_historical_entitlement.entitlement) as total_entitlement, - SUM(kjc_historical_entitlement.holding) as total_holding - ') - ->whereBetween('kjc_historical_entitlement.date', [$dateRange['from'], $dateRange['to']]); - - // Apply filters to historical entitlement query - $this->applyHistoricalFiltersToEloquent($entitlementQuery, $filters); - - // Apply visibility scoping if user is provided (uses HasVisibility trait) - if ($user) { - $entitlementQuery->visibleTo($user); - } - - $entitlementData = $entitlementQuery->groupBy('month') - ->orderBy('month') - ->get() - ->keyBy('month'); - - // Get BDG status count by month from historical tables using Eloquent - $bdgQuery = KJCHistoricalHoldingStatus::query() - ->selectRaw(' - TO_CHAR(kjc_historical_holding_status.date, \'YYYY-MM\') as month, - COUNT(*) as bdg_count - ') - ->where('kjc_historical_holding_status.status', 'BDG') - ->whereBetween('kjc_historical_holding_status.date', [$dateRange['from'], $dateRange['to']]); - - // Apply filters to historical holding status query - $this->applyHistoricalFiltersToEloquent($bdgQuery, $filters); - - // Apply visibility scoping if user is provided (uses HasVisibility trait) - if ($user) { - $bdgQuery->visibleTo($user); - } - - $bdgData = $bdgQuery->groupBy('month') - ->orderBy('month') - ->get() - ->keyBy('month'); - - // Get current month data from real-time tables if current month is in range - $currentMonthEntitlement = 0; - $currentMonthHolding = 0; - $currentMonthBdgCount = 0; - - if ($this->isCurrentMonthInRange($currentMonth, $dateRange)) { - // Get current month entitlement data (all entitlements, not filtered by updated_at) - // This ensures we get real-time entitlement values regardless of when they were last updated - $currentEntitlementQuery = KJCAssetEntitlement::query(); - if ($user) { - $currentEntitlementQuery->visibleTo($user); - } - $this->applyCurrentMonthFilters($currentEntitlementQuery, $filters); - - $currentMonthEntitlement = $currentEntitlementQuery->sum('entitlement'); - - // Calculate current month holdings from actual KJCAssetHolding records - // This ensures we get real-time holding counts even if entitlement.holding is stale - $currentHoldingQuery = KJCAssetHolding::query(); - if ($user) { - $currentHoldingQuery->visibleTo($user); - } - $this->applyCurrentMonthFilters($currentHoldingQuery, $filters); - - $currentMonthHolding = $currentHoldingQuery->count(); - - // Get current month BDG count (only holdings updated in current month) - // This shows BDG holdings that were updated/changed status in the current month - $currentBdgQuery = KJCAssetHolding::query() - ->where('status', 'BDG') - ->whereMonth('updated_at', Carbon::now()->month) - ->whereYear('updated_at', Carbon::now()->year); - if ($user) { - $currentBdgQuery->visibleTo($user); - } - $this->applyCurrentMonthFilters($currentBdgQuery, $filters); - - $currentMonthBdgCount = $currentBdgQuery->count(); - } - - // Generate all months in the range - $months = []; - $current = $dateRange['from']->copy()->startOfMonth(); - $end = $dateRange['to']->copy()->endOfMonth(); - - while ($current->lte($end)) { - $monthKey = $current->format('Y-m'); - $months[] = $monthKey; - $current->addMonth(); - } - - // Calculate metrics for each month - $result = []; - foreach ($months as $month) { - $entitlement = $entitlementData->get($month); - $bdg = $bdgData->get($month); - - // Use current month real-time data if this is the current month - if ($month === $currentMonth && $this->isCurrentMonthInRange($currentMonth, $dateRange)) { - $totalEntitlement = $currentMonthEntitlement; - $totalHolding = $currentMonthHolding; - $bdgCount = $currentMonthBdgCount; - } else { - $totalEntitlement = $entitlement ? $entitlement->total_entitlement : 0; - $totalHolding = $entitlement ? $entitlement->total_holding : 0; - $bdgCount = $bdg ? $bdg->bdg_count : 0; - } - - // Calculate metrics - $keupayaan = $totalEntitlement > 0 ? ($totalHolding / $totalEntitlement) * 100 : 0; - $kesiagaan = $totalEntitlement > 0 ? ($bdgCount / $totalEntitlement) * 100 : 0; - $servisibiliti = $totalHolding > 0 ? ($bdgCount / $totalHolding) * 100 : 0; - - $result[] = [ - 'month' => $month, - 'keupayaan' => round($keupayaan, 2), - 'kesiagaan' => round($kesiagaan, 2), - 'servisibiliti' => round($servisibiliti, 2), - 'total_entitlement' => $totalEntitlement, - 'total_holding' => $totalHolding, - 'bdg_count' => $bdgCount - ]; - } - - return $result; - } - - /** - * Apply common filters to query - */ - private function applyFilters($query, array $filters = [], bool $excludeDateFilter = false): void - { - $model = $query->getModel(); - if ($model && method_exists($model, 'getTable')) { - $schema = $model->getConnection()->getSchemaBuilder(); - $columns = $schema->getColumnListing($model->getTable()); - - // Only apply created_at filter if the model has created_at column (skip when excludeDateFilter) - if (!$excludeDateFilter && in_array('created_at', $columns)) { - $dateRange = $this->getDateRange($filters); - $query->whereBetween('created_at', [$dateRange['from'], $dateRange['to']]); - } - - // Apply unit_id filter if model has unit_id column - if (isset($filters['unit_id']) && $filters['unit_id']) { - if (in_array('unit_id', $columns)) { - $query->where('unit_id', $filters['unit_id']); - } - } - - // Apply government_id filter if provided - if (isset($filters['government_id']) && $filters['government_id']) { - $unitIds = $this->getUnitsByGovernment($filters['government_id']); - if (in_array('unit_id', $columns)) { - if (!empty($unitIds)) { - $query->whereIn('unit_id', $unitIds); - } else { - // No units found for this government - return empty result - $query->whereNull('id'); - } - } - } - - // Apply camp_id filter - if (isset($filters['camp_id']) && $filters['camp_id']) { - $query->whereHas('unit', function ($q) use ($filters) { - $q->where('camp_id', $filters['camp_id']); - }); - } - - // Apply formation_id filter - if (isset($filters['formation_id']) && $filters['formation_id']) { - // Get units under this formation - $unitIds = Unit::where('formation_id', $filters['formation_id']) - ->pluck('id') - ->toArray(); - - if (!empty($unitIds)) { - if (in_array('unit_id', $columns)) { - $query->whereIn('unit_id', $unitIds); - } else { - // Fallback to whereHas if no unit_id column - $query->whereHas('unit', function ($q) use ($filters) { - $q->where('formation_id', $filters['formation_id']); - }); - } - } else { - // No units found for this formation - return empty result - $query->whereNull('id'); - } - } - } - } - - /** - * Get units by government ID - * Uses VisibilityService for consistent logic - */ - private function getUnitsByGovernment(int $governmentId): array - { - $governmentUnit = Unit::where('government_id', $governmentId)->first(); - if (!$governmentUnit) { - // If no direct unit found, try to get from formation - $formation = Formation::where('government_id', $governmentId)->first(); - if ($formation) { - $governmentUnit = Unit::where('formation_id', $formation->id)->first(); - } - } - - if (!$governmentUnit) { - return []; - } - - return $this->visibilityService->getUnitsUnderGovernment($governmentUnit); - } - - /** - * Get date range for filtering - */ - private function getDateRange(array $filters = []): array - { - $dateFrom = $filters['date_from'] ?? Carbon::now()->startOfYear(); - $dateTo = $filters['date_to'] ?? Carbon::now()->endOfYear(); - - return [ - 'from' => Carbon::parse($dateFrom)->startOfDay(), - 'to' => Carbon::parse($dateTo)->endOfDay() - ]; - } - - /** - * Apply filters to historical Eloquent queries - * Handles government_id, formation_id, unit_id, and camp_id filters - */ - private function applyHistoricalFiltersToEloquent($query, array $filters = []): void - { - $model = $query->getModel(); - $isHoldingStatus = $model instanceof KJCHistoricalHoldingStatus; - - // Apply unit_id filter - if (isset($filters['unit_id']) && $filters['unit_id']) { - if ($isHoldingStatus) { - // For holding status, filter through the relationship - $query->whereHas('kjcAssetHolding', function ($q) use ($filters) { - $q->where('unit_id', $filters['unit_id']); - }); - } else { - $query->where('unit_id', $filters['unit_id']); - } - } - - // Apply government_id filter - if (isset($filters['government_id']) && $filters['government_id']) { - $unitIds = $this->getUnitsByGovernment($filters['government_id']); - if (!empty($unitIds)) { - if ($isHoldingStatus) { - $query->whereHas('kjcAssetHolding', function ($q) use ($unitIds) { - $q->whereIn('unit_id', $unitIds); - }); - } else { - $query->whereIn('unit_id', $unitIds); - } - } else { - // No units found for this government - return empty result - $query->whereRaw('1 = 0'); - } - } - - // Apply formation_id filter - if (isset($filters['formation_id']) && $filters['formation_id']) { - // Get units under this formation - $unitIds = Unit::where('formation_id', $filters['formation_id']) - ->pluck('id') - ->toArray(); - - if (!empty($unitIds)) { - if ($isHoldingStatus) { - $query->whereHas('kjcAssetHolding', function ($q) use ($unitIds) { - $q->whereIn('unit_id', $unitIds); - }); - } else { - $query->whereIn('unit_id', $unitIds); - } - } else { - // No units found for this formation - return empty result - $query->whereRaw('1 = 0'); - } - } - - // Apply camp_id filter - if (isset($filters['camp_id']) && $filters['camp_id']) { - if ($isHoldingStatus) { - $query->whereHas('kjcAssetHolding.unit', function ($q) use ($filters) { - $q->where('camp_id', $filters['camp_id']); - }); - } else { - $query->whereHas('unit', function ($q) use ($filters) { - $q->where('camp_id', $filters['camp_id']); - }); - } - } - } - - /** - * Check if current month is within the date range - */ - private function isCurrentMonthInRange(string $currentMonth, array $dateRange): bool - { - $currentMonthDate = Carbon::createFromFormat('Y-m', $currentMonth)->startOfMonth(); - return $currentMonthDate->between($dateRange['from'], $dateRange['to']); - } - - /** - * Apply filters for current month data queries (without date range filtering) - */ - private function applyCurrentMonthFilters($query, array $filters = []): void - { - if (isset($filters['unit_id']) && $filters['unit_id']) { - $query->where('unit_id', $filters['unit_id']); - } - - // Apply government_id filter - if (isset($filters['government_id']) && $filters['government_id']) { - $unitIds = $this->getUnitsByGovernment($filters['government_id']); - if (!empty($unitIds)) { - $query->whereIn('unit_id', $unitIds); - } else { - // No units found for this government - return empty result - $query->whereNull('id'); - } - } - - if (isset($filters['camp_id']) && $filters['camp_id']) { - $query->whereHas('unit', function ($q) use ($filters) { - $q->where('camp_id', $filters['camp_id']); - }); - } - - if (isset($filters['formation_id']) && $filters['formation_id']) { - // Get units under this formation - $unitIds = Unit::where('formation_id', $filters['formation_id']) - ->pluck('id') - ->toArray(); - - if (!empty($unitIds)) { - $query->whereIn('unit_id', $unitIds); - } else { - // No units found for this formation - return empty result - $query->whereNull('id'); - } - } - } - -} diff --git a/Modules/Dashboard/Repositories/PKJDashboardRepository.php b/Modules/Dashboard/Repositories/PKJDashboardRepository.php deleted file mode 100644 index 99fa14a..0000000 --- a/Modules/Dashboard/Repositories/PKJDashboardRepository.php +++ /dev/null @@ -1,495 +0,0 @@ -visibleTo($user); - } - - $this->applyFilters($query, $filters, excludeDateFilter: true); - - return $query->sum('entitlement'); - } - - public function getTotalHoldings(array $filters = [], $user = null): int - { - $query = PKJAssetEntitlement::query(); - - // Apply visibility scoping if user is provided - if ($user) { - $query->visibleTo($user); - } - - $this->applyFilters($query, $filters, excludeDateFilter: true); - - return $query->sum('holding') ?? 0; - } - - /** - * Get PKJ asset holdings grouped by status - */ - public function getHoldingsByStatus(array $filters = [], $user = null): array - { - $query = PKJAssetHolding::query(); - - // Apply visibility scoping if user is provided - if ($user) { - $query->visibleTo($user); - } - - $this->applyFilters($query, $filters); - - return $query->selectRaw('status, COUNT(*) as count') - ->groupBy('status') - ->orderBy('status') - ->get() - ->toArray(); - } - - /** - * Get PKJ asset holdings by specific status - */ - public function getHoldingsBySpecificStatus(string $status, array $filters = [], $user = null): array - { - $query = PKJAssetHolding::query(); - - // Apply visibility scoping if user is provided - if ($user) { - $query->visibleTo($user); - } - - $this->applyFilters($query, $filters); - - $holdings = $query->where('status', $status)->paginate(10); - - return [ - 'data' => $holdings->items(), - 'pagination' => [ - 'current_page' => $holdings->currentPage(), - 'per_page' => $holdings->perPage(), - 'total' => $holdings->total(), - 'last_page' => $holdings->lastPage(), - 'from' => $holdings->firstItem(), - 'to' => $holdings->lastItem() - ] - ]; - } - - /** - * Get PKJ monthly trends - */ - public function getMonthlyTrends(array $filters = [], $user = null): array - { - $dateRange = $this->getDateRange($filters); - - $query = PKJReport::query(); - - // Apply visibility scoping if user is provided - if ($user) { - $query->visibleTo($user); - } - - $trends = $query->selectRaw(' - TO_CHAR(created_at, \'YYYY-MM\') as month, - COUNT(*) as total_reports - ') - ->whereBetween('created_at', [$dateRange['from'], $dateRange['to']]) - ->groupBy('month') - ->orderBy('month') - ->get() - ->toArray(); - - return $trends; - } - - /** - * Get PKJ holdings by category - */ - public function getHoldingsByCategory(array $filters = [], $user = null): array - { - // Build a subquery for filtered entitlements - // This ensures all categories are included, but only matching entitlements are summed - $entitlementSubquery = PKJAssetEntitlement::query() - ->select('pkj_category_id', DB::raw('SUM(holding) as total_holding')) - ->groupBy('pkj_category_id'); - - // Apply visibility scoping if user is provided - if ($user) { - $entitlementSubquery->visibleTo($user); - } - - // Apply filters to entitlement subquery - if (isset($filters['unit_id'])) { - $entitlementSubquery->where('unit_id', $filters['unit_id']); - } - - // Apply government_id filter - if (isset($filters['government_id']) && $filters['government_id']) { - $unitIds = $this->getUnitsByGovernment($filters['government_id']); - if (!empty($unitIds)) { - $entitlementSubquery->whereIn('unit_id', $unitIds); - } else { - // If no units match, set to empty result but don't filter categories - $entitlementSubquery->whereRaw('1 = 0'); - } - } - - // Apply formation_id filter - if (isset($filters['formation_id']) && $filters['formation_id']) { - $unitIds = Unit::where('formation_id', $filters['formation_id']) - ->pluck('id') - ->toArray(); - - if (!empty($unitIds)) { - $entitlementSubquery->whereIn('unit_id', $unitIds); - } else { - // If no units match, set to empty result but don't filter categories - $entitlementSubquery->whereRaw('1 = 0'); - } - } - - // Main query: Get ALL categories and LEFT JOIN with filtered entitlements - // This ensures all categories are returned even if they have no matching entitlements - $query = PKJCategory::query() - ->leftJoinSub($entitlementSubquery, 'filtered_entitlements', function ($join) { - $join->on('pkj_categories.id', '=', 'filtered_entitlements.pkj_category_id'); - }) - ->selectRaw(' - pkj_categories.id as category_id, - pkj_categories.name as category_name, - COALESCE(filtered_entitlements.total_holding, 0) as total_holding - ') - ->orderBy('pkj_categories.name'); - - return $query->get()->toArray(); - } - - /** - * Get PKJ category metrics (KEUPAYAAN, KESIAGAAN, SERVISIBILITI) - */ - public function getCategoryMetrics(int $categoryId, array $filters = [], $user = null): array - { - // Get category name - $category = PKJCategory::find($categoryId); - $categoryName = $category ? $category->name : 'Unknown Category'; - - // Get entitlement and holding data for the category - $entitlementQuery = PKJAssetEntitlement::query() - ->where('pkj_category_id', $categoryId); - - // Apply visibility scoping if user is provided - if ($user) { - $entitlementQuery->visibleTo($user); - } - - // Apply filters - if (isset($filters['unit_id'])) { - $entitlementQuery->where('unit_id', $filters['unit_id']); - } - - $entitlementData = $entitlementQuery->selectRaw(' - SUM(entitlement) as total_entitlement, - SUM(holding) as total_holding - ')->first(); - - // Get operational/usable assets count (BDG, BT, BP statuses) for the category - $operationalQuery = PKJAssetHolding::query() - ->where('pkj_category_id', $categoryId) - ->whereIn('status', ['BDG', 'BT', 'BP']); - - // Apply visibility scoping if user is provided - if ($user) { - $operationalQuery->visibleTo($user); - } - - // Apply filters - if (isset($filters['unit_id'])) { - $operationalQuery->where('unit_id', $filters['unit_id']); - } - - $operationalCount = $operationalQuery->count(); - - // Get BDG, BT, BG count specifically for servisibiliti calculation - $bdgQuery = PKJAssetHolding::query() - ->where('pkj_category_id', $categoryId) - ->whereIn('status', ['BDG', 'BT', 'BG']); - - // Apply visibility scoping if user is provided - if ($user) { - $bdgQuery->visibleTo($user); - } - - // Apply filters - if (isset($filters['unit_id'])) { - $bdgQuery->where('unit_id', $filters['unit_id']); - } - - $bdgCount = $bdgQuery->count(); - - $totalEntitlement = $entitlementData ? $entitlementData->total_entitlement : 0; - $totalHolding = $entitlementData ? $entitlementData->total_holding : 0; - - // Calculate metrics - $keupayaan = $totalEntitlement > 0 ? ($totalHolding / $totalEntitlement) * 100 : 0; - $kesiagaan = $totalEntitlement > 0 ? ($operationalCount / $totalEntitlement) * 100 : 0; - $servisibiliti = $totalHolding > 0 ? ($bdgCount / $totalHolding) * 100 : 0; - - return [ - 'category_id' => $categoryId, - 'category_name' => $categoryName, - 'keupayaan' => round($keupayaan, 2), - 'kesiagaan' => round($kesiagaan, 2), - 'servisibiliti' => round($servisibiliti, 2), - 'total_entitlement' => $totalEntitlement, - 'total_holding' => $totalHolding, - 'operational_count' => $operationalCount, - 'bdg_count' => $bdgCount - ]; - } - - /** - * Get PKJ category status breakdown - */ - public function getCategoryStatusBreakdown(int $categoryId, array $filters = [], $user = null): array - { - // Get category name - $category = PKJCategory::find($categoryId); - $categoryName = $category ? $category->name : 'Unknown Category'; - - $query = PKJAssetHolding::query() - ->where('pkj_category_id', $categoryId) - ->selectRaw('status, COUNT(*) as count') - ->groupBy('status') - ->orderBy('status'); - - // Apply visibility scoping if user is provided - if ($user) { - $query->visibleTo($user); - } - - // Apply filters - if (isset($filters['unit_id'])) { - $query->where('unit_id', $filters['unit_id']); - } - - $statusCounts = $query->get()->toArray(); - - return [ - 'category_id' => $categoryId, - 'category_name' => $categoryName, - 'status_breakdown' => $statusCounts - ]; - } - - /** - * Get PKJ asset details by category and status. - * - * When the asset has no status yet, the status column in the database is NULL. - * Callers pass status as '' or the string 'null' to request these "no status" assets. - */ - public function getAssetDetailsByCategoryAndStatus(int $categoryId, string $status, array $filters = [], $user = null): array - { - // Handle no-status: API passes '' or 'null'; DB column is NULL for assets not yet assigned a status - $status = trim($status ?? ''); - - $query = PKJAssetHolding::query() - ->with(['unit', 'pkjCategory', 'pkjSubcategory', 'pkjModel']) - ->where('pkj_category_id', $categoryId); - - if ($status === '' || $status === 'null') { - // Assets with status column NULL (not yet assigned a status) - $query->whereNull('status'); - } else { - // For specific status, must match exactly and NOT be null - $query->whereNotNull('status') - ->where('status', '=', $status); - } - - $query->select([ - 'id', - 'uuid', - 'registration_number', - 'kewpa_registration_number', - 'status', - 'unit_id', - 'pkj_category_id', - 'pkj_subcategory_id', - 'pkj_model_id', - 'purchase_date', - 'receipt_date', - 'purchase_price', - 'contract_reference', - 'country_of_manufacture', - 'economic_year', - 'economic_distance', - 'note', - 'engine_number', - 'chassis_number', - 'created_at', - 'updated_at' - ]); - - // Apply visibility scoping if user is provided - if ($user) { - $query->visibleTo($user); - } - - // Apply filters - if (isset($filters['unit_id'])) { - $query->where('unit_id', $filters['unit_id']); - } - - $perPage = isset($filters['per_page']) ? (int) $filters['per_page'] : 10; - $page = isset($filters['page']) ? (int) $filters['page'] : null; - $assets = $query->paginate($perPage, ['*'], 'page', $page); - - return [ - 'data' => $assets->items(), - 'pagination' => [ - 'current_page' => $assets->currentPage(), - 'per_page' => $assets->perPage(), - 'total' => $assets->total(), - 'last_page' => $assets->lastPage(), - 'from' => $assets->firstItem(), - 'to' => $assets->lastItem(), - 'has_more_pages' => $assets->hasMorePages() - ] - ]; - } - - /** - * Apply common filters to query - */ - private function applyFilters($query, array $filters = [], bool $excludeDateFilter = false): void - { - // Only apply created_at filter if the model has created_at column - $model = $query->getModel(); - if ($model && method_exists($model, 'getTable')) { - $schema = $model->getConnection()->getSchemaBuilder(); - $columns = $schema->getColumnListing($model->getTable()); - - if (!$excludeDateFilter && in_array('created_at', $columns)) { - $dateRange = $this->getDateRange($filters); - $query->whereBetween('created_at', [$dateRange['from'], $dateRange['to']]); - } - } - - // Apply unit_id filter if model has unit_id column - if (isset($filters['unit_id']) && $filters['unit_id']) { - if ($model && method_exists($model, 'getTable')) { - $columns = $schema->getColumnListing($model->getTable()); - if (in_array('unit_id', $columns)) { - $query->where('unit_id', $filters['unit_id']); - } - } - } - - // Apply government_id filter if provided - if (isset($filters['government_id']) && $filters['government_id']) { - // Get units under the government - $unitIds = $this->getUnitsByGovernment($filters['government_id']); - - if ($model && method_exists($model, 'getTable')) { - $columns = $schema->getColumnListing($model->getTable()); - if (in_array('unit_id', $columns)) { - if (!empty($unitIds)) { - $query->whereIn('unit_id', $unitIds); - } else { - // No units found for this government - return empty result - $query->whereNull('id'); - } - } - } - } - - // Apply formation_id filter if provided - if (isset($filters['formation_id']) && $filters['formation_id']) { - if ($model && method_exists($model, 'getTable')) { - $columns = $schema->getColumnListing($model->getTable()); - if (in_array('unit_id', $columns)) { - // Get units under this formation - $unitIds = Unit::where('formation_id', $filters['formation_id']) - ->pluck('id') - ->toArray(); - - if (!empty($unitIds)) { - $query->whereIn('unit_id', $unitIds); - } else { - // No units found for this formation - return empty result - $query->whereNull('id'); - } - } - } - } - - // Apply camp_id filter if provided - if (isset($filters['camp_id']) && $filters['camp_id']) { - if ($model && method_exists($model, 'getTable')) { - $columns = $schema->getColumnListing($model->getTable()); - if (in_array('camp_id', $columns)) { - $query->where('camp_id', $filters['camp_id']); - } - } - } - } - - /** - * Get units by government ID - * Uses VisibilityService for consistent logic - */ - private function getUnitsByGovernment(int $governmentId): array - { - $governmentUnit = Unit::where('government_id', $governmentId)->first(); - if (!$governmentUnit) { - // If no direct unit found, try to get from formation - $formation = \Modules\Formation\Entities\Formation::where('government_id', $governmentId)->first(); - if ($formation) { - $governmentUnit = Unit::where('formation_id', $formation->id)->first(); - } - } - - if (!$governmentUnit) { - return []; - } - - return $this->visibilityService->getUnitsUnderGovernment($governmentUnit); - } - - /** - * Get date range for filtering - */ - private function getDateRange(array $filters = []): array - { - $dateFrom = $filters['date_from'] ?? Carbon::now()->startOfYear(); - $dateTo = $filters['date_to'] ?? Carbon::now()->endOfYear(); - - return [ - 'from' => Carbon::parse($dateFrom)->startOfDay(), - 'to' => Carbon::parse($dateTo)->endOfDay() - ]; - } -} diff --git a/Modules/Dashboard/Routes/.gitkeep b/Modules/Dashboard/Routes/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/Modules/Dashboard/Routes/api.php b/Modules/Dashboard/Routes/api.php deleted file mode 100644 index 5fedfb7..0000000 --- a/Modules/Dashboard/Routes/api.php +++ /dev/null @@ -1,44 +0,0 @@ -prefix('v1')->group(function () { - // Main dashboard endpoints - Route::get('/dashboard/stat-cards', [DashboardController::class, 'getStatCards']); - Route::get('/dashboard/overview', [DashboardController::class, 'getOverview']); - - // KJC specific endpoints - Route::prefix('dashboard/kjc')->group(function () { - // Pie charts - Route::get('/entitlement-vs-holdings-pie', [KJCDashboardController::class, 'getEntitlementVsHoldingsPieChart']); - Route::get('/holdings-status-breakdown-pie', [KJCDashboardController::class, 'getHoldingsStatusBreakdownPieChart']); - - // Bar charts - Route::get('/historical-metrics-bar', [KJCDashboardController::class, 'getHistoricalMetricsBarChart']); - - // Other charts - Route::get('/unit-performance-chart', [KJCDashboardController::class, 'getUnitPerformanceChart']); - Route::get('/monthly-trends-chart', [KJCDashboardController::class, 'getMonthlyTrendsChart']); - - // Drill-down endpoints - Route::get('/drill-down/entitlement-vs-holdings', [KJCDashboardController::class, 'getEntitlementVsHoldingsDetails']); - Route::get('/drill-down/holdings-status', [KJCDashboardController::class, 'getHoldingsStatusDetails']); - Route::get('/status/{status}/details', [KJCDashboardController::class, 'getAssetDetailsByStatus']); - }); - - // PKJ specific endpoints - Route::prefix('dashboard/pkj')->group(function () { - Route::get('/entitlement-vs-holdings-pie', [PKJDashboardController::class, 'getEntitlementVsHoldingsPieChart']); - Route::get('/holdings-status-breakdown-pie', [PKJDashboardController::class, 'getHoldingsStatusBreakdownPieChart']); - Route::get('/monthly-trends', [PKJDashboardController::class, 'getMonthlyTrendsChart']); - Route::get('/holdings-by-category-bar', [PKJDashboardController::class, 'getHoldingsByCategoryBarChart']); - - // Drill-down endpoints - Route::get('/category/{categoryId}/metrics-bar', [PKJDashboardController::class, 'getCategoryMetricsBarChart']); - Route::get('/category/{categoryId}/status-breakdown-pie', [PKJDashboardController::class, 'getCategoryStatusBreakdownPieChart']); - Route::get('/category/{categoryId}/status/{status}/details', [PKJDashboardController::class, 'getAssetDetailsByCategoryAndStatus']); - }); -}); \ No newline at end of file diff --git a/Modules/Dashboard/Services/.gitkeep b/Modules/Dashboard/Services/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/Modules/Dashboard/Services/DashboardService.php b/Modules/Dashboard/Services/DashboardService.php deleted file mode 100644 index 73ab4f8..0000000 --- a/Modules/Dashboard/Services/DashboardService.php +++ /dev/null @@ -1,486 +0,0 @@ -startOfYear(); - $dateTo = $filters['date_to'] ?? Carbon::now()->endOfYear(); - - return [ - 'from' => Carbon::parse($dateFrom)->startOfDay(), - 'to' => Carbon::parse($dateTo)->endOfDay() - ]; - } - - /** - * Apply common filters to query - */ - protected function applyCommonFilters($query, array $filters = []): void - { - $dateRange = $this->getDateRange($filters); - - $query->whereBetween('created_at', [$dateRange['from'], $dateRange['to']]); - - // Government filter: Filter by government (get all units under that government) - if (isset($filters['government_id']) && $filters['government_id']) { - $unitIds = $this->getUnitsByGovernment($filters['government_id']); - - if (!empty($unitIds)) { - $query->whereIn('unit_id', $unitIds); - } else { - // No units found for this government - $query->whereNull('id'); - } - } - - if (isset($filters['unit_id']) && $filters['unit_id']) { - $query->where('unit_id', $filters['unit_id']); - } - - if (isset($filters['camp_id']) && $filters['camp_id']) { - $query->whereHas('unit', function ($q) use ($filters) { - $q->where('camp_id', $filters['camp_id']); - }); - } - - if (isset($filters['formation_id']) && $filters['formation_id']) { - $query->whereHas('unit', function ($q) use ($filters) { - $q->where('formation_id', $filters['formation_id']); - }); - } - } - - /** - * Apply visibility scoping to query based on user permissions - */ - protected function applyVisibilityScoping($query, $user): void - { - if (!$user) { - return; - } - - // Check if the model has the HasVisibility trait - if (method_exists($query->getModel(), 'scopeVisibleTo')) { - $query->visibleTo($user); - } - } - - /** - * Apply visibility scoping to raw database queries - * Uses VisibilityService for consistent logic - */ - protected function applyRawVisibilityScoping($query, $user, string $tableName): void - { - if (!$user) { - return; - } - - $userUnit = Unit::find($user->unit_id); - if (!$userUnit) { - $query->whereRaw('1 = 0'); - return; - } - - $unitIds = $this->visibilityService->getVisibleUnitIds($user); - - if (empty($unitIds)) { - $query->whereRaw('1 = 0'); - } else { - $query->whereIn('unit_id', $unitIds); - } - } - - /** - * Get all units by government ID - * Uses VisibilityService for consistent logic - */ - protected function getUnitsByGovernment(int $governmentId): array - { - $governmentUnit = Unit::where('government_id', $governmentId)->first(); - if (!$governmentUnit) { - // If no direct unit found, try to get from formation - $formation = Formation::where('government_id', $governmentId)->first(); - if ($formation) { - $governmentUnit = Unit::where('formation_id', $formation->id)->first(); - } - } - - if (!$governmentUnit) { - return []; - } - - return $this->visibilityService->getUnitsUnderGovernment($governmentUnit); - } - - /** - * Get total units count - */ - public function getTotalUnits(array $filters = []): int - { - $query = Unit::query(); - - if (isset($filters['camp_id']) && $filters['camp_id']) { - $query->where('camp_id', $filters['camp_id']); - } - - if (isset($filters['formation_id']) && $filters['formation_id']) { - $query->where('formation_id', $filters['formation_id']); - } - - return $query->count(); - } - - /** - * Get total camps count - */ - public function getTotalCamps(array $filters = []): int - { - return Camp::count(); - } - - /** - * Get total formations count - */ - public function getTotalFormations(array $filters = []): int - { - return Formation::count(); - } - - /** - * Get active repairs count (to be implemented by child classes) - */ - abstract public function getActiveRepairs(array $filters = []): int; - - /** - * Get pending reports count (to be implemented by child classes) - */ - abstract public function getPendingReports(array $filters = []): int; - - /** - * Get total assets count (to be implemented by child classes) - */ - abstract public function getTotalAssets(array $filters = []): int; - - /** - * Get operational assets count (to be implemented by child classes) - */ - abstract public function getOperationalAssets(array $filters = []): int; - - /** - * Get under repair assets count (to be implemented by child classes) - */ - abstract public function getUnderRepairAssets(array $filters = []): int; - - /** - * Get maintenance due assets count (to be implemented by child classes) - */ - abstract public function getMaintenanceDueAssets(array $filters = []): int; - - /** - * Format chart data with common structure - */ - protected function formatChartData( - string $chartType, - string $title, - array $series, - array $categories = [], - array $drillDown = [] - ): array { - return [ - 'chart_type' => $chartType, - 'title' => $title, - 'series' => $series, - 'categories' => $categories, - 'drill_down' => $drillDown - ]; - } - - /** - * Format pie chart data - */ - protected function formatPieChartData( - string $title, - array $data, - string $drillDownEndpoint = '', - array $drillDownParameters = [] - ): array { - $total = array_sum(array_column($data, 'value')); - $series = []; - - foreach ($data as $item) { - $percentage = $total > 0 ? round(($item['value'] / $total) * 100, 1) : 0; - - $seriesItem = [ - 'name' => $item['name'], - 'value' => $item['value'], - 'percentage' => $percentage - ]; - - // Preserve status_code if it exists (for drill-down functionality) - if (isset($item['status_code'])) { - $seriesItem['status_code'] = $item['status_code']; - } - - $series[] = $seriesItem; - } - - return $this->formatChartData( - 'pie', - $title, - $series, - [], - [ - 'enabled' => !empty($drillDownEndpoint), - 'endpoint' => $drillDownEndpoint, - 'parameters' => $drillDownParameters - ] - ); - } - - /** - * Format bar chart data - */ - protected function formatBarChartData( - string $title, - array $series, - array $categories, - string $drillDownEndpoint = '', - array $drillDownParameters = [] - ): array { - return $this->formatChartData( - 'bar', - $title, - $series, - $categories, - [ - 'enabled' => !empty($drillDownEndpoint), - 'endpoint' => $drillDownEndpoint, - 'parameters' => $drillDownParameters - ] - ); - } - - /** - * Format line chart data - */ - protected function formatLineChartData( - string $title, - array $series, - array $categories, - string $drillDownEndpoint = '', - array $drillDownParameters = [] - ): array { - return $this->formatChartData( - 'line', - $title, - $series, - $categories, - [ - 'enabled' => !empty($drillDownEndpoint), - 'endpoint' => $drillDownEndpoint, - 'parameters' => $drillDownParameters - ] - ); - } - - /** - * Get monthly categories for date range - */ - protected function getMonthlyCategories(array $filters = []): array - { - $dateRange = $this->getDateRange($filters); - $categories = []; - - $current = $dateRange['from']->copy()->startOfMonth(); - $end = $dateRange['to']->copy()->endOfMonth(); - - while ($current->lte($end)) { - $categories[] = $current->format('M Y'); - $current->addMonth(); - } - - return $categories; - } - - /** - * Get weekly categories for date range - */ - protected function getWeeklyCategories(array $filters = []): array - { - $dateRange = $this->getDateRange($filters); - $categories = []; - - $current = $dateRange['from']->copy()->startOfWeek(); - $end = $dateRange['to']->copy()->endOfWeek(); - - while ($current->lte($end)) { - $categories[] = 'Week ' . $current->weekOfYear . ' ' . $current->year; - $current->addWeek(); - } - - return $categories; - } - - /** - * Get daily categories for date range - */ - protected function getDailyCategories(array $filters = []): array - { - $dateRange = $this->getDateRange($filters); - $categories = []; - - $current = $dateRange['from']->copy(); - $end = $dateRange['to']->copy(); - - while ($current->lte($end)) { - $categories[] = $current->format('M d'); - $current->addDay(); - } - - return $categories; - } - - /** - * Calculate percentage change - */ - protected function calculatePercentageChange(int $current, int $previous): float - { - if ($previous === 0) { - return $current > 0 ? 100.0 : 0.0; - } - - return round((($current - $previous) / $previous) * 100, 1); - } - - /** - * Get status color mapping - */ - protected function getStatusColors(): array - { - return [ - 'operational' => '#4caf50', - 'under_repair' => '#ff9800', - 'maintenance_due' => '#f44336', - 'out_of_service' => '#9e9e9e', - 'completed' => '#4caf50', - 'pending' => '#ff9800', - 'in_progress' => '#2196f3', - 'cancelled' => '#f44336' - ]; - } - - /** - * Get priority color mapping - */ - protected function getPriorityColors(): array - { - return [ - 'high' => '#f44336', - 'medium' => '#ff9800', - 'low' => '#4caf50' - ]; - } - - /** - * Format currency - */ - protected function formatCurrency(float $amount): string - { - return 'RM ' . number_format($amount, 2); - } - - /** - * Format duration in hours - */ - protected function formatDuration(float $hours): string - { - if ($hours < 24) { - return round($hours, 1) . ' hours'; - } - - $days = floor($hours / 24); - $remainingHours = $hours % 24; - - if ($remainingHours > 0) { - return $days . ' days ' . round($remainingHours, 1) . ' hours'; - } - - return $days . ' days'; - } - - /** - * Get common drill-down parameters - */ - protected function getCommonDrillDownParameters(): array - { - return ['date_from', 'date_to', 'unit_id', 'camp_id', 'formation_id']; - } - - /** - * Build drill-down endpoint - */ - protected function buildDrillDownEndpoint(string $baseEndpoint, array $parameters = []): string - { - $endpoint = $baseEndpoint; - - if (!empty($parameters)) { - $endpoint .= '?' . http_build_query($parameters); - } - - return $endpoint; - } - - /** - * Get summary statistics for drill-down - */ - protected function getSummaryStatistics($query, array $filters = []): array - { - $total = $query->count(); - - // Get breakdown by unit - $byUnit = $query->clone() - ->join('units', function ($join) { - $join->on('units.id', '=', $this->getUnitIdColumn()); - }) - ->selectRaw('units.name as unit_name, COUNT(*) as count') - ->groupBy('units.name') - ->get() - ->toArray(); - - return [ - 'total' => $total, - 'by_unit' => $byUnit - ]; - } - - /** - * Get unit ID column name (to be implemented by child classes) - */ - abstract protected function getUnitIdColumn(): string; - - /** - * Get asset model class (to be implemented by child classes) - */ - abstract protected function getAssetModelClass(): string; - - /** - * Get repair model class (to be implemented by child classes) - */ - abstract protected function getRepairModelClass(): string; -} \ No newline at end of file diff --git a/Modules/Dashboard/Services/KJC/KJCDashboardService.php b/Modules/Dashboard/Services/KJC/KJCDashboardService.php deleted file mode 100644 index 2e276e9..0000000 --- a/Modules/Dashboard/Services/KJC/KJCDashboardService.php +++ /dev/null @@ -1,315 +0,0 @@ -kjcDashboardRepository = $kjcDashboardRepository; - } - /** - * Get total KJC assets count - */ - public function getTotalAssets(array $filters = [], $user = null): int - { - return $this->kjcDashboardRepository->getTotalHoldings($filters, $user); - } - - /** - * Get operational KJC assets count (BP status) - */ - public function getOperationalAssets(array $filters = [], $user = null): int - { - $holdings = $this->kjcDashboardRepository->getHoldingsBySpecificStatus('BP', $filters, $user); - return $holdings['pagination']['total']; - } - - /** - * Get under repair KJC assets count (BDG status) - */ - public function getUnderRepairAssets(array $filters = [], $user = null): int - { - $holdings = $this->kjcDashboardRepository->getHoldingsBySpecificStatus('BDG', $filters, $user); - return $holdings['pagination']['total']; - } - - /** - * Get maintenance due KJC assets count (TBDG status) - */ - public function getMaintenanceDueAssets(array $filters = [], $user = null): int - { - $holdings = $this->kjcDashboardRepository->getHoldingsBySpecificStatus('TBDG', $filters, $user); - return $holdings['pagination']['total']; - } - - /** - * Get active KJC repairs count - */ - public function getActiveRepairs(array $filters = []): int - { - $query = KJCRepair::whereIn('status', ['pending', 'in_progress']); - $this->applyCommonFilters($query, $filters); - - return $query->count(); - } - - /** - * Get pending KJC reports count - */ - public function getPendingReports(array $filters = []): int - { - $query = KJCReport::where('status', 'pending'); - $this->applyCommonFilters($query, $filters); - - return $query->count(); - } - - /** - * Get KJC entitlement vs holdings pie chart - */ - public function getEntitlementVsHoldingsPieChart(array $filters = [], $user = null): array - { - // Get total entitlement - $totalEntitlement = $this->kjcDashboardRepository->getTotalEntitlement($filters, $user); - - // Get total holdings - $totalHoldings = $this->kjcDashboardRepository->getTotalHoldings($filters, $user); - - // Prepare data for pie chart (only showing Perjawatan and Pegangan) - $data = [ - [ - 'name' => 'Perjawatan', - 'value' => $totalEntitlement, - 'color' => '#4caf50' - ], - [ - 'name' => 'Pegangan', - 'value' => $totalHoldings, - 'color' => '#2196f3' - ] - ]; - - return $this->formatPieChartData( - 'Perjawatan vs Pegangan KJC', - $data, - '/v1/dashboard/kjc/drill-down/entitlement-vs-holdings', - ['type'] - ); - } - - /** - * Get KJC holdings status breakdown pie chart - */ - public function getHoldingsStatusBreakdownPieChart(array $filters = [], $user = null): array - { - $statusCounts = $this->kjcDashboardRepository->getHoldingsByStatus($filters, $user); - - $statusNames = [ - 'BP' => 'BP', - 'BDG' => 'BDG', - 'TBDG' => 'TBDG', - 'BT' => 'BT', - 'TBT' => 'TBT', - 'BG' => 'BG', - 'TBG' => 'TBG', - ]; - - $data = []; - foreach ($statusCounts as $item) { - $statusCode = $item['status']; - if ($statusCode === null || $statusCode === '') { - $statusName = 'Belum Diberi Status'; - } else { - $statusName = $statusNames[$statusCode] ?? $statusCode; - } - $data[] = [ - 'name' => $statusName, - 'value' => $item['count'], - 'percentage' => 0, - 'status_code' => $statusCode, - ]; - } - - // Calculate percentages - $total = array_sum(array_column($data, 'value')); - foreach ($data as &$row) { - $row['percentage'] = $total > 0 ? round(($row['value'] / $total) * 100, 2) : 0; - } - - return $this->formatPieChartData( - 'KJC Status Pegangan', - $data, - '/v1/dashboard/kjc/drill-down/holdings-status', - ['status'] - ); - } - - /** - * Get KJC unit performance chart - */ - public function getUnitPerformanceChart(array $filters = []): array - { - $units = Unit::withCount([ - 'kjcAssetHoldings as bp_count' => function ($query) use ($filters) { - $query->where('status', 'BP'); - $this->applyCommonFilters($query, $filters); - }, - 'kjcAssetHoldings as bdg_count' => function ($query) use ($filters) { - $query->where('status', 'BDG'); - $this->applyCommonFilters($query, $filters); - }, - 'kjcAssetHoldings as tbdg_count' => function ($query) use ($filters) { - $query->where('status', 'TBDG'); - $this->applyCommonFilters($query, $filters); - } - ])->get(); - - $unitNames = []; - $bpCounts = []; - $bdgCounts = []; - $tbdgCounts = []; - - foreach ($units as $unit) { - $unitNames[] = $unit->name; - $bpCounts[] = $unit->bp_count; - $bdgCounts[] = $unit->bdg_count; - $tbdgCounts[] = $unit->tbdg_count; - } - - return $this->formatBarChartData( - 'KJC Unit 2025', - [ - ['name' => 'Baik dalam Perhatian (BP)', 'data' => $bpCounts], - ['name' => 'Boleh Digunakan (BDG)', 'data' => $bdgCounts], - ['name' => 'Tidak Boleh Digunakan (TBDG)', 'data' => $tbdgCounts] - ], - $unitNames, - '/v1/dashboard/kjc/drill-down/unit-performance', - ['unit_id'] - ); - } - - /** - * Get KJC monthly trends chart - */ - public function getMonthlyTrendsChart(array $filters = [], $user = null): array - { - $dateRange = $this->getDateRange($filters); - $categories = $this->getMonthlyCategories($filters); - - $monthlyData = $this->kjcDashboardRepository->getMonthlyTrends($filters, $user); - - // Initialize data arrays - $bpData = array_fill(0, count($categories), 0); - $bdgData = array_fill(0, count($categories), 0); - $tbdgData = array_fill(0, count($categories), 0); - - // Fill data arrays - foreach ($monthlyData as $item) { - $index = array_search($item['month'], $categories); - if ($index !== false) { - switch ($item['status']) { - case 'BP': - $bpData[$index] = $item['count']; - break; - case 'BDG': - $bdgData[$index] = $item['count']; - break; - case 'TBDG': - $tbdgData[$index] = $item['count']; - break; - } - } - } - - return $this->formatLineChartData( - 'KJC Trend Bulanan 2025', - [ - ['name' => 'Baik dalam Perhatian (BP)', 'data' => $bpData], - ['name' => 'Boleh Digunakan (BDG)', 'data' => $bdgData], - ['name' => 'Tidak Boleh Digunakan (TBDG)', 'data' => $tbdgData] - ], - $categories, - '/v1/dashboard/kjc/drill-down/monthly-trends', - ['month', 'status'] - ); - } - - /** - * Get KJC historical metrics bar chart (KEUPAYAAN, KESIAGAAN, SERVISIBILITI) - */ - public function getHistoricalMetricsBarChart(array $filters = [], $user = null): array - { - $monthlyData = $this->kjcDashboardRepository->getHistoricalMetricsByMonth($filters, $user); - - // Extract data for chart - $categories = []; - $keupayaanData = []; - $kesiagaanData = []; - $servisibilitiData = []; - - foreach ($monthlyData as $data) { - $monthDate = Carbon::createFromFormat('Y-m', $data['month']); - $categories[] = $monthDate->format('M'); // Remove year from categories - $keupayaanData[] = $data['keupayaan']; - $kesiagaanData[] = $data['kesiagaan']; - $servisibilitiData[] = $data['servisibiliti']; - } - - // Use year from user selection (date_from/date_to) or fall back to current year - $year = Carbon::now()->year; - if (!empty($filters['date_from'])) { - $year = Carbon::parse($filters['date_from'])->year; - } elseif (!empty($filters['date_to'])) { - $year = Carbon::parse($filters['date_to'])->year; - } - - return $this->formatBarChartData( - "Metrik Tahunan KJC {$year}", - [ - ['name' => 'KEUPAYAAN (%)', 'data' => $keupayaanData], - ['name' => 'KESIAGAAN (%)', 'data' => $kesiagaanData], - ['name' => 'SERVISIBILITI (%)', 'data' => $servisibilitiData] - ], - $categories - ); - } - - /** - * Get unit ID column name for KJC - */ - protected function getUnitIdColumn(): string - { - return 'kjc_asset_holdings.unit_id'; - } - - /** - * Get asset model class for KJC - */ - protected function getAssetModelClass(): string - { - return KJCAssetHolding::class; - } - - /** - * Get repair model class for KJC - */ - protected function getRepairModelClass(): string - { - return KJCRepair::class; - } -} \ No newline at end of file diff --git a/Modules/Dashboard/Services/PKJ/PKJDashboardService.php b/Modules/Dashboard/Services/PKJ/PKJDashboardService.php deleted file mode 100644 index af5d006..0000000 --- a/Modules/Dashboard/Services/PKJ/PKJDashboardService.php +++ /dev/null @@ -1,319 +0,0 @@ -pkjDashboardRepository = $pkjDashboardRepository; - } - /** - * Get total PKJ assets - */ - public function getTotalAssets(array $filters = [], $user = null): int - { - return $this->pkjDashboardRepository->getTotalHoldings($filters, $user); - } - - /** - * Get operational PKJ assets (BP status) - */ - public function getOperationalAssets(array $filters = [], $user = null): int - { - $holdings = $this->pkjDashboardRepository->getHoldingsBySpecificStatus('BP', $filters, $user); - return $holdings['pagination']['total']; - } - - /** - * Get under repair PKJ assets (TBDG status) - */ - public function getUnderRepairAssets(array $filters = [], $user = null): int - { - $holdings = $this->pkjDashboardRepository->getHoldingsBySpecificStatus('TBDG', $filters, $user); - return $holdings['pagination']['total']; - } - - /** - * Get maintenance due PKJ assets (BDG status) - */ - public function getMaintenanceDueAssets(array $filters = [], $user = null): int - { - $holdings = $this->pkjDashboardRepository->getHoldingsBySpecificStatus('BDG', $filters, $user); - return $holdings['pagination']['total']; - } - - /** - * Get active PKJ repairs - */ - public function getActiveRepairs(array $filters = []): int - { - $dateRange = $this->getDateRange($filters); - - return PKJRepair::query() - ->whereBetween('created_at', [$dateRange['from'], $dateRange['to']]) - ->where('status', 'active') - ->count(); - } - - /** - * Get pending PKJ reports - */ - public function getPendingReports(array $filters = []): int - { - $dateRange = $this->getDateRange($filters); - - return PKJReport::query() - ->whereBetween('created_at', [$dateRange['from'], $dateRange['to']]) - ->where('status', 'pending') - ->count(); - } - - /** - * Get PKJ entitlement vs holdings pie chart - */ - public function getEntitlementVsHoldingsPieChart(array $filters = [], $user = null): array - { - $totalEntitlement = $this->pkjDashboardRepository->getTotalEntitlement($filters, $user); - $totalHoldings = $this->pkjDashboardRepository->getTotalHoldings($filters, $user); - - $data = [ - [ - 'name' => 'Perjawatan', - 'value' => $totalEntitlement, - 'percentage' => ($totalEntitlement + $totalHoldings) > 0 ? round(($totalEntitlement / ($totalEntitlement + $totalHoldings)) * 100, 2) : 0 - ], - [ - 'name' => 'Pegangan', - 'value' => $totalHoldings, - 'percentage' => ($totalEntitlement + $totalHoldings) > 0 ? round(($totalHoldings / ($totalEntitlement + $totalHoldings)) * 100, 2) : 0 - ] - ]; - - return $this->formatPieChartData( - 'Perjawatan vs Pegangan PKJ', - $data, - '', // No drill-down - [] - ); - } - - /** - * Get PKJ holdings status breakdown pie chart - */ - public function getHoldingsStatusBreakdownPieChart(array $filters = [], $user = null): array - { - $statusCounts = $this->pkjDashboardRepository->getHoldingsByStatus($filters, $user); - - // Map status codes to readable names - $statusNames = [ - 'BP' => 'Baik dalam Perhatian', - 'BDG' => 'Boleh Digunakan', - 'TBDG' => 'Tidak Boleh Digunakan', - 'BT' => 'Boleh Tembak', - 'BG' => 'Baik Guna' - ]; - - $data = []; - foreach ($statusCounts as $status) { - $statusName = $statusNames[$status['status']] ?? $status['status']; - $data[] = [ - 'name' => $statusName, - 'value' => $status['count'], - 'percentage' => 0 // Will be calculated after total - ]; - } - - // Calculate percentages - $total = array_sum(array_column($data, 'value')); - foreach ($data as &$item) { - $item['percentage'] = $total > 0 ? round(($item['value'] / $total) * 100, 2) : 0; - } - - return $this->formatPieChartData( - 'PKJ Status Pegangan', - $data, - '', // No drill-down - [] - ); - } - - /** - * Get PKJ monthly trends chart - */ - public function getMonthlyTrendsChart(array $filters = [], $user = null): array - { - $monthlyData = $this->pkjDashboardRepository->getMonthlyTrends($filters, $user); - - $categories = []; - $data = []; - - foreach ($monthlyData as $trend) { - $categories[] = Carbon::createFromFormat('Y-m', $trend['month'])->format('M Y'); - $data[] = $trend['total_reports']; - } - - return $this->formatBarChartData( - 'Trend Bulanan PKJ 2025', - [ - ['name' => 'Laporan', 'data' => $data] - ], - $categories - ); - } - - /** - * Get PKJ holdings by category bar chart - */ - public function getHoldingsByCategoryBarChart(array $filters = [], $user = null): array - { - $categoryData = $this->pkjDashboardRepository->getHoldingsByCategory($filters, $user); - - $categories = []; - $categoryIds = []; - $data = []; - - foreach ($categoryData as $category) { - $categories[] = $category['category_name']; - $categoryIds[] = (int) $category['category_id']; - $data[] = (int) $category['total_holding']; - } - - $chartData = $this->formatBarChartData( - 'Pegangan PKJ Mengikut Kategori', - [ - ['name' => 'Jumlah Pegangan', 'data' => $data] - ], - $categories - ); - - // Add category IDs to the chart data for drill-down functionality - $chartData['category_ids'] = $categoryIds; - - return $chartData; - } - - /** - * Get PKJ category metrics bar chart (drill-down) - */ - public function getCategoryMetricsBarChart(int $categoryId, array $filters = [], $user = null): array - { - $metrics = $this->pkjDashboardRepository->getCategoryMetrics($categoryId, $filters, $user); - - $categories = ['KEUPAYAAN (%)', 'KESIAGAAN (%)', 'SERVISIBILITI (%)']; - $data = [ - $metrics['keupayaan'], - $metrics['kesiagaan'], - $metrics['servisibiliti'] - ]; - - $chartData = $this->formatBarChartData( - 'Metrik Kategori PKJ', - [ - ['name' => 'Metrics', 'data' => $data] - ], - $categories - ); - - // Add category information to the response - $chartData['category_id'] = $metrics['category_id']; - $chartData['category_name'] = $metrics['category_name']; - - return $chartData; - } - - /** - * Get PKJ category status breakdown pie chart (drill-down) - */ - public function getCategoryStatusBreakdownPieChart(int $categoryId, array $filters = [], $user = null): array - { - $breakdown = $this->pkjDashboardRepository->getCategoryStatusBreakdown($categoryId, $filters, $user); - - // Map status codes to readable names - $statusNames = [ - 'BP' => 'Baik dalam Perhatian', - 'BDG' => 'Boleh Digunakan', - 'TBDG' => 'Tidak Boleh Digunakan', - 'BT' => 'Boleh Tembak', - 'TBT' => 'Tidak Boleh Tembak', - 'BG' => 'Baik Guna', - 'TBG' => 'Tidak Baik Guna', - ]; - - $data = []; - foreach ($breakdown['status_breakdown'] as $status) { - $statusCode = $status['status']; - - // In the breakdown, status_code is null when the asset has no status (DB column is NULL). - if ($statusCode === null || $statusCode === '') { - $statusName = 'Status Tidak Diketahui'; - } else { - $statusName = $statusNames[$statusCode] ?? $statusCode; - } - - $data[] = [ - 'name' => $statusName, - 'value' => $status['count'], - 'percentage' => 0, // Will be calculated after total - 'status_code' => $statusCode // null when DB status column is NULL (Belum Diberi Status) - ]; - } - - // Calculate percentages - $total = array_sum(array_column($data, 'value')); - foreach ($data as &$item) { - $item['percentage'] = $total > 0 ? round(($item['value'] / $total) * 100, 2) : 0; - } - - $chartData = $this->formatPieChartData( - 'PKJ Status Pegangan Mengikut Kategori', - $data, - '/v1/dashboard/pkj/category/' . $categoryId . '/status-details', - ['status'] - ); - - // Add category information to the response - $chartData['category_id'] = $breakdown['category_id']; - $chartData['category_name'] = $breakdown['category_name']; - - return $chartData; - } - - /** - * Get unit ID column name for PKJ - */ - protected function getUnitIdColumn(): string - { - return 'pkj_asset_holdings.unit_id'; - } - - /** - * Get asset model class for PKJ - */ - protected function getAssetModelClass(): string - { - return PKJAssetHolding::class; - } - - /** - * Get repair model class for PKJ - */ - protected function getRepairModelClass(): string - { - return PKJRepair::class; - } -} diff --git a/Modules/Dashboard/Tests/Feature/.gitkeep b/Modules/Dashboard/Tests/Feature/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/Modules/Dashboard/Tests/Unit/.gitkeep b/Modules/Dashboard/Tests/Unit/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/Modules/Dashboard/Transformers/.gitkeep b/Modules/Dashboard/Transformers/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/Modules/Dashboard/composer.json b/Modules/Dashboard/composer.json deleted file mode 100644 index b53db27..0000000 --- a/Modules/Dashboard/composer.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "nwidart/dashboard", - "description": "", - "authors": [ - { - "name": "Nicolas Widart", - "email": "n.widart@gmail.com" - } - ], - "extra": { - "laravel": { - "providers": [], - "aliases": {} - } - }, - "autoload": { - "psr-4": { - "Modules\\Dashboard\\": "App", - "Modules\\Dashboard\\Database\\Factories\\": "database/factories/", - "Modules\\Dashboard\\Database\\Seeders\\": "database/seeders/" - } - }, - "autoload-dev": { - "psr-4": { - "Modules\\Dashboard\\Tests\\": "tests/" - } - } -} \ No newline at end of file diff --git a/Modules/Dashboard/module.json b/Modules/Dashboard/module.json deleted file mode 100644 index aeeead2..0000000 --- a/Modules/Dashboard/module.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "Dashboard", - "alias": "dashboard", - "description": "", - "keywords": [], - "priority": 0, - "providers": [ - "Modules\\Dashboard\\Providers\\DashboardServiceProvider" - ], - "files": [] -} \ No newline at end of file diff --git a/Modules/Dashboard/package.json b/Modules/Dashboard/package.json deleted file mode 100644 index d6fbfc8..0000000 --- a/Modules/Dashboard/package.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "private": true, - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build" - }, - "devDependencies": { - "axios": "^1.1.2", - "laravel-vite-plugin": "^0.7.5", - "sass": "^1.69.5", - "postcss": "^8.3.7", - "vite": "^4.0.0" - } -} diff --git a/modules_statuses.json b/modules_statuses.json index 117a790..a812f17 100644 --- a/modules_statuses.json +++ b/modules_statuses.json @@ -3,7 +3,6 @@ "User": true, "ActivityLog": true, "Role": true, - "Dashboard": true, "ExternalAPI": true, "Notification": true, "MembershipApplication": true,