Refactor permissions and sidebar items; enhance user role checks

- Updated HandleInertiaRequests middleware to include user permissions in the shared data.
- Modified RolePermissionSeeder to redefine permissions and roles, adding new permissions and restructuring existing ones.
- Refactored app-sidebar component to filter menu items based on user permissions, improving sidebar visibility based on roles.
- Enhanced useCan hook to support checking multiple permissions with canAny function.
- Updated web routes to enforce permission checks on various resource routes, ensuring proper access control.
This commit is contained in:
Yoga Pangestu 2026-08-04 23:57:00 +07:00
parent 0a688f1028
commit 3e9776696d
7 changed files with 1202 additions and 203 deletions

184
app/Enums/Permission.php Normal file
View File

@ -0,0 +1,184 @@
<?php
namespace App\Enums;
enum Permission: string
{
// Dashboard
case DASHBOARD_VIEW = 'dashboard.view';
case DASHBOARD_ATTENDANCE = 'dashboard.attendance';
case DASHBOARD_REVENUE = 'dashboard.revenue';
case DASHBOARD_EXPENSE = 'dashboard.expense';
case DASHBOARD_ORDERS_CHANNEL = 'dashboard.orders_channel';
case DASHBOARD_ORDERS_PAYMENT = 'dashboard.orders_payment';
case DASHBOARD_ORDERS_MARKETING = 'dashboard.orders_marketing';
case DASHBOARD_ORDERS_STATUS = 'dashboard.orders_status';
case DASHBOARD_CASH = 'dashboard.cash';
// Analysis
case ANALYSIS_VIEW = 'analysis.view';
case ANALYSIS_ATTENDANCE = 'analysis.attendance';
case ANALYSIS_CASH = 'analysis.cash';
case ANALYSIS_PRODUCT_STOCK = 'analysis.product_stock';
case ANALYSIS_REVENUE = 'analysis.revenue';
case ANALYSIS_EXPENSE = 'analysis.expense';
case ANALYSIS_BUSY_HOURS = 'analysis.busy_hours';
case ANALYSIS_PROFIT_ORDERS = 'analysis.profit_orders';
case ANALYSIS_PROFIT_HPP = 'analysis.profit_hpp';
case ANALYSIS_PROFIT_GROSS = 'analysis.profit_gross';
case ANALYSIS_PROFIT_MARGIN = 'analysis.profit_margin';
case ANALYSIS_TOP_CUSTOMERS = 'analysis.top_customers';
case ANALYSIS_TOP_PRODUCTS = 'analysis.top_products';
case ANALYSIS_MARKETING_SALES = 'analysis.marketing_sales';
case ANALYSIS_RAW_MATERIALS = 'analysis.raw_materials';
// Employees
case EMPLOYEES_VIEW = 'employees.view';
case EMPLOYEES_CREATE = 'employees.create';
case EMPLOYEES_UPDATE = 'employees.update';
case EMPLOYEES_DELETE = 'employees.delete';
case EMPLOYEES_TOGGLE_STATUS = 'employees.toggle_status';
case EMPLOYEES_RESET_PASSWORD = 'employees.reset_password';
// Attendances
case ATTENDANCES_VIEW = 'attendances.view';
case ATTENDANCES_CREATE = 'attendances.create';
case ATTENDANCES_DELETE = 'attendances.delete';
case ATTENDANCES_MANAGE = 'attendances.manage';
// Leave Requests
case LEAVE_REQUESTS_VIEW = 'leave_requests.view';
case LEAVE_REQUESTS_CREATE = 'leave_requests.create';
case LEAVE_REQUESTS_UPDATE = 'leave_requests.update';
case LEAVE_REQUESTS_DELETE = 'leave_requests.delete';
case LEAVE_REQUESTS_VERIFY = 'leave_requests.verify';
// Categories
case CATEGORIES_VIEW = 'categories.view';
case CATEGORIES_CREATE = 'categories.create';
case CATEGORIES_UPDATE = 'categories.update';
case CATEGORIES_DELETE = 'categories.delete';
// Customers
case CUSTOMERS_VIEW = 'customers.view';
case CUSTOMERS_CREATE = 'customers.create';
case CUSTOMERS_UPDATE = 'customers.update';
case CUSTOMERS_DELETE = 'customers.delete';
// Products
case PRODUCTS_VIEW = 'products.view';
case PRODUCTS_CREATE = 'products.create';
case PRODUCTS_UPDATE = 'products.update';
case PRODUCTS_DELETE = 'products.delete';
case PRODUCTS_TOGGLE_STATUS = 'products.toggle_status';
// Stocks
case STOCKS_VIEW = 'stocks.view';
// Orders
case ORDERS_VIEW = 'orders.view';
case ORDERS_CREATE = 'orders.create';
case ORDERS_UPDATE = 'orders.update';
case ORDERS_DELETE = 'orders.delete';
case ORDERS_SEND = 'orders.send';
case ORDERS_COMPLETE = 'orders.complete';
case ORDERS_CANCEL = 'orders.cancel';
// Cuttings
case CUTTINGS_VIEW = 'cuttings.view';
case CUTTINGS_CREATE = 'cuttings.create';
case CUTTINGS_UPDATE = 'cuttings.update';
case CUTTINGS_DELETE = 'cuttings.delete';
case CUTTINGS_COMPLETE = 'cuttings.complete';
// Cash
case CASH_VIEW = 'cash.view';
case CASH_DEPOSIT = 'cash.deposit';
case CASH_WITHDRAW = 'cash.withdraw';
case CASH_UPDATE = 'cash.update';
case CASH_DELETE = 'cash.delete';
// Expenses
case EXPENSES_VIEW = 'expenses.view';
case EXPENSES_CREATE = 'expenses.create';
case EXPENSES_UPDATE = 'expenses.update';
case EXPENSES_DELETE = 'expenses.delete';
// Activity Logs
case ACTIVITY_LOGS_VIEW = 'activity_logs.view';
// Employee Advances
case EMPLOYEE_ADVANCES_VIEW = 'employee_advances.view';
case EMPLOYEE_ADVANCES_CREATE = 'employee_advances.create';
case EMPLOYEE_ADVANCES_UPDATE = 'employee_advances.update';
case EMPLOYEE_ADVANCES_DELETE = 'employee_advances.delete';
case EMPLOYEE_ADVANCES_PAY = 'employee_advances.pay';
case EMPLOYEE_ADVANCES_VERIFY = 'employee_advances.verify';
// Payroll
case PAYROLL_VIEW = 'payroll.view';
case PAYROLL_PAY = 'payroll.pay';
case PAYROLL_CANCEL = 'payroll.cancel';
case PAYROLL_ADJUST = 'payroll.adjust';
// Owner Verifications
case OWNER_VERIFICATIONS_VIEW = 'owner_verifications.view';
case OWNER_VERIFICATIONS_VERIFY = 'owner_verifications.verify';
case OWNER_VERIFICATIONS_REJECT = 'owner_verifications.reject';
// Restocks
case RESTOCKS_VIEW = 'restocks.view';
case RESTOCKS_CREATE = 'restocks.create';
case RESTOCKS_UPDATE = 'restocks.update';
case RESTOCKS_DELETE = 'restocks.delete';
// Raw Materials
case RAW_MATERIALS_VIEW = 'raw_materials.view';
case RAW_MATERIALS_CREATE = 'raw_materials.create';
case RAW_MATERIALS_UPDATE = 'raw_materials.update';
case RAW_MATERIALS_DELETE = 'raw_materials.delete';
case RAW_MATERIALS_TOGGLE_STATUS = 'raw_materials.toggle_status';
// Suppliers
case SUPPLIERS_VIEW = 'suppliers.view';
case SUPPLIERS_CREATE = 'suppliers.create';
case SUPPLIERS_UPDATE = 'suppliers.update';
case SUPPLIERS_DELETE = 'suppliers.delete';
// Purchases
case PURCHASES_VIEW = 'purchases.view';
case PURCHASES_CREATE = 'purchases.create';
case PURCHASES_UPDATE = 'purchases.update';
case PURCHASES_DELETE = 'purchases.delete';
// Stok Opnames
case STOK_OPNAMES_VIEW = 'stok_opnames.view';
case STOK_OPNAMES_CREATE = 'stok_opnames.create';
case STOK_OPNAMES_UPDATE = 'stok_opnames.update';
case STOK_OPNAMES_DELETE = 'stok_opnames.delete';
case STOK_OPNAMES_SUBMIT = 'stok_opnames.submit';
case STOK_OPNAMES_VERIFY = 'stok_opnames.verify';
// Roles
case ROLES_VIEW = 'roles.view';
case ROLES_CREATE = 'roles.create';
case ROLES_UPDATE = 'roles.update';
case ROLES_DELETE = 'roles.delete';
// Settings
case SETTINGS_VIEW_SYSTEM = 'settings.view_system';
case SETTINGS_UPDATE_SYSTEM = 'settings.update_system';
case SETTINGS_VIEW_HOMEPAGE = 'settings.view_homepage';
case SETTINGS_UPDATE_HOMEPAGE = 'settings.update_homepage';
case SETTINGS_VIEW_SOCIAL_MEDIA = 'settings.view_social_media';
case SETTINGS_UPDATE_SOCIAL_MEDIA = 'settings.update_social_media';
case SETTINGS_VIEW_MARKETPLACE = 'settings.view_marketplace';
case SETTINGS_UPDATE_MARKETPLACE = 'settings.update_marketplace';
case SETTINGS_VIEW_HR = 'settings.view_hr';
case SETTINGS_UPDATE_HR = 'settings.update_hr';
public function label(): string
{
return $this->value;
}
}

