Complete adaptation from BukidBountyApp to Philippine barangay governance: - Barangay models: Resident, Household, HouseholdMember, Blotter, BlotterHearing, DocumentRequest, RequestPayment, RequestType, BarangayProject, BarangayBudget - Controllers: ResidentController, HouseholdController, BlotterController, BlotterHearingController, DocumentRequestController, RequestTypeController, ProjectController, BudgetController, QRPHController, AdminConsoleController, UserController, FileController, ChapterController, LoginController - Vue pages: Home, ManageResidents, ResidentProfile, ManageHouseholds, ManageBlotters, BlotterDetail, RequestDocument, ManageDocumentRequests, DocumentRequestDetail, ManageRequestTypes, ManageProjects, BudgetLedger, AdminConsole - Barangay roles: PunongBarangay, Kagawad, Secretary, Treasurer, SK, Tanod, BHW, Staff, Resident - UserPermissions matrix rewritten with barangay-specific permission mappings - VueRouteMap replaced with barangay SPA routes - UserActions enum references corrected across all controllers - Removed all market/cooperative/POS/subscription code and models
356 lines
16 KiB
PHP
356 lines
16 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Http\Controllers\Support;
|
|
|
|
use Hypervel\Support\Facades\Route;
|
|
use Hypervel\Support\Facades\Auth;
|
|
use App\Enums\UserTypes;
|
|
use App\Support\ModuleHelper;
|
|
|
|
/**
|
|
* VueRouteMap handles automated registration of Vue SPA pages mapped via the Custom Inertia setup.
|
|
* It provides a modular way to handle ViewMap functionality without cluttering web.php.
|
|
*/
|
|
class VueRouteMap
|
|
{
|
|
/**
|
|
* Define the route mappings here.
|
|
* key: The URI route
|
|
* value: An array with:
|
|
* - 'component' (string): The Vue component path (e.g. 'ListProductsMarket')
|
|
* - 'middlewares' (array|string): Optional middlewares to apply to this route
|
|
* - 'name' (string): Optional route name
|
|
* - 'loginRequired' (bool): Whether authentication is required to access this page
|
|
* - 'allowedUserTypes' (array): List of allowed user types who can view this page
|
|
*/
|
|
protected static array $routes = [
|
|
// ── Public / Auth
|
|
'/' => ['component' => 'Home', 'loginRequired' => false],
|
|
'/app' => ['component' => 'Home', 'loginRequired' => false],
|
|
'/barangaysystem' => ['component' => 'Home', 'loginRequired' => false],
|
|
|
|
// ── Dashboard / Home
|
|
'/home' => ['component' => 'Home', 'loginRequired' => true],
|
|
'/dashboard' => ['component' => 'Home', 'loginRequired' => true],
|
|
|
|
// ── Auth
|
|
'/accountsettings' => ['component' => 'AccountSettings', 'loginRequired' => true],
|
|
|
|
// ── Announcements
|
|
'/manageannouncements' => ['component' => 'ManageAnnouncements', 'loginRequired' => true, 'module' => 'announcements'],
|
|
|
|
// ── System Settings / Admin
|
|
'/systemsettings' => ['component' => 'SystemSettings', 'loginRequired' => true],
|
|
'/landingpageeditor' => ['component' => 'LandingPageEditor', 'loginRequired' => true],
|
|
'/adminconsole' => ['component' => 'AdminConsole', 'loginRequired' => true],
|
|
|
|
// ── User Management
|
|
'/userlist' => ['component' => 'UserList', 'loginRequired' => true],
|
|
'/createuser' => ['component' => 'CreateUser', 'loginRequired' => true],
|
|
'/edituser' => ['component' => 'EditUser', 'loginRequired' => true],
|
|
'/manageuser' => ['component' => 'ManageUser', 'loginRequired' => true],
|
|
'/userregistration' => ['component' => 'UserRegistration', 'loginRequired' => true],
|
|
|
|
// ── Chapter Hierarchy
|
|
'/createchapter' => ['component' => 'CreateChapter', 'loginRequired' => true, 'module' => 'chapters'],
|
|
'/registerchapter' => ['component' => 'RegisterChapter', 'loginRequired' => true, 'module' => 'chapters'],
|
|
'/chapterorgchart' => ['component' => 'ChapterOrgChart', 'loginRequired' => true, 'module' => 'chapters'],
|
|
'/assignchapterofficer' => ['component' => 'AssignChapterOfficer', 'loginRequired' => true, 'module' => 'chapters'],
|
|
|
|
// ── Barangay Residents
|
|
'/barangay/manageresidents' => ['component' => 'Barangay.ManageResidents', 'loginRequired' => true, 'module' => 'residents'],
|
|
'/barangay/residentprofile' => ['component' => 'Barangay.ResidentProfile', 'loginRequired' => true, 'module' => 'residents'],
|
|
|
|
// ── Barangay Households
|
|
'/barangay/managehouseholds' => ['component' => 'Barangay.ManageHouseholds', 'loginRequired' => true, 'module' => 'households'],
|
|
|
|
// ── Blotters
|
|
'/barangay/manageblotters' => ['component' => 'Barangay.ManageBlotters', 'loginRequired' => true, 'module' => 'blotters'],
|
|
'/barangay/blotterdetail' => ['component' => 'Barangay.BlotterDetail', 'loginRequired' => true, 'module' => 'blotters'],
|
|
|
|
// ── Document Requests
|
|
'/barangay/requestdocument' => ['component' => 'Barangay.RequestDocument', 'loginRequired' => true, 'module' => 'certificates'],
|
|
'/barangay/managedocumentrequests' => ['component' => 'Barangay.ManageDocumentRequests','loginRequired' => true, 'module' => 'documents'],
|
|
'/barangay/documentrequestdetail' => ['component' => 'Barangay.DocumentRequestDetail','loginRequired' => true, 'module' => 'documents'],
|
|
'/barangay/managerequesttypes' => ['component' => 'Barangay.ManageRequestTypes', 'loginRequired' => true, 'module' => 'documents'],
|
|
|
|
// ── Projects
|
|
'/barangay/manageprojects' => ['component' => 'Barangay.ManageProjects', 'loginRequired' => true, 'module' => 'projects'],
|
|
|
|
// ── Budget
|
|
'/barangay/budgetledger' => ['component' => 'Barangay.BudgetLedger', 'loginRequired' => true, 'module' => 'budget'],
|
|
];
|
|
|
|
|
|
/**
|
|
* Helper method to check if user is allowed based on their type.
|
|
* Returns true if the user can access the page, false otherwise.
|
|
*/
|
|
private static function isUserAllowed(string $currentUserType, array $allowedUserTypes): bool
|
|
{
|
|
// If no restrictions specified, allow all authenticated users
|
|
if (empty($allowedUserTypes)) {
|
|
return true;
|
|
}
|
|
|
|
// Check if user type matches any allowed types
|
|
foreach ($allowedUserTypes as $type) {
|
|
if (is_string($type) && $currentUserType === $type) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Registers the defined Vue routes into the application.
|
|
* This should be called in `routes/web.php`.
|
|
*/
|
|
public static function registerRoutes(): void
|
|
{
|
|
foreach (self::$routes as $uri => $settings) {
|
|
$component = $settings['component'] ?? '';
|
|
$middlewares = $settings['middlewares'] ?? [];
|
|
$name = $settings['name'] ?? null;
|
|
$loginRequired = $settings['loginRequired'] ?? false;
|
|
$allowedUserTypes = $settings['allowedUserTypes'] ?? [];
|
|
|
|
$options = [];
|
|
|
|
if (!empty($middlewares)) {
|
|
$options['middleware'] = $middlewares;
|
|
}
|
|
|
|
if ($name) {
|
|
$options['as'] = $name;
|
|
}
|
|
|
|
// Add login requirement middleware if needed
|
|
if ($loginRequired) {
|
|
$options['middleware'] = array_unique(array_merge($options['middleware'] ?? [], ['auth']));
|
|
}
|
|
|
|
// Add module requirement middleware if specified
|
|
$moduleKey = $settings['module'] ?? null;
|
|
if ($moduleKey) {
|
|
$options['middleware'] = array_unique(array_merge($options['middleware'] ?? [], ["module:{$moduleKey}"]));
|
|
}
|
|
|
|
Route::get($uri, function (...$routeParams) use ($component, $loginRequired, $allowedUserTypes, $moduleKey) {
|
|
// Check if user is authenticated
|
|
if (empty(Auth::user())) {
|
|
// If login is required but not available, redirect to login page
|
|
if ($loginRequired) {
|
|
return redirect('/login');
|
|
}
|
|
}
|
|
|
|
// Check if module is enabled
|
|
if ($moduleKey && ModuleHelper::isDisabled($moduleKey)) {
|
|
return redirect('/');
|
|
}
|
|
|
|
// Global Page Disabled Check
|
|
/** @var \App\Models\User $user */
|
|
$user = Auth::user();
|
|
$disabledPages = \App\Models\SystemSetting::getValue('disabled_pages', []);
|
|
if (is_array($disabledPages) && in_array(strtolower((string)$component), array_map('strtolower', $disabledPages))) {
|
|
// Ultimate accounts can still access to allow fixing settings
|
|
if (!$user || $user->acct_type !== UserTypes::SUPER_ADMIN) {
|
|
return redirect('/');
|
|
}
|
|
}
|
|
|
|
// Check allowed user types if specified
|
|
if (!empty($allowedUserTypes)) {
|
|
$currentUserType = $user->acct_type?->value ?? $user->acct_type ?? UserTypes::PUBLIC->value;
|
|
$isAllowed = self::isUserAllowed($currentUserType, $allowedUserTypes);
|
|
if (!$isAllowed) {
|
|
// Redirect to a forbidden page or login
|
|
return redirect('/login');
|
|
}
|
|
}
|
|
|
|
// Return Inertia page properly set up with current user data
|
|
// Pass parameterized URL segments to the Vue component as properties
|
|
$page = Inertia::render($component, [
|
|
'user' => Auth::user(),
|
|
'routeParams' => empty($routeParams) ? null : $routeParams
|
|
]);
|
|
|
|
return view('layouts/application-layout', compact('page'));
|
|
}, $options);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handles a generic SPA request by converting the path into a component name.
|
|
* This acts as a catch-all universal router.
|
|
*/
|
|
public static function handleSpa(string $path = '/')
|
|
{
|
|
$path = trim($path, '/');
|
|
|
|
// Get user type for access control
|
|
/** @var \App\Models\User $user */
|
|
$user = Auth::user();
|
|
$currentUserType = null;
|
|
|
|
if ($user) {
|
|
$currentUserType = $user->acct_type?->value ?? $user->acct_type ?? UserTypes::PUBLIC->value;
|
|
}
|
|
|
|
// Strip hashkey/payload suffix from path (e.g., "edituser--h:HASHKEY" -> "edituser")
|
|
// Use RouteArgumentParser to properly extract the base component name
|
|
$component = $path;
|
|
|
|
try {
|
|
$parser = new \App\Support\RouteArgumentParser();
|
|
$parsedData = $parser->parseArgument($path);
|
|
|
|
// If we have a hash or payload format, use the slug as the component
|
|
if (isset($parsedData['slug']) && ($parsedData['type'] === 'hash' || $parsedData['type'] === 'payload')) {
|
|
$component = $parsedData['slug'];
|
|
}
|
|
} catch (\Throwable $th) {
|
|
// If parsing fails or no hash/payload format, use original path
|
|
}
|
|
|
|
// fallback to Home if empty
|
|
$component = empty($component) ? 'Home' : $component;
|
|
|
|
// Convert lowercase component names to camelCase for Vue component matching
|
|
// e.g., "edituser" -> "EditUser"
|
|
$vueComponent = self::toCamelCase($component);
|
|
|
|
// Find base component from routes map using the route key
|
|
$componentLower = strtolower($component);
|
|
$routeKey = '/' . $componentLower;
|
|
|
|
$loginRequired = true; // Default: require login
|
|
$pathWithSlash = '/' . ltrim($path, '/');
|
|
$pathLower = strtolower($pathWithSlash);
|
|
|
|
$routeSettings = self::$routes[$pathWithSlash] ?? self::$routes[$pathLower] ?? null;
|
|
$allowedUserTypes = [];
|
|
$moduleKey = null;
|
|
$foundInMap = false;
|
|
|
|
if ($routeSettings) {
|
|
$settings = $routeSettings;
|
|
$vueComponent = $settings['component'] ?? $vueComponent;
|
|
$loginRequired = $settings['loginRequired'] ?? true;
|
|
$allowedUserTypes = $settings['allowedUserTypes'] ?? [];
|
|
$moduleKey = $settings['module'] ?? null;
|
|
$foundInMap = true;
|
|
} else {
|
|
// Try hyphen-insensitive match (e.g. "viewstoremarket" vs "view-store-market")
|
|
$cleanSlug = str_replace(['-', '_'], '', $componentLower);
|
|
foreach (self::$routes as $uri => $settings) {
|
|
$cleanUri = str_replace(['-', '_'], '', strtolower(trim($uri, '/')));
|
|
if ($cleanUri === $cleanSlug) {
|
|
$vueComponent = $settings['component'] ?? $vueComponent;
|
|
$loginRequired = $settings['loginRequired'] ?? true;
|
|
$allowedUserTypes = $settings['allowedUserTypes'] ?? [];
|
|
$moduleKey = $settings['module'] ?? null;
|
|
$foundInMap = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!$foundInMap) {
|
|
// Fallback: search by Vue component name (case-insensitive) in case they passed the component direct name
|
|
foreach (self::$routes as $uri => $settings) {
|
|
$mappedComponent = $settings['component'] ?? '';
|
|
if (strcasecmp($mappedComponent, $vueComponent) === 0) {
|
|
$vueComponent = $mappedComponent; // Use the correctly cased name from map
|
|
$loginRequired = $settings['loginRequired'] ?? true;
|
|
$allowedUserTypes = $settings['allowedUserTypes'] ?? [];
|
|
$moduleKey = $settings['module'] ?? null;
|
|
$foundInMap = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Enforce login requirement
|
|
if ($loginRequired && empty($user)) {
|
|
return redirect('/login');
|
|
}
|
|
|
|
// Enforce module requirement
|
|
if ($moduleKey && ModuleHelper::isDisabled($moduleKey)) {
|
|
return redirect('/');
|
|
}
|
|
|
|
// For store-level users, check the store-specific module toggle
|
|
$storeModuleKey = $routeSettings['store_module'] ?? null;
|
|
if ($storeModuleKey && in_array($currentUserType, ['store owner', 'store manager'])) {
|
|
if (ModuleHelper::isDisabled($storeModuleKey)) {
|
|
return redirect('/');
|
|
}
|
|
}
|
|
|
|
// Global Page Disabled Check
|
|
$disabledPages = \App\Models\SystemSetting::getValue('disabled_pages', []);
|
|
if (is_array($disabledPages) && in_array(strtolower((string)$vueComponent), array_map('strtolower', $disabledPages))) {
|
|
// Ultimate accounts can still access to allow fixing settings
|
|
if (!$user || $user->acct_type !== UserTypes::SUPER_ADMIN) {
|
|
return redirect('/');
|
|
}
|
|
}
|
|
|
|
// Check allowed user types if specified
|
|
if (!empty($allowedUserTypes) && $currentUserType !== null) {
|
|
$isAllowed = self::isUserAllowed($currentUserType, $allowedUserTypes);
|
|
if (!$isAllowed) {
|
|
// Redirect to a forbidden page or login
|
|
return redirect('/login');
|
|
}
|
|
}
|
|
|
|
// Pass standard Inertia props with hashkey if present
|
|
$props = [
|
|
'user' => Auth::user(),
|
|
];
|
|
|
|
// Add hashkey to props if it was in the URL for special pages
|
|
if (isset($parsedData['type']) && $parsedData['type'] === 'hash') {
|
|
// Provide the hash value under multiple common prop names for better compatibility
|
|
$props['hashkey'] = $parsedData['value'];
|
|
$props['target'] = $parsedData['value'];
|
|
$props['id'] = $parsedData['value'];
|
|
} elseif (isset($parsedData['type']) && $parsedData['type'] === 'payload') {
|
|
$props['payload'] = $parsedData['value'];
|
|
}
|
|
|
|
$page = Inertia::render($vueComponent, $props);
|
|
|
|
return view('layouts/application-layout', compact('page'));
|
|
}
|
|
|
|
/**
|
|
* Convert snake_case or lowercase to camelCase for Vue component matching.
|
|
*/
|
|
private static function toCamelCase(string $name): string
|
|
{
|
|
// Replace slashes with dots first to handle directory structures
|
|
$name = str_replace('/', '.', $name);
|
|
|
|
// Split by dot (hierarchy)
|
|
$parts = explode('.', $name);
|
|
$pascalParts = array_map(function($part) {
|
|
// Split by hyphen and capitalize each part for PascalCase
|
|
$subParts = explode('-', $part);
|
|
return implode('', array_map('ucfirst', $subParts));
|
|
}, $parts);
|
|
|
|
// Return with dots if original had them, otherwise just one string
|
|
return implode('.', $pascalParts);
|
|
}
|
|
}
|