458
app/Enums/Role.php Normal file
View File

@ -0,0 +1,458 @@
<?php
namespace App\Enums;
use App\Traits\ProvidesEnumOptions;
enum Role: string
{
use ProvidesEnumOptions;
case DEVELOPER = 'developer';
case OWNER = 'owner';
case ADMIN_TOKO = 'admin-toko';
case ADMIN_BAHAN_BAKU = 'admin-bahan-baku';
case DIREKTUR = 'direktur';
case MARKETING_OFFLINE = 'marketing-offline';
case MARKETING_ONLINE = 'marketing-online';
case CASHIER = 'cashier';
case NON_OPERATOR = 'non-operator';
case STOK_OPNAME = 'stok-opname';
public function label(): string
{
return match ($this) {
self::DEVELOPER => 'Developer',
self::OWNER => 'Owner',
self::ADMIN_TOKO => 'Admin Toko',
self::ADMIN_BAHAN_BAKU => 'Admin Bahan Baku',
self::DIREKTUR => 'Direktur',
self::MARKETING_OFFLINE => 'Marketing Offline',
self::MARKETING_ONLINE => 'Marketing Online',
self::CASHIER => 'Kasir',
self::NON_OPERATOR => 'Non Operator',
self::STOK_OPNAME => 'Stok Opname',
};
}
public function permissions(): array
{
return match ($this) {
self::DEVELOPER, self::OWNER => array_values(array_filter(
Permission::cases(),
fn (Permission $permission) => ! in_array($permission, [
Permission::ATTENDANCES_CREATE,
Permission::ATTENDANCES_DELETE,
Permission::STOK_OPNAMES_SUBMIT,
Permission::ROLES_VIEW,
Permission::ROLES_CREATE,
Permission::ROLES_UPDATE,
Permission::ROLES_DELETE,
], true)
)),
self::DIREKTUR => [
Permission::DASHBOARD_VIEW,
Permission::DASHBOARD_ATTENDANCE,
Permission::DASHBOARD_REVENUE,
Permission::DASHBOARD_EXPENSE,
Permission::DASHBOARD_ORDERS_CHANNEL,
Permission::DASHBOARD_ORDERS_PAYMENT,
Permission::DASHBOARD_ORDERS_MARKETING,
Permission::DASHBOARD_ORDERS_STATUS,
Permission::ANALYSIS_VIEW,
Permission::ANALYSIS_ATTENDANCE,
Permission::ANALYSIS_CASH,
Permission::ANALYSIS_PRODUCT_STOCK,
Permission::ANALYSIS_REVENUE,
Permission::ANALYSIS_EXPENSE,
Permission::ANALYSIS_BUSY_HOURS,
Permission::ANALYSIS_PROFIT_ORDERS,
Permission::ANALYSIS_PROFIT_HPP,
Permission::ANALYSIS_PROFIT_GROSS,
Permission::ANALYSIS_PROFIT_MARGIN,
Permission::ANALYSIS_TOP_CUSTOMERS,
Permission::ANALYSIS_TOP_PRODUCTS,
Permission::ANALYSIS_MARKETING_SALES,
Permission::EMPLOYEES_VIEW,
Permission::ATTENDANCES_VIEW,
Permission::ATTENDANCES_CREATE,
Permission::ATTENDANCES_DELETE,
Permission::ATTENDANCES_MANAGE,
Permission::LEAVE_REQUESTS_VIEW,
Permission::LEAVE_REQUESTS_CREATE,
Permission::LEAVE_REQUESTS_UPDATE,
Permission::LEAVE_REQUESTS_DELETE,
Permission::CATEGORIES_VIEW,
Permission::CUSTOMERS_VIEW,
Permission::PRODUCTS_VIEW,
Permission::STOCKS_VIEW,
Permission::ORDERS_VIEW,
Permission::CUTTINGS_VIEW,
Permission::CASH_VIEW,
Permission::EXPENSES_VIEW,
Permission::ACTIVITY_LOGS_VIEW,
Permission::EMPLOYEE_ADVANCES_VIEW,
Permission::EMPLOYEE_ADVANCES_CREATE,
Permission::EMPLOYEE_ADVANCES_UPDATE,
Permission::EMPLOYEE_ADVANCES_DELETE,
Permission::EMPLOYEE_ADVANCES_PAY,
Permission::PAYROLL_VIEW,
],
self::ADMIN_TOKO => [
Permission::DASHBOARD_VIEW,
Permission::DASHBOARD_REVENUE,
Permission::DASHBOARD_EXPENSE,
Permission::DASHBOARD_ORDERS_CHANNEL,
Permission::DASHBOARD_ORDERS_PAYMENT,
Permission::DASHBOARD_ORDERS_MARKETING,
Permission::DASHBOARD_ORDERS_STATUS,
Permission::ANALYSIS_VIEW,
Permission::ANALYSIS_ATTENDANCE,
Permission::ANALYSIS_CASH,
Permission::ANALYSIS_PRODUCT_STOCK,
Permission::ANALYSIS_REVENUE,
Permission::ANALYSIS_EXPENSE,
Permission::ANALYSIS_BUSY_HOURS,
Permission::ANALYSIS_PROFIT_ORDERS,
Permission::ANALYSIS_PROFIT_HPP,
Permission::ANALYSIS_PROFIT_GROSS,
Permission::ANALYSIS_TOP_CUSTOMERS,
Permission::ANALYSIS_TOP_PRODUCTS,
Permission::ANALYSIS_MARKETING_SALES,
Permission::STOCKS_VIEW,
Permission::STOK_OPNAMES_VIEW,
Permission::ATTENDANCES_VIEW,
Permission::ATTENDANCES_CREATE,
Permission::ATTENDANCES_DELETE,
Permission::LEAVE_REQUESTS_VIEW,
Permission::LEAVE_REQUESTS_CREATE,
Permission::LEAVE_REQUESTS_UPDATE,
Permission::LEAVE_REQUESTS_DELETE,
Permission::RESTOCKS_VIEW,
Permission::RESTOCKS_CREATE,
Permission::RESTOCKS_UPDATE,
Permission::RESTOCKS_DELETE,
Permission::CUSTOMERS_VIEW,
Permission::CUSTOMERS_CREATE,
Permission::CUSTOMERS_UPDATE,
Permission::CUSTOMERS_DELETE,
Permission::PRODUCTS_VIEW,
Permission::PRODUCTS_CREATE,
Permission::PRODUCTS_UPDATE,
Permission::PRODUCTS_DELETE,
Permission::PRODUCTS_TOGGLE_STATUS,
Permission::STOCKS_VIEW,
Permission::OWNER_VERIFICATIONS_VIEW,
Permission::ORDERS_VIEW,
Permission::ORDERS_CREATE,
Permission::ORDERS_UPDATE,
Permission::ORDERS_DELETE,
Permission::ORDERS_SEND,
Permission::ORDERS_COMPLETE,
Permission::ORDERS_CANCEL,
Permission::CASH_VIEW,
Permission::CASH_DEPOSIT,
Permission::CASH_WITHDRAW,
Permission::CASH_UPDATE,
Permission::CASH_DELETE,
Permission::EXPENSES_VIEW,
Permission::EXPENSES_CREATE,
Permission::EXPENSES_UPDATE,
Permission::EXPENSES_DELETE,
Permission::ACTIVITY_LOGS_VIEW,
Permission::EMPLOYEE_ADVANCES_VIEW,
Permission::EMPLOYEE_ADVANCES_CREATE,
Permission::EMPLOYEE_ADVANCES_UPDATE,
Permission::EMPLOYEE_ADVANCES_DELETE,
Permission::EMPLOYEE_ADVANCES_PAY,
Permission::PAYROLL_VIEW,
Permission::PAYROLL_ADJUST,
Permission::SETTINGS_VIEW_SYSTEM,
Permission::SETTINGS_UPDATE_SYSTEM,
Permission::SETTINGS_VIEW_SOCIAL_MEDIA,
Permission::SETTINGS_UPDATE_SOCIAL_MEDIA,
Permission::SETTINGS_VIEW_MARKETPLACE,
Permission::SETTINGS_UPDATE_MARKETPLACE,
Permission::SETTINGS_VIEW_HR,
Permission::SETTINGS_UPDATE_HR,
Permission::SETTINGS_VIEW_HOMEPAGE,
Permission::SETTINGS_UPDATE_HOMEPAGE,
Permission::EMPLOYEES_VIEW,
Permission::EMPLOYEES_CREATE,
Permission::EMPLOYEES_UPDATE,
Permission::EMPLOYEES_TOGGLE_STATUS,
Permission::EMPLOYEES_DELETE,
],
self::CASHIER => [
Permission::DASHBOARD_VIEW,
Permission::DASHBOARD_REVENUE,
Permission::DASHBOARD_ORDERS_CHANNEL,
Permission::DASHBOARD_ORDERS_PAYMENT,
Permission::DASHBOARD_ORDERS_STATUS,
Permission::ANALYSIS_VIEW,
Permission::ANALYSIS_ATTENDANCE,
Permission::ANALYSIS_REVENUE,
Permission::ANALYSIS_PROFIT_ORDERS,
Permission::ANALYSIS_TOP_CUSTOMERS,
Permission::ANALYSIS_TOP_PRODUCTS,
Permission::ATTENDANCES_VIEW,
Permission::ATTENDANCES_CREATE,
Permission::ATTENDANCES_DELETE,
Permission::LEAVE_REQUESTS_VIEW,
Permission::LEAVE_REQUESTS_CREATE,
Permission::LEAVE_REQUESTS_UPDATE,
Permission::LEAVE_REQUESTS_DELETE,
Permission::CUSTOMERS_VIEW,
Permission::CUSTOMERS_CREATE,
Permission::CUSTOMERS_UPDATE,
Permission::CUSTOMERS_DELETE,
Permission::PRODUCTS_VIEW,
Permission::ORDERS_VIEW,
Permission::ORDERS_CREATE,
Permission::ORDERS_UPDATE,
Permission::ORDERS_DELETE,
Permission::ORDERS_SEND,
Permission::ORDERS_COMPLETE,
Permission::ORDERS_CANCEL,
Permission::EMPLOYEE_ADVANCES_VIEW,
Permission::EMPLOYEE_ADVANCES_CREATE,
Permission::EMPLOYEE_ADVANCES_UPDATE,
Permission::EMPLOYEE_ADVANCES_DELETE,
Permission::EMPLOYEE_ADVANCES_PAY,
Permission::PAYROLL_VIEW,
Permission::PAYROLL_ADJUST,
],
self::ADMIN_BAHAN_BAKU => [
Permission::DASHBOARD_VIEW,
Permission::EMPLOYEES_VIEW,
Permission::EMPLOYEES_CREATE,
Permission::EMPLOYEES_UPDATE,
Permission::EMPLOYEES_TOGGLE_STATUS,
Permission::EMPLOYEES_DELETE,
Permission::LEAVE_REQUESTS_VIEW,
Permission::LEAVE_REQUESTS_CREATE,
Permission::LEAVE_REQUESTS_UPDATE,
Permission::LEAVE_REQUESTS_DELETE,
Permission::CATEGORIES_VIEW,
Permission::CATEGORIES_CREATE,
Permission::CATEGORIES_UPDATE,
Permission::CATEGORIES_DELETE,
Permission::SUPPLIERS_VIEW,
Permission::SUPPLIERS_CREATE,
Permission::SUPPLIERS_UPDATE,
Permission::SUPPLIERS_DELETE,
Permission::RAW_MATERIALS_VIEW,
Permission::RAW_MATERIALS_CREATE,
Permission::RAW_MATERIALS_UPDATE,
Permission::RAW_MATERIALS_TOGGLE_STATUS,
Permission::STOCKS_VIEW,
Permission::OWNER_VERIFICATIONS_VIEW,
Permission::PRODUCTS_VIEW,
Permission::PRODUCTS_CREATE,
Permission::PRODUCTS_UPDATE,
Permission::PRODUCTS_DELETE,
Permission::PRODUCTS_TOGGLE_STATUS,
Permission::CUTTINGS_VIEW,
Permission::CUTTINGS_CREATE,
Permission::CUTTINGS_UPDATE,
Permission::CUTTINGS_DELETE,
Permission::CUTTINGS_COMPLETE,
Permission::PURCHASES_VIEW,
Permission::PURCHASES_CREATE,
Permission::PURCHASES_UPDATE,
Permission::PURCHASES_DELETE,
Permission::EMPLOYEE_ADVANCES_VIEW,
Permission::EMPLOYEE_ADVANCES_CREATE,
Permission::EMPLOYEE_ADVANCES_UPDATE,
Permission::EMPLOYEE_ADVANCES_DELETE,
Permission::EMPLOYEE_ADVANCES_PAY,
Permission::ACTIVITY_LOGS_VIEW,
Permission::PAYROLL_VIEW,
],
self::MARKETING_OFFLINE, self::MARKETING_ONLINE => [
Permission::DASHBOARD_VIEW,
Permission::DASHBOARD_REVENUE,
Permission::DASHBOARD_ORDERS_CHANNEL,
Permission::DASHBOARD_ORDERS_PAYMENT,
Permission::DASHBOARD_ORDERS_MARKETING,
Permission::DASHBOARD_ORDERS_STATUS,
Permission::ANALYSIS_VIEW,
Permission::ANALYSIS_ATTENDANCE,
Permission::ANALYSIS_REVENUE,
Permission::ANALYSIS_PROFIT_ORDERS,
Permission::ANALYSIS_TOP_PRODUCTS,
Permission::ANALYSIS_TOP_CUSTOMERS,
Permission::ATTENDANCES_VIEW,
Permission::ATTENDANCES_CREATE,
Permission::LEAVE_REQUESTS_VIEW,
Permission::LEAVE_REQUESTS_CREATE,
Permission::LEAVE_REQUESTS_UPDATE,
Permission::LEAVE_REQUESTS_DELETE,
Permission::PRODUCTS_VIEW,
Permission::ORDERS_VIEW,
Permission::EMPLOYEE_ADVANCES_VIEW,
Permission::EMPLOYEE_ADVANCES_CREATE,
Permission::EMPLOYEE_ADVANCES_UPDATE,
Permission::EMPLOYEE_ADVANCES_DELETE,
Permission::EMPLOYEE_ADVANCES_PAY,
Permission::PAYROLL_VIEW,
],
self::NON_OPERATOR => [
Permission::DASHBOARD_VIEW,
Permission::ANALYSIS_VIEW,
Permission::ANALYSIS_ATTENDANCE,
Permission::ATTENDANCES_VIEW,
Permission::ATTENDANCES_CREATE,
Permission::LEAVE_REQUESTS_VIEW,
Permission::LEAVE_REQUESTS_CREATE,
Permission::LEAVE_REQUESTS_UPDATE,
Permission::LEAVE_REQUESTS_DELETE,
Permission::EMPLOYEE_ADVANCES_VIEW,
Permission::EMPLOYEE_ADVANCES_CREATE,
Permission::EMPLOYEE_ADVANCES_UPDATE,
Permission::EMPLOYEE_ADVANCES_DELETE,
Permission::EMPLOYEE_ADVANCES_PAY,
Permission::PAYROLL_VIEW,
],
self::STOK_OPNAME => [
Permission::DASHBOARD_VIEW,
Permission::ATTENDANCES_VIEW,
Permission::ATTENDANCES_CREATE,
Permission::LEAVE_REQUESTS_VIEW,
Permission::LEAVE_REQUESTS_CREATE,
Permission::LEAVE_REQUESTS_UPDATE,
Permission::LEAVE_REQUESTS_DELETE,
Permission::PRODUCTS_VIEW,
Permission::STOCKS_VIEW,
Permission::STOK_OPNAMES_VIEW,
Permission::STOK_OPNAMES_CREATE,
Permission::STOK_OPNAMES_UPDATE,
Permission::STOK_OPNAMES_DELETE,
Permission::STOK_OPNAMES_SUBMIT,
Permission::EMPLOYEE_ADVANCES_VIEW,
Permission::EMPLOYEE_ADVANCES_CREATE,
Permission::EMPLOYEE_ADVANCES_UPDATE,
Permission::EMPLOYEE_ADVANCES_DELETE,
Permission::EMPLOYEE_ADVANCES_PAY,
Permission::PAYROLL_VIEW,
],
};
}
public function isAssignable(): bool
{
return $this !== self::DEVELOPER;
}
/**
* @return list<array{value: string, label: string}>
*/
public static function assignableSelectOptions(): array
{
return collect(self::cases())
->filter(fn (self $role) => $role->isAssignable())
->map(fn (self $role) => [
'value' => $role->value,
'label' => $role->label(),
])
->values()
->all();
}
/**
* @return list<string>
*/
public static function assignableValues(): array
{
return collect(self::cases())
->filter(fn (self $role) => $role->isAssignable())
->map(fn (self $role) => $role->value)
->values()
->all();
}
/**
* @return list<string>
*/
public static function values(): array
{
return array_column(self::cases(), 'value');
}
}

View File

@ -40,7 +40,9 @@ public function share(Request $request): array
'name' => config('app.name'),
'auth' => [
'user' => $request->user()
? $request->user()->load('userProfile', 'roles', 'permissions')
? tap($request->user()->load('userProfile', 'roles'), function ($user) {
$user->setRelation('permissions', $user->getAllPermissions());
})
: null,
],
'sidebarOpen' => ! $request->hasCookie('sidebar_state') || $request->cookie('sidebar_state') === 'true',

View File

@ -14,25 +14,30 @@ public function run(): void
app()[PermissionRegistrar::class]->forgetCachedPermissions();
$permissions = [
'user' => ['view', 'create', 'update', 'delete', 'toggle-active', 'reset-password'],
'category' => ['view', 'create', 'update', 'delete'],
'supplier' => ['view', 'create', 'update', 'delete'],
'customer' => ['view', 'create', 'update', 'delete'],
'cash-account' => ['view', 'deposit', 'withdrawal', 'update', 'delete'],
'expense' => ['view', 'create', 'update', 'delete'],
'employee-advance' => ['view', 'create', 'update', 'delete', 'approve', 'pay'],
'payroll-period' => ['view', 'show', 'current', 'close', 'reopen'],
'payroll' => ['pay', 'cancel'],
'payroll-adjustment' => ['create', 'delete'],
'leave-request' => ['view', 'create', 'update', 'delete', 'approve', 'reject'],
'attendance' => ['view', 'check-in', 'check-out', 'by-date'],
'settings' => ['view', 'update-system', 'update-homepage', 'update-social-media', 'update-marketplace', 'update-hr'],
'purchase' => ['view', 'create', 'update', 'delete'],
'cutting' => ['view', 'create', 'update', 'delete'],
'restock' => ['view', 'create', 'update', 'delete'],
'transaction' => ['view', 'create', 'update', 'delete'],
'dashboard' => ['attendance', 'revenue', 'expense', 'orders_channel', 'orders_payment', 'orders_marketing', 'orders_status'],
'analysis' => ['attendance', 'cash', 'raw_materials', 'product_stock', 'revenue', 'expense', 'profit_gross', 'profit_hpp', 'profit_orders', 'marketing_sales', 'top_suppliers', 'top_products', 'top_customers', 'busy_hours'],
'dashboard' => ['view', 'attendance', 'revenue', 'expense', 'orders_channel', 'orders_payment', 'orders_marketing', 'orders_status', 'cash'],
'analysis' => ['view', 'attendance', 'cash', 'product_stock', 'revenue', 'expense', 'busy_hours', 'profit_orders', 'profit_hpp', 'profit_gross', 'profit_margin', 'top_customers', 'top_products', 'marketing_sales', 'raw_materials'],
'employees' => ['view', 'create', 'update', 'delete', 'toggle_status', 'reset_password'],
'attendances' => ['view', 'create', 'delete', 'manage'],
'leave_requests' => ['view', 'create', 'update', 'delete', 'verify'],
'categories' => ['view', 'create', 'update', 'delete'],
'customers' => ['view', 'create', 'update', 'delete'],
'products' => ['view', 'create', 'update', 'delete', 'toggle_status'],
'stocks' => ['view'],
'orders' => ['view', 'create', 'update', 'delete', 'send', 'complete', 'cancel'],
'cuttings' => ['view', 'create', 'update', 'delete', 'complete'],
'cash' => ['view', 'deposit', 'withdraw', 'update', 'delete'],
'expenses' => ['view', 'create', 'update', 'delete'],
'activity_logs' => ['view'],
'employee_advances' => ['view', 'create', 'update', 'delete', 'pay', 'verify'],
'payroll' => ['view', 'pay', 'cancel', 'adjust'],
'owner_verifications' => ['view', 'verify', 'reject'],
'restocks' => ['view', 'create', 'update', 'delete'],
'raw_materials' => ['view', 'create', 'update', 'delete', 'toggle_status'],
'suppliers' => ['view', 'create', 'update', 'delete'],
'purchases' => ['view', 'create', 'update', 'delete'],
'stok_opnames' => ['view', 'create', 'update', 'delete', 'submit', 'verify'],
'roles' => ['view', 'create', 'update', 'delete'],
'settings' => ['view_system', 'update_system', 'view_homepage', 'update_homepage', 'view_social_media', 'update_social_media', 'view_marketplace', 'update_marketplace', 'view_hr', 'update_hr'],
];
foreach ($permissions as $module => $actions) {
@ -43,81 +48,416 @@ public function run(): void
$allPermissions = Permission::all()->pluck('name')->toArray();
$excludedFromDeveloperOwner = [
'attendances.create',
'attendances.delete',
'stok_opnames.submit',
'roles.view',
'roles.create',
'roles.update',
'roles.delete',
];
$developerOwnerPerms = array_values(array_filter(
$allPermissions,
fn($p) => ! in_array($p, $excludedFromDeveloperOwner, true)
));
$rolePermissions = [
'Developer' => $allPermissions,
'developer' => $developerOwnerPerms,
'Owner' => array_filter($allPermissions, fn ($p) => ! str_starts_with($p, 'settings.update-')),
'owner' => $developerOwnerPerms,
'Direktur' => array_filter($allPermissions, function ($p) {
return str_ends_with($p, '.view')
|| str_starts_with($p, 'payroll-period.')
|| $p === 'payroll.pay'
|| $p === 'leave-request.approve'
|| $p === 'leave-request.reject'
|| $p === 'attendance.by-date';
}),
'admin-toko' => array_values(array_filter($allPermissions, function ($p) {
return in_array($p, [
'dashboard.view',
'dashboard.revenue',
'dashboard.expense',
'dashboard.orders_channel',
'dashboard.orders_payment',
'dashboard.orders_marketing',
'dashboard.orders_status',
'Admin Bahan Baku' => array_filter($allPermissions, function ($p) {
return str_starts_with($p, 'supplier.')
|| str_starts_with($p, 'purchase.')
|| str_starts_with($p, 'cutting.')
|| str_starts_with($p, 'restock.')
|| $p === 'category.view'
|| $p === 'customer.view'
|| $p === 'cash-account.view';
}),
'analysis.view',
'analysis.attendance',
'analysis.cash',
'analysis.product_stock',
'analysis.revenue',
'analysis.expense',
'analysis.busy_hours',
'analysis.profit_orders',
'analysis.profit_hpp',
'analysis.profit_gross',
'analysis.top_customers',
'analysis.top_products',
'analysis.marketing_sales',
'Admin Toko' => array_filter($allPermissions, function ($p) {
return str_starts_with($p, 'category.')
|| str_starts_with($p, 'customer.')
|| str_starts_with($p, 'expense.')
|| str_starts_with($p, 'restock.')
|| $p === 'cash-account.view'
|| $p === 'leave-request.view'
|| $p === 'leave-request.create'
|| str_starts_with($p, 'attendance.');
}),
'stocks.view',
'stok_opnames.view',
'Marketing Offline' => array_filter($allPermissions, function ($p) {
return str_starts_with($p, 'customer.')
|| $p === 'category.view'
|| $p === 'supplier.view'
|| $p === 'leave-request.view'
|| $p === 'leave-request.create'
|| $p === 'attendance.check-in'
|| $p === 'attendance.check-out';
}),
'attendances.view',
'attendances.create',
'attendances.delete',
'Marketing Online' => array_filter($allPermissions, function ($p) {
return str_starts_with($p, 'customer.')
|| $p === 'category.view'
|| $p === 'supplier.view'
|| $p === 'leave-request.view'
|| $p === 'leave-request.create'
|| $p === 'attendance.check-in'
|| $p === 'attendance.check-out';
}),
'leave_requests.view',
'leave_requests.create',
'leave_requests.update',
'leave_requests.delete',
'Kasir' => array_filter($allPermissions, function ($p) {
return str_starts_with($p, 'cash-account.')
|| $p === 'expense.view'
|| $p === 'leave-request.view'
|| $p === 'leave-request.create'
|| $p === 'attendance.check-in'
|| $p === 'attendance.check-out';
}),
'restocks.view',
'restocks.create',
'restocks.update',
'restocks.delete',
'Stok Opname' => array_filter($allPermissions, function ($p) {
return str_starts_with($p, 'supplier.')
|| $p === 'category.view'
|| $p === 'cash-account.view'
|| $p === 'leave-request.view'
|| $p === 'leave-request.create'
|| $p === 'attendance.check-in'
|| $p === 'attendance.check-out';
}),
'categories.view',
'categories.create',
'categories.update',
'categories.delete',
'Non Operator' => array_filter($allPermissions, fn ($p) => str_ends_with($p, '.view')),
'customers.view',
'customers.create',
'customers.update',
'customers.delete',
'products.view',
'products.create',
'products.update',
'products.delete',
'products.toggle_status',
'owner_verifications.view',
'orders.view',
'orders.create',
'orders.update',
'orders.delete',
'orders.send',
'orders.complete',
'orders.cancel',
'cash.view',
'cash.deposit',
'cash.withdraw',
'cash.update',
'cash.delete',
'expenses.view',
'expenses.create',
'expenses.update',
'expenses.delete',
'activity_logs.view',
'employee_advances.view',
'employee_advances.create',
'employee_advances.update',
'employee_advances.delete',
'employee_advances.pay',
'payroll.view',
'payroll.adjust',
'settings.view_system',
'settings.update_system',
'settings.view_social_media',
'settings.update_social_media',
'settings.view_marketplace',
'settings.update_marketplace',
'settings.view_hr',
'settings.update_hr',
'settings.view_homepage',
'settings.update_homepage',
'employees.view',
'employees.create',
'employees.update',
'employees.toggle_status',
'employees.delete',
], true);
})),
'cashier' => array_values(array_filter($allPermissions, function ($p) {
return in_array($p, [
'dashboard.view',
'dashboard.revenue',
'dashboard.orders_channel',
'dashboard.orders_payment',
'dashboard.orders_status',
'analysis.view',
'analysis.attendance',
'analysis.revenue',
'analysis.profit_orders',
'analysis.top_customers',
'analysis.top_products',
'attendances.view',
'attendances.create',
'attendances.delete',
'leave_requests.view',
'leave_requests.create',
'leave_requests.update',
'leave_requests.delete',
'customers.view',
'customers.create',
'customers.update',
'customers.delete',
'products.view',
'orders.view',
'orders.create',
'orders.update',
'orders.delete',
'orders.send',
'orders.complete',
'orders.cancel',
'employee_advances.view',
'employee_advances.create',
'employee_advances.update',
'employee_advances.delete',
'employee_advances.pay',
'payroll.view',
'payroll.adjust',
], true);
})),
'admin-bahan-baku' => array_values(array_filter($allPermissions, function ($p) {
return in_array($p, [
'dashboard.view',
'employees.view',
'employees.create',
'employees.update',
'employees.toggle_status',
'employees.delete',
'leave_requests.view',
'leave_requests.create',
'leave_requests.update',
'leave_requests.delete',
'categories.view',
'categories.create',
'categories.update',
'categories.delete',
'suppliers.view',
'suppliers.create',
'suppliers.update',
'suppliers.delete',
'raw_materials.view',
'raw_materials.create',
'raw_materials.update',
'raw_materials.toggle_status',
'stocks.view',
'owner_verifications.view',
'products.view',
'products.create',
'products.update',
'products.delete',
'products.toggle_status',
'cuttings.view',
'cuttings.create',
'cuttings.update',
'cuttings.delete',
'cuttings.complete',
'purchases.view',
'purchases.create',
'purchases.update',
'purchases.delete',
'employee_advances.view',
'employee_advances.create',
'employee_advances.update',
'employee_advances.delete',
'employee_advances.pay',
'activity_logs.view',
'payroll.view',
], true);
})),
'marketing-offline' => array_values(array_filter($allPermissions, function ($p) {
return in_array($p, [
'dashboard.view',
'dashboard.revenue',
'dashboard.orders_channel',
'dashboard.orders_payment',
'dashboard.orders_marketing',
'dashboard.orders_status',
'analysis.view',
'analysis.attendance',
'analysis.revenue',
'analysis.profit_orders',
'analysis.top_products',
'analysis.top_customers',
'attendances.view',
'attendances.create',
'leave_requests.view',
'leave_requests.create',
'leave_requests.update',
'leave_requests.delete',
'products.view',
'orders.view',
'employee_advances.view',
'employee_advances.create',
'employee_advances.update',
'employee_advances.delete',
'employee_advances.pay',
'payroll.view',
], true);
})),
'marketing-online' => array_values(array_filter($allPermissions, function ($p) {
return in_array($p, [
'dashboard.view',
'dashboard.revenue',
'dashboard.orders_channel',
'dashboard.orders_payment',
'dashboard.orders_marketing',
'dashboard.orders_status',
'analysis.view',
'analysis.attendance',
'analysis.revenue',
'analysis.profit_orders',
'analysis.top_products',
'analysis.top_customers',
'attendances.view',
'attendances.create',
'leave_requests.view',
'leave_requests.create',
'leave_requests.update',
'leave_requests.delete',
'products.view',
'orders.view',
'employee_advances.view',
'employee_advances.create',
'employee_advances.update',
'employee_advances.delete',
'employee_advances.pay',
'payroll.view',
], true);
})),
'direktur' => array_values(array_filter($allPermissions, function ($p) {
return in_array($p, [
'dashboard.view',
'dashboard.attendance',
'dashboard.revenue',
'dashboard.expense',
'dashboard.orders_channel',
'dashboard.orders_payment',
'dashboard.orders_marketing',
'dashboard.orders_status',
'analysis.view',
'analysis.attendance',
'analysis.cash',
'analysis.product_stock',
'analysis.revenue',
'analysis.expense',
'analysis.busy_hours',
'analysis.profit_orders',
'analysis.profit_hpp',
'analysis.profit_gross',
'analysis.profit_margin',
'analysis.top_customers',
'analysis.top_products',
'analysis.marketing_sales',
'employees.view',
'attendances.view',
'attendances.create',
'attendances.delete',
'attendances.manage',
'leave_requests.view',
'leave_requests.create',
'leave_requests.update',
'leave_requests.delete',
'categories.view',
'customers.view',
'products.view',
'stocks.view',
'orders.view',
'cuttings.view',
'cash.view',
'expenses.view',
'activity_logs.view',
'employee_advances.view',
'employee_advances.create',
'employee_advances.update',
'employee_advances.delete',
'employee_advances.pay',
'payroll.view',
], true);
})),
'non-operator' => array_values(array_filter($allPermissions, function ($p) {
return in_array($p, [
'dashboard.view',
'analysis.view',
'analysis.attendance',
'attendances.view',
'attendances.create',
'leave_requests.view',
'leave_requests.create',
'leave_requests.update',
'leave_requests.delete',
'employee_advances.view',
'employee_advances.create',
'employee_advances.update',
'employee_advances.delete',
'employee_advances.pay',
'payroll.view',
], true);
})),
'stok-opname' => array_values(array_filter($allPermissions, function ($p) {
return in_array($p, [
'dashboard.view',
'attendances.view',
'attendances.create',
'leave_requests.view',
'leave_requests.create',
'leave_requests.update',
'leave_requests.delete',
'products.view',
'stocks.view',
'stok_opnames.view',
'stok_opnames.create',
'stok_opnames.update',
'stok_opnames.delete',
'stok_opnames.submit',
'employee_advances.view',
'employee_advances.create',
'employee_advances.update',
'employee_advances.delete',
'employee_advances.pay',
'payroll.view',
], true);
})),
];
foreach ($rolePermissions as $roleName => $perms) {

View File

@ -1,3 +1,35 @@
import AppLogo from '@/components/app-logo';
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupLabel,
SidebarHeader,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
useSidebar,
} from '@/components/ui/sidebar';
import { useCan } from '@/hooks/use-can';
import { useCurrentUrl } from '@/hooks/use-current-url';
import { dashboard } from '@/routes';
import { index as cashAccountsIndex } from '@/routes/admin/finance/cash-accounts';
import { index as employeeAdvancesIndex } from '@/routes/admin/finance/employee-advances';
import { index as expensesIndex } from '@/routes/admin/finance/expenses';
import { current as payrollCurrent } from '@/routes/admin/finance/payroll-periods';
import { index as attendancesIndex } from '@/routes/admin/hr/attendances';
import { index as employeesIndex } from '@/routes/admin/hr/employees';
import { index as leaveRequestsIndex } from '@/routes/admin/hr/leave-requests';
import { index as cuttingsIndex } from '@/routes/admin/manage/cuttings';
import { index as purchasesIndex } from '@/routes/admin/manage/purchases';
import { index as restocksIndex } from '@/routes/admin/manage/restocks';
import { index as transactionsIndex } from '@/routes/admin/manage/transactions';
import { index as categoriesIndex } from '@/routes/admin/master/categories';
import { index as customersIndex } from '@/routes/admin/master/customers';
import { index as productsIndex } from '@/routes/admin/master/products';
import { index as rawMaterialsIndex } from '@/routes/admin/master/raw-materials';
import { index as suppliersIndex } from '@/routes/admin/master/suppliers';
import { index as rolesIndex } from '@/routes/admin/settings/roles';
import { Link, router } from '@inertiajs/react';
import type { LucideIcon } from 'lucide-react';
import {
@ -24,40 +56,8 @@ import {
Wallet,
} from 'lucide-react';
import React from 'react';
import AppLogo from '@/components/app-logo';
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupLabel,
SidebarHeader,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
useSidebar,
} from '@/components/ui/sidebar';
import { useCurrentUrl } from '@/hooks/use-current-url';
import { dashboard } from '@/routes';
import admin from '@/routes/admin';
import { index as cashAccountsIndex } from '@/routes/admin/finance/cash-accounts';
import { index as employeeAdvancesIndex } from '@/routes/admin/finance/employee-advances';
import { index as expensesIndex } from '@/routes/admin/finance/expenses';
import { current as payrollCurrent } from '@/routes/admin/finance/payroll-periods';
import { index as attendancesIndex } from '@/routes/admin/hr/attendances';
import { index as employeesIndex } from '@/routes/admin/hr/employees';
import { index as leaveRequestsIndex } from '@/routes/admin/hr/leave-requests';
import { index as cuttingsIndex } from '@/routes/admin/manage/cuttings';
import { index as purchasesIndex } from '@/routes/admin/manage/purchases';
import { index as restocksIndex } from '@/routes/admin/manage/restocks';
import { index as transactionsIndex } from '@/routes/admin/manage/transactions';
import { index as categoriesIndex } from '@/routes/admin/master/categories';
import { index as customersIndex } from '@/routes/admin/master/customers';
import { index as productsIndex } from '@/routes/admin/master/products';
import { index as rawMaterialsIndex } from '@/routes/admin/master/raw-materials';
import { index as suppliersIndex } from '@/routes/admin/master/suppliers';
import { index as rolesIndex } from '@/routes/admin/settings/roles';
type NavMenuItem = { title: string; href: string; icon: LucideIcon };
type NavMenuItem = { title: string; href: string; icon: LucideIcon; permission?: string | string[] };
const dasborItem: NavMenuItem = {
title: 'Dasbor',
@ -72,48 +72,57 @@ const analisaItem: NavMenuItem = {
};
const masterItems: NavMenuItem[] = [
{ title: 'Kategori', href: categoriesIndex.url(), icon: Tags },
{ title: 'Produk', href: productsIndex.url(), icon: Package },
{ title: 'Bahan Baku', href: rawMaterialsIndex.url(), icon: Boxes },
{ title: 'Supplier', href: suppliersIndex.url(), icon: Truck },
{ title: 'Customer', href: customersIndex.url(), icon: Users },
{ title: 'Kategori', href: categoriesIndex.url(), icon: Tags, permission: 'categories.view' },
{ title: 'Produk', href: productsIndex.url(), icon: Package, permission: 'products.view' },
{ title: 'Bahan Baku', href: rawMaterialsIndex.url(), icon: Boxes, permission: 'raw_materials.view' },
{ title: 'Supplier', href: suppliersIndex.url(), icon: Truck, permission: 'suppliers.view' },
{ title: 'Customer', href: customersIndex.url(), icon: Users, permission: 'customers.view' },
];
const kelolaItems: NavMenuItem[] = [
{ title: 'Belanja', href: purchasesIndex.url(), icon: ShoppingCart },
{ title: 'Cutting', href: cuttingsIndex.url(), icon: Scissors },
{ title: 'Transaksi', href: transactionsIndex.url(), icon: ShoppingCart },
{ title: 'Restock', href: restocksIndex.url(), icon: RefreshCw },
{ title: 'Stok Opname', href: '#', icon: ClipboardCheck },
{ title: 'Belanja', href: purchasesIndex.url(), icon: ShoppingCart, permission: 'purchases.view' },
{ title: 'Cutting', href: cuttingsIndex.url(), icon: Scissors, permission: 'cuttings.view' },
{ title: 'Transaksi', href: transactionsIndex.url(), icon: ShoppingCart, permission: 'orders.view' },
{ title: 'Restock', href: restocksIndex.url(), icon: RefreshCw, permission: 'restocks.view' },
{ title: 'Stok Opname', href: '#', icon: ClipboardCheck, permission: 'stok_opnames.view' },
];
const keuanganItems: NavMenuItem[] = [
{ title: 'Kas Toko', href: cashAccountsIndex.url(), icon: Wallet },
{ title: 'Pengeluaran', href: expensesIndex.url(), icon: ArrowUpFromLine },
{ title: 'Kasbon', href: employeeAdvancesIndex.url(), icon: HandCoins },
{ title: 'Gaji', href: payrollCurrent.url(), icon: DollarSign },
{ title: 'Kas Toko', href: cashAccountsIndex.url(), icon: Wallet, permission: 'cash.view' },
{ title: 'Pengeluaran', href: expensesIndex.url(), icon: ArrowUpFromLine, permission: 'expenses.view' },
{ title: 'Kasbon', href: employeeAdvancesIndex.url(), icon: HandCoins, permission: 'employee_advances.view' },
{ title: 'Gaji', href: payrollCurrent.url(), icon: DollarSign, permission: 'payroll.view' },
];
const hrItems: NavMenuItem[] = [
{ title: 'Pegawai', href: employeesIndex.url(), icon: UserCircle },
{ title: 'Presensi', href: attendancesIndex.url(), icon: CalendarCheck },
{ title: 'Cuti', href: leaveRequestsIndex.url(), icon: CalendarDays },
{ title: 'Pegawai', href: employeesIndex.url(), icon: UserCircle, permission: 'employees.view' },
{ title: 'Presensi', href: attendancesIndex.url(), icon: CalendarCheck, permission: 'attendances.view' },
{ title: 'Cuti', href: leaveRequestsIndex.url(), icon: CalendarDays, permission: 'leave_requests.view' },
];
const sistemItems: NavMenuItem[] = [
{ title: 'Pengaturan', href: '/admin/settings', icon: Settings },
{ title: 'Role & Permission', href: rolesIndex.url(), icon: Shield },
{ title: 'Log Aktivitas', href: '#', icon: Activity },
{ title: 'Pengaturan', href: '/admin/settings', icon: Settings, permission: ['settings.view_system', 'settings.view_homepage', 'settings.view_social_media', 'settings.view_marketplace', 'settings.view_hr'] },
{ title: 'Role & Permission', href: rolesIndex.url(), icon: Shield, permission: 'roles.view' },
{ title: 'Log Aktivitas', href: '#', icon: Activity, permission: 'activity_logs.view' },
];
function MenuGroup({ label, items }: { label: string; items: NavMenuItem[] }) {
const { isCurrentUrl } = useCurrentUrl();
const { can, canAny } = useCan();
const filtered = items.filter((item) => {
if (!item.permission) return true;
if (Array.isArray(item.permission)) return canAny(...item.permission);
return can(item.permission);
});
if (filtered.length === 0) return null;
return (
<SidebarGroup>
<SidebarGroupLabel>{label}</SidebarGroupLabel>
<SidebarMenu>
{items.map((item) => (
{filtered.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton
asChild
@ -138,8 +147,8 @@ export function AppSidebar() {
React.useEffect(() => {
if (!isMobile) {
return;
}
return;
}
const cleanup = router.on('finish', () => {
setOpenMobile(false);

View File

@ -34,6 +34,12 @@ export function useCan() {
return permissionNames.includes(permission);
}
function canAny(...permissions: string[]): boolean {
if (!user) return false;
if (roleNames.includes('developer') || roleNames.includes('owner')) return true;
return permissions.some((p) => permissionNames.includes(p));
}
function hasRole(role: string): boolean {
if (!user) return false;
return roleNames.includes(role);
@ -44,5 +50,5 @@ export function useCan() {
return roles.some((role) => roleNames.includes(role));
}
return { can, hasRole, hasAnyRole };
return { can, canAny, hasRole, hasAnyRole };
}

View File

@ -38,93 +38,93 @@
Route::inertia('dashboard', 'dashboard')->name('dashboard');
Route::prefix('admin/master')->name('admin.master.')->group(function () {
Route::resource('categories', CategoryController::class)->except(['show', 'create', 'edit']);
Route::resource('categories', CategoryController::class)->except(['show', 'create', 'edit'])->middleware('permission:categories.view|categories.create|categories.update|categories.delete');
Route::resource('products', ProductController::class)->except(['show']);
Route::post('products/{product}/toggle-status', [ProductController::class, 'toggleStatus'])->name('products.toggle-status');
Route::delete('products/{product}/variants/{variant}', [ProductVariantController::class, 'destroy'])->name('products.variants.destroy');
Route::get('products/{product}/variants/{variant}/edit', [ProductVariantController::class, 'edit'])->name('products.variants.edit');
Route::put('products/{product}/variants/{variant}', [ProductVariantController::class, 'update'])->name('products.variants.update');
Route::post('products/{product}/variants/{variant}/transfer-stock', [ProductVariantController::class, 'transferStock'])->name('products.variants.transfer-stock');
Route::get('products/{product}/variants/{variant}/stock-mutations', [StockMutationController::class, 'index'])->name('products.variants.stock-mutations');
Route::resource('products', ProductController::class)->except(['show'])->middleware('permission:products.view|products.create|products.update|products.delete');
Route::post('products/{product}/toggle-status', [ProductController::class, 'toggleStatus'])->name('products.toggle-status')->middleware('permission:products.toggle_status');
Route::delete('products/{product}/variants/{variant}', [ProductVariantController::class, 'destroy'])->name('products.variants.destroy')->middleware('permission:products.delete');
Route::get('products/{product}/variants/{variant}/edit', [ProductVariantController::class, 'edit'])->name('products.variants.edit')->middleware('permission:products.update');
Route::put('products/{product}/variants/{variant}', [ProductVariantController::class, 'update'])->name('products.variants.update')->middleware('permission:products.update');
Route::post('products/{product}/variants/{variant}/transfer-stock', [ProductVariantController::class, 'transferStock'])->name('products.variants.transfer-stock')->middleware('permission:products.update');
Route::get('products/{product}/variants/{variant}/stock-mutations', [StockMutationController::class, 'index'])->name('products.variants.stock-mutations')->middleware('permission:products.view');
Route::resource('raw-materials', RawMaterialController::class)->except(['show']);
Route::post('raw-materials/{rawMaterial}/toggle-status', [RawMaterialController::class, 'toggleStatus'])->name('raw-materials.toggle-status');
Route::get('raw-materials/{rawMaterial}/variants/{variant}/edit', [RawMaterialVariantController::class, 'edit'])->name('raw-materials.variants.edit');
Route::put('raw-materials/{rawMaterial}/variants/{variant}', [RawMaterialVariantController::class, 'update'])->name('raw-materials.variants.update');
Route::delete('raw-materials/{rawMaterial}/variants/{variant}', [RawMaterialVariantController::class, 'destroy'])->name('raw-materials.variants.destroy');
Route::resource('suppliers', SupplierController::class)->except(['show', 'create', 'edit']);
Route::resource('customers', CustomerController::class)->except(['show', 'create', 'edit']);
Route::resource('raw-materials', RawMaterialController::class)->except(['show'])->middleware('permission:raw_materials.view|raw_materials.create|raw_materials.update|raw_materials.delete');
Route::post('raw-materials/{rawMaterial}/toggle-status', [RawMaterialController::class, 'toggleStatus'])->name('raw-materials.toggle-status')->middleware('permission:raw_materials.toggle_status');
Route::get('raw-materials/{rawMaterial}/variants/{variant}/edit', [RawMaterialVariantController::class, 'edit'])->name('raw-materials.variants.edit')->middleware('permission:raw_materials.update');
Route::put('raw-materials/{rawMaterial}/variants/{variant}', [RawMaterialVariantController::class, 'update'])->name('raw-materials.variants.update')->middleware('permission:raw_materials.update');
Route::delete('raw-materials/{rawMaterial}/variants/{variant}', [RawMaterialVariantController::class, 'destroy'])->name('raw-materials.variants.destroy')->middleware('permission:raw_materials.delete');
Route::resource('suppliers', SupplierController::class)->except(['show', 'create', 'edit'])->middleware('permission:suppliers.view|suppliers.create|suppliers.update|suppliers.delete');
Route::resource('customers', CustomerController::class)->except(['show', 'create', 'edit'])->middleware('permission:customers.view|customers.create|customers.update|customers.delete');
});
Route::prefix('admin/manage')->name('admin.manage.')->group(function () {
Route::resource('purchases', PurchaseController::class)->except(['show'])->middleware('permission:purchase.view|purchase.create|purchase.update|purchase.delete');
Route::resource('cuttings', CuttingController::class)->except(['show'])->middleware('permission:cutting.view|cutting.create|cutting.update|cutting.delete');
Route::resource('transactions', TransactionController::class)->except(['show'])->middleware('permission:transaction.view|transaction.create|transaction.update|transaction.delete');
Route::resource('restocks', RestockController::class)->except(['show'])->middleware('permission:restock.view|restock.create|restock.update|restock.delete');
Route::resource('purchases', PurchaseController::class)->except(['show'])->middleware('permission:purchases.view|purchases.create|purchases.update|purchases.delete');
Route::resource('cuttings', CuttingController::class)->except(['show'])->middleware('permission:cuttings.view|cuttings.create|cuttings.update|cuttings.delete');
Route::resource('transactions', TransactionController::class)->except(['show'])->middleware('permission:orders.view|orders.create|orders.update|orders.delete');
Route::resource('restocks', RestockController::class)->except(['show'])->middleware('permission:restocks.view|restocks.create|restocks.update|restocks.delete');
});
Route::prefix('admin/finance')->name('admin.finance.')->group(function () {
Route::get('cash-accounts', [CashAccountController::class, 'index'])->name('cash-accounts.index')->middleware('permission:cash-account.view');
Route::post('cash-accounts/deposit', [CashAccountController::class, 'deposit'])->name('cash-accounts.deposit')->middleware('permission:cash-account.deposit');
Route::post('cash-accounts/withdrawal', [CashAccountController::class, 'withdrawal'])->name('cash-accounts.withdrawal')->middleware('permission:cash-account.withdrawal');
Route::put('cash-accounts/transactions/{transaction}', [CashAccountController::class, 'update'])->name('cash-accounts.transactions.update')->middleware('permission:cash-account.update');
Route::delete('cash-accounts/transactions/{transaction}', [CashAccountController::class, 'destroy'])->name('cash-accounts.transactions.destroy')->middleware('permission:cash-account.delete');
Route::get('cash-accounts', [CashAccountController::class, 'index'])->name('cash-accounts.index')->middleware('permission:cash.view');
Route::post('cash-accounts/deposit', [CashAccountController::class, 'deposit'])->name('cash-accounts.deposit')->middleware('permission:cash.deposit');
Route::post('cash-accounts/withdrawal', [CashAccountController::class, 'withdrawal'])->name('cash-accounts.withdrawal')->middleware('permission:cash.withdraw');
Route::put('cash-accounts/transactions/{transaction}', [CashAccountController::class, 'update'])->name('cash-accounts.transactions.update')->middleware('permission:cash.update');
Route::delete('cash-accounts/transactions/{transaction}', [CashAccountController::class, 'destroy'])->name('cash-accounts.transactions.destroy')->middleware('permission:cash.delete');
Route::resource('expenses', ExpenseController::class)->except(['show', 'create', 'edit'])->middleware('permission:expense.view|expense.create|expense.update|expense.delete');
Route::resource('expenses', ExpenseController::class)->except(['show', 'create', 'edit'])->middleware('permission:expenses.view|expenses.create|expenses.update|expenses.delete');
Route::resource('employee-advances', EmployeeAdvanceController::class)->except(['show', 'create', 'edit'])->middleware('permission:employee-advance.view|employee-advance.create|employee-advance.update|employee-advance.delete');
Route::post('employee-advances/{employeeAdvance}/approve', [EmployeeAdvanceController::class, 'approve'])->name('employee-advances.approve')->middleware('permission:employee-advance.approve');
Route::post('employee-advances/{employeeAdvance}/pay', [EmployeeAdvanceController::class, 'pay'])->name('employee-advances.pay')->middleware('permission:employee-advance.pay');
Route::resource('employee-advances', EmployeeAdvanceController::class)->except(['show', 'create', 'edit'])->middleware('permission:employee_advances.view|employee_advances.create|employee_advances.update|employee_advances.delete');
Route::post('employee-advances/{employeeAdvance}/approve', [EmployeeAdvanceController::class, 'approve'])->name('employee-advances.approve')->middleware('permission:employee_advances.verify');
Route::post('employee-advances/{employeeAdvance}/pay', [EmployeeAdvanceController::class, 'pay'])->name('employee-advances.pay')->middleware('permission:employee_advances.pay');
Route::get('payroll-periods/current', [PayrollPeriodController::class, 'current'])->name('payroll-periods.current')->middleware('permission:payroll-period.current');
Route::resource('payroll-periods', PayrollPeriodController::class)->only(['index', 'show'])->middleware('permission:payroll-period.view');
Route::post('payroll-periods/{payrollPeriod}/close', [PayrollPeriodController::class, 'close'])->name('payroll-periods.close')->middleware('permission:payroll-period.close');
Route::post('payroll-periods/{payrollPeriod}/reopen', [PayrollPeriodController::class, 'reopen'])->name('payroll-periods.reopen')->middleware('permission:payroll-period.reopen');
Route::get('payroll-periods/current', [PayrollPeriodController::class, 'current'])->name('payroll-periods.current')->middleware('permission:payroll.view');
Route::resource('payroll-periods', PayrollPeriodController::class)->only(['index', 'show'])->middleware('permission:payroll.view');
Route::post('payroll-periods/{payrollPeriod}/close', [PayrollPeriodController::class, 'close'])->name('payroll-periods.close')->middleware('permission:payroll.view');
Route::post('payroll-periods/{payrollPeriod}/reopen', [PayrollPeriodController::class, 'reopen'])->name('payroll-periods.reopen')->middleware('permission:payroll.view');
Route::post('payrolls/{payroll}/pay', [PayrollController::class, 'pay'])->name('payrolls.pay')->middleware('permission:payroll.pay');
Route::post('payrolls/{payroll}/cancel', [PayrollController::class, 'cancel'])->name('payrolls.cancel')->middleware('permission:payroll.cancel');
Route::post('payrolls/{payroll}/adjustments', [PayrollAdjustmentController::class, 'store'])->name('payrolls.adjustments.store')->middleware('permission:payroll-adjustment.create');
Route::delete('payroll-adjustments/{payrollAdjustment}', [PayrollAdjustmentController::class, 'destroy'])->name('payroll-adjustments.destroy')->middleware('permission:payroll-adjustment.delete');
Route::post('payrolls/{payroll}/adjustments', [PayrollAdjustmentController::class, 'store'])->name('payrolls.adjustments.store')->middleware('permission:payroll.adjust');
Route::delete('payroll-adjustments/{payrollAdjustment}', [PayrollAdjustmentController::class, 'destroy'])->name('payroll-adjustments.destroy')->middleware('permission:payroll.adjust');
});
Route::prefix('admin/hr')->name('admin.hr.')->group(function () {
Route::resource('employees', EmployeeController::class)->except(['show'])->parameters(['employees' => 'user'])->middleware('permission:user.view|user.create|user.update|user.delete');
Route::post('employees/{user}/toggle-active', [EmployeeController::class, 'toggleActive'])->name('employees.toggle-active')->middleware('permission:user.toggle-active');
Route::post('employees/{user}/reset-password', [EmployeeController::class, 'resetPassword'])->name('employees.reset-password')->middleware('permission:user.reset-password');
Route::resource('employees', EmployeeController::class)->except(['show'])->parameters(['employees' => 'user'])->middleware('permission:employees.view|employees.create|employees.update|employees.delete');
Route::post('employees/{user}/toggle-active', [EmployeeController::class, 'toggleActive'])->name('employees.toggle-active')->middleware('permission:employees.toggle_status');
Route::post('employees/{user}/reset-password', [EmployeeController::class, 'resetPassword'])->name('employees.reset-password')->middleware('permission:employees.reset_password');
Route::get('attendances', [AttendanceController::class, 'index'])->name('attendances.index')->middleware('permission:attendance.view');
Route::post('attendances', [AttendanceController::class, 'store'])->name('attendances.store')->middleware('permission:attendance.check-in');
Route::put('attendances/{attendance}', [AttendanceController::class, 'update'])->name('attendances.update')->middleware('permission:attendance.check-out');
Route::get('attendances/by-date', [AttendanceController::class, 'byDate'])->name('attendances.by-date')->middleware('permission:attendance.by-date');
Route::get('attendances', [AttendanceController::class, 'index'])->name('attendances.index')->middleware('permission:attendances.view');
Route::post('attendances', [AttendanceController::class, 'store'])->name('attendances.store')->middleware('permission:attendances.manage');
Route::put('attendances/{attendance}', [AttendanceController::class, 'update'])->name('attendances.update')->middleware('permission:attendances.manage');
Route::get('attendances/by-date', [AttendanceController::class, 'byDate'])->name('attendances.by-date')->middleware('permission:attendances.view');
Route::resource('leave-requests', LeaveRequestController::class)->except(['show', 'create', 'edit'])->middleware('permission:leave-request.view|leave-request.create|leave-request.update|leave-request.delete');
Route::post('leave-requests/{leaveRequest}/approve', [LeaveRequestController::class, 'approve'])->name('leave-requests.approve')->middleware('permission:leave-request.approve');
Route::post('leave-requests/{leaveRequest}/reject', [LeaveRequestController::class, 'reject'])->name('leave-requests.reject')->middleware('permission:leave-request.reject');
Route::resource('leave-requests', LeaveRequestController::class)->except(['show', 'create', 'edit'])->middleware('permission:leave_requests.view|leave_requests.create|leave_requests.update|leave_requests.delete');
Route::post('leave-requests/{leaveRequest}/approve', [LeaveRequestController::class, 'approve'])->name('leave-requests.approve')->middleware('permission:leave_requests.verify');
Route::post('leave-requests/{leaveRequest}/reject', [LeaveRequestController::class, 'reject'])->name('leave-requests.reject')->middleware('permission:leave_requests.verify');
});
Route::prefix('admin/settings')->name('admin.settings.')->group(function () {
Route::get('/', [AdminSettingsController::class, 'index'])->name('index')->middleware('permission:settings.view');
Route::put('system', [AdminSettingsController::class, 'updateSystem'])->name('update-system')->middleware('permission:settings.update-system');
Route::put('homepage', [AdminSettingsController::class, 'updateHomepage'])->name('update-homepage')->middleware('permission:settings.update-homepage');
Route::put('social-media', [AdminSettingsController::class, 'updateSocialMedia'])->name('update-social-media')->middleware('permission:settings.update-social-media');
Route::put('marketplace', [AdminSettingsController::class, 'updateMarketplace'])->name('update-marketplace')->middleware('permission:settings.update-marketplace');
Route::put('hr', [AdminSettingsController::class, 'updateHR'])->name('update-hr')->middleware('permission:settings.update-hr');
Route::get('/', [AdminSettingsController::class, 'index'])->name('index')->middleware('permission:settings.view_system|settings.view_homepage|settings.view_social_media|settings.view_marketplace|settings.view_hr');
Route::put('system', [AdminSettingsController::class, 'updateSystem'])->name('update-system')->middleware('permission:settings.update_system');
Route::put('homepage', [AdminSettingsController::class, 'updateHomepage'])->name('update-homepage')->middleware('permission:settings.update_homepage');
Route::put('social-media', [AdminSettingsController::class, 'updateSocialMedia'])->name('update-social-media')->middleware('permission:settings.update_social_media');
Route::put('marketplace', [AdminSettingsController::class, 'updateMarketplace'])->name('update-marketplace')->middleware('permission:settings.update_marketplace');
Route::put('hr', [AdminSettingsController::class, 'updateHR'])->name('update-hr')->middleware('permission:settings.update_hr');
});
Route::prefix('admin/settings')->name('admin.settings.')->group(function () {
Route::get('roles', [RoleController::class, 'index'])->name('roles.index')->middleware('permission:user.view');
Route::get('roles/create', [RoleController::class, 'create'])->name('roles.create')->middleware('permission:user.create');
Route::post('roles', [RoleController::class, 'store'])->name('roles.store')->middleware('permission:user.create');
Route::get('roles/{role}/edit', [RoleController::class, 'edit'])->name('roles.edit')->middleware('permission:user.update');
Route::put('roles/{role}', [RoleController::class, 'update'])->name('roles.update')->middleware('permission:user.update');
Route::delete('roles/{role}', [RoleController::class, 'destroy'])->name('roles.destroy')->middleware('permission:user.delete');
Route::get('roles', [RoleController::class, 'index'])->name('roles.index')->middleware('permission:roles.view');
Route::get('roles/create', [RoleController::class, 'create'])->name('roles.create')->middleware('permission:roles.create');
Route::post('roles', [RoleController::class, 'store'])->name('roles.store')->middleware('permission:roles.create');
Route::get('roles/{role}/edit', [RoleController::class, 'edit'])->name('roles.edit')->middleware('permission:roles.update');
Route::put('roles/{role}', [RoleController::class, 'update'])->name('roles.update')->middleware('permission:roles.update');
Route::delete('roles/{role}', [RoleController::class, 'destroy'])->name('roles.destroy')->middleware('permission:roles.delete');
});
});