Restrict access to forbidden links and widgets based on roles

- Wrapped Admin/Management navigation links in @role and @can permission checks.
- Restricted dashboard 'Management/Ops' and 'Finance Application' widgets to authorized roles.
- Applied granular visibility control to 'To-do' buckets on the dashboard for Applicant, Cashier, Accountant, and Chair.
This commit is contained in:
2025-11-28 00:38:10 +08:00
parent ebf7f4b42d
commit b6be6578c4
2 changed files with 55 additions and 3 deletions

View File

@@ -11,9 +11,11 @@
<a href="{{ route('member.dashboard') }}" class="inline-flex items-center px-3 py-2 text-sm font-medium rounded-md bg-blue-50 text-blue-700 hover:bg-blue-100 border border-blue-200"> <a href="{{ route('member.dashboard') }}" class="inline-flex items-center px-3 py-2 text-sm font-medium rounded-md bg-blue-50 text-blue-700 hover:bg-blue-100 border border-blue-200">
我的會籍/繳費 我的會籍/繳費
</a> </a>
@can('create_finance_documents')
<a href="{{ route('admin.finance.create') }}" class="inline-flex items-center px-3 py-2 text-sm font-medium rounded-md bg-emerald-50 text-emerald-700 hover:bg-emerald-100 border border-emerald-200"> <a href="{{ route('admin.finance.create') }}" class="inline-flex items-center px-3 py-2 text-sm font-medium rounded-md bg-emerald-50 text-emerald-700 hover:bg-emerald-100 border border-emerald-200">
建立財務申請 建立財務申請
</a> </a>
@endcan
</div> </div>
</div> </div>
</x-slot> </x-slot>
@@ -40,6 +42,7 @@
</div> </div>
</div> </div>
@if(Auth::user()->can('create_finance_documents') || Auth::user()->can('view_finance_documents'))
<div class="bg-white shadow-sm sm:rounded-lg border border-gray-100 p-5"> <div class="bg-white shadow-sm sm:rounded-lg border border-gray-100 p-5">
<div class="flex items-start justify-between"> <div class="flex items-start justify-between">
<div> <div>
@@ -49,15 +52,21 @@
<span class="text-2xl">💼</span> <span class="text-2xl">💼</span>
</div> </div>
<div class="mt-4 flex gap-3"> <div class="mt-4 flex gap-3">
@can('create_finance_documents')
<a href="{{ route('admin.finance.create') }}" class="inline-flex items-center px-3 py-2 text-sm font-medium rounded-md bg-emerald-600 text-white hover:bg-emerald-700"> <a href="{{ route('admin.finance.create') }}" class="inline-flex items-center px-3 py-2 text-sm font-medium rounded-md bg-emerald-600 text-white hover:bg-emerald-700">
新增申請 新增申請
</a> </a>
@endcan
@can('view_finance_documents')
<a href="{{ route('admin.finance.index') }}" class="inline-flex items-center px-3 py-2 text-sm font-medium rounded-md border border-gray-200 text-gray-700 hover:bg-gray-50"> <a href="{{ route('admin.finance.index') }}" class="inline-flex items-center px-3 py-2 text-sm font-medium rounded-md border border-gray-200 text-gray-700 hover:bg-gray-50">
查看案件列表 查看案件列表
</a> </a>
@endcan
</div> </div>
</div> </div>
@endif
@if(Auth::user()->hasRole(['admin', 'membership_manager']) || Auth::user()->can('view_audit_logs'))
<div class="bg-white shadow-sm sm:rounded-lg border border-gray-100 p-5"> <div class="bg-white shadow-sm sm:rounded-lg border border-gray-100 p-5">
<div class="flex items-start justify-between"> <div class="flex items-start justify-between">
<div> <div>
@@ -67,28 +76,35 @@
<span class="text-2xl">🛡️</span> <span class="text-2xl">🛡️</span>
</div> </div>
<div class="mt-4 flex gap-3 flex-wrap"> <div class="mt-4 flex gap-3 flex-wrap">
@hasrole('admin|membership_manager')
<a href="{{ route('admin.members.index') }}" class="inline-flex items-center px-3 py-2 text-sm font-medium rounded-md border border-gray-200 text-gray-700 hover:bg-gray-50"> <a href="{{ route('admin.members.index') }}" class="inline-flex items-center px-3 py-2 text-sm font-medium rounded-md border border-gray-200 text-gray-700 hover:bg-gray-50">
會員管理 會員管理
</a> </a>
@endhasrole
@role('admin')
<a href="{{ route('admin.roles.index') }}" class="inline-flex items-center px-3 py-2 text-sm font-medium rounded-md border border-gray-200 text-gray-700 hover:bg-gray-50"> <a href="{{ route('admin.roles.index') }}" class="inline-flex items-center px-3 py-2 text-sm font-medium rounded-md border border-gray-200 text-gray-700 hover:bg-gray-50">
角色與權限 角色與權限
</a> </a>
<a href="{{ route('admin.audit.index') }}" class="inline-flex items-center px-3 py-2 text-sm font-medium rounded-md border border-gray-200 text-gray-700 hover:bg-gray-50"> <a href="{{ route('admin.audit.index') }}" class="inline-flex items-center px-3 py-2 text-sm font-medium rounded-md border border-gray-200 text-gray-700 hover:bg-gray-50">
審計日誌 審計日誌
</a> </a>
@endrole
</div> </div>
</div> </div>
@endif
</div> </div>
<!-- To-do by role (all roles see the buckets) --> <!-- To-do by role (all roles see the buckets) -->
@if(Auth::user()->hasRole(['admin', 'finance_cashier', 'payment_cashier', 'finance_accountant', 'payment_accountant', 'finance_chair', 'payment_chair']) || Auth::user()->can('create_finance_documents'))
<div class="bg-white shadow-sm sm:rounded-lg border border-gray-100 p-6"> <div class="bg-white shadow-sm sm:rounded-lg border border-gray-100 p-6">
<div class="flex items-center justify-between mb-4"> <div class="flex items-center justify-between mb-4">
<div> <div>
<h3 class="text-lg font-semibold text-gray-900">待辦總覽(所有角色可見)</h3> <h3 class="text-lg font-semibold text-gray-900">待辦總覽</h3>
<p class="text-sm text-gray-500">依職責挑選你需要處理的事項。</p> <p class="text-sm text-gray-500">依職責挑選你需要處理的事項。</p>
</div> </div>
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4"> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
@can('create_finance_documents')
<div class="p-4 rounded-lg border border-gray-100 bg-slate-50"> <div class="p-4 rounded-lg border border-gray-100 bg-slate-50">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<h4 class="text-sm font-semibold text-gray-800">申請人 / 會員</h4><span>📝</span> <h4 class="text-sm font-semibold text-gray-800">申請人 / 會員</h4><span>📝</span>
@@ -99,6 +115,8 @@
<li><a class="hover:text-blue-600" href="{{ route('member.dashboard') }}">查看會籍與繳費紀錄</a></li> <li><a class="hover:text-blue-600" href="{{ route('member.dashboard') }}">查看會籍與繳費紀錄</a></li>
</ul> </ul>
</div> </div>
@endcan
@hasrole('finance_cashier|payment_cashier|admin')
<div class="p-4 rounded-lg border border-gray-100 bg-slate-50"> <div class="p-4 rounded-lg border border-gray-100 bg-slate-50">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<h4 class="text-sm font-semibold text-gray-800">出納</h4><span>💰</span> <h4 class="text-sm font-semibold text-gray-800">出納</h4><span>💰</span>
@@ -109,6 +127,8 @@
<li><a class="hover:text-blue-600" href="{{ route('admin.cashier-ledger.index') }}">填寫現金簿/匯出報表</a></li> <li><a class="hover:text-blue-600" href="{{ route('admin.cashier-ledger.index') }}">填寫現金簿/匯出報表</a></li>
</ul> </ul>
</div> </div>
@endhasrole
@hasrole('finance_accountant|payment_accountant|admin')
<div class="p-4 rounded-lg border border-gray-100 bg-slate-50"> <div class="p-4 rounded-lg border border-gray-100 bg-slate-50">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<h4 class="text-sm font-semibold text-gray-800">會計</h4><span>📊</span> <h4 class="text-sm font-semibold text-gray-800">會計</h4><span>📊</span>
@@ -119,6 +139,8 @@
<li><a class="hover:text-blue-600" href="{{ route('admin.transactions.index') }}">建立交易分錄</a></li> <li><a class="hover:text-blue-600" href="{{ route('admin.transactions.index') }}">建立交易分錄</a></li>
</ul> </ul>
</div> </div>
@endhasrole
@hasrole('finance_chair|payment_chair|admin')
<div class="p-4 rounded-lg border border-gray-100 bg-slate-50"> <div class="p-4 rounded-lg border border-gray-100 bg-slate-50">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<h4 class="text-sm font-semibold text-gray-800">理事長/理事</h4><span></span> <h4 class="text-sm font-semibold text-gray-800">理事長/理事</h4><span></span>
@@ -129,8 +151,10 @@
<li><a class="hover:text-blue-600" href="{{ route('admin.roles.index') }}">角色/權限檢視</a></li> <li><a class="hover:text-blue-600" href="{{ route('admin.roles.index') }}">角色/權限檢視</a></li>
</ul> </ul>
</div> </div>
@endhasrole
</div> </div>
</div> </div>
@endif
<!-- Issues / Documents --> <!-- Issues / Documents -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4"> <div class="grid grid-cols-1 lg:grid-cols-2 gap-4">

View File

@@ -24,7 +24,7 @@
{{ __('Documents') }} {{ __('Documents') }}
</x-nav-link> </x-nav-link>
@if(Auth::user()) @if(Auth::user() && (Auth::user()->hasRole(['admin', 'membership_manager', 'finance_accountant', 'staff']) || Auth::user()->canAny(['view_finance_documents', 'view_accounting_transactions', 'manage_system_settings'])))
<div class="hidden sm:flex sm:items-center"> <div class="hidden sm:flex sm:items-center">
<x-dropdown align="right" width="48"> <x-dropdown align="right" width="48">
<x-slot name="trigger"> <x-slot name="trigger">
@@ -40,33 +40,47 @@
</x-slot> </x-slot>
<x-slot name="content"> <x-slot name="content">
@hasrole('admin|membership_manager')
<x-dropdown-link :href="route('admin.members.index')"> <x-dropdown-link :href="route('admin.members.index')">
{{ __('Admin: Members') }} {{ __('Admin: Members') }}
</x-dropdown-link> </x-dropdown-link>
@endhasrole
@role('admin')
<x-dropdown-link :href="route('admin.roles.index')"> <x-dropdown-link :href="route('admin.roles.index')">
{{ __('Admin: Roles') }} {{ __('Admin: Roles') }}
</x-dropdown-link> </x-dropdown-link>
@endrole
@can('view_finance_documents')
<x-dropdown-link :href="route('admin.finance.index')"> <x-dropdown-link :href="route('admin.finance.index')">
{{ __('Admin: Finance') }} {{ __('Admin: Finance') }}
</x-dropdown-link> </x-dropdown-link>
@endcan
@hasrole('admin|finance_accountant')
<x-dropdown-link :href="route('admin.budgets.index')"> <x-dropdown-link :href="route('admin.budgets.index')">
{{ __('Admin: Budgets') }} {{ __('Admin: Budgets') }}
</x-dropdown-link> </x-dropdown-link>
@endhasrole
@can('view_accounting_transactions')
<x-dropdown-link :href="route('admin.transactions.index')"> <x-dropdown-link :href="route('admin.transactions.index')">
{{ __('Admin: Transactions') }} {{ __('Admin: Transactions') }}
</x-dropdown-link> </x-dropdown-link>
@endcan
<x-dropdown-link :href="route('admin.issues.index')"> <x-dropdown-link :href="route('admin.issues.index')">
{{ __('Admin: Issues') }} {{ __('Admin: Issues') }}
</x-dropdown-link> </x-dropdown-link>
@role('admin')
<x-dropdown-link :href="route('admin.audit.index')"> <x-dropdown-link :href="route('admin.audit.index')">
{{ __('Admin: Audit Logs') }} {{ __('Admin: Audit Logs') }}
</x-dropdown-link> </x-dropdown-link>
<x-dropdown-link :href="route('admin.document-categories.index')"> <x-dropdown-link :href="route('admin.document-categories.index')">
{{ __('Admin: Document Categories') }} {{ __('Admin: Document Categories') }}
</x-dropdown-link> </x-dropdown-link>
@endrole
@hasrole('admin|staff')
<x-dropdown-link :href="route('admin.documents.index')"> <x-dropdown-link :href="route('admin.documents.index')">
{{ __('Admin: Documents') }} {{ __('Admin: Documents') }}
</x-dropdown-link> </x-dropdown-link>
@endhasrole
@can('manage_system_settings') @can('manage_system_settings')
<x-dropdown-link :href="route('admin.settings.general')"> <x-dropdown-link :href="route('admin.settings.general')">
{{ __('Admin: System Settings') }} {{ __('Admin: System Settings') }}
@@ -156,39 +170,53 @@
{{ __('Documents') }} {{ __('Documents') }}
</x-responsive-nav-link> </x-responsive-nav-link>
@if(Auth::user()) @if(Auth::user() && (Auth::user()->hasRole(['admin', 'membership_manager', 'finance_accountant', 'staff']) || Auth::user()->canAny(['view_finance_documents', 'view_accounting_transactions', 'manage_system_settings'])))
<div class="pt-2 pb-1 border-t border-gray-200 dark:border-gray-700 mt-2"> <div class="pt-2 pb-1 border-t border-gray-200 dark:border-gray-700 mt-2">
<div class="px-4 text-xs font-semibold text-gray-500 uppercase tracking-wider"> <div class="px-4 text-xs font-semibold text-gray-500 uppercase tracking-wider">
{{ __('Management') }} {{ __('Management') }}
</div> </div>
</div> </div>
@hasrole('admin|membership_manager')
<x-responsive-nav-link :href="route('admin.members.index')" :active="request()->routeIs('admin.members.*')"> <x-responsive-nav-link :href="route('admin.members.index')" :active="request()->routeIs('admin.members.*')">
{{ __('Admin: Members') }} {{ __('Admin: Members') }}
</x-responsive-nav-link> </x-responsive-nav-link>
@endhasrole
@role('admin')
<x-responsive-nav-link :href="route('admin.roles.index')" :active="request()->routeIs('admin.roles.*')"> <x-responsive-nav-link :href="route('admin.roles.index')" :active="request()->routeIs('admin.roles.*')">
{{ __('Admin: Roles') }} {{ __('Admin: Roles') }}
</x-responsive-nav-link> </x-responsive-nav-link>
@endrole
@can('view_finance_documents')
<x-responsive-nav-link :href="route('admin.finance.index')" :active="request()->routeIs('admin.finance.*')"> <x-responsive-nav-link :href="route('admin.finance.index')" :active="request()->routeIs('admin.finance.*')">
{{ __('Admin: Finance') }} {{ __('Admin: Finance') }}
</x-responsive-nav-link> </x-responsive-nav-link>
@endcan
@hasrole('admin|finance_accountant')
<x-responsive-nav-link :href="route('admin.budgets.index')" :active="request()->routeIs('admin.budgets.*')"> <x-responsive-nav-link :href="route('admin.budgets.index')" :active="request()->routeIs('admin.budgets.*')">
{{ __('Admin: Budgets') }} {{ __('Admin: Budgets') }}
</x-responsive-nav-link> </x-responsive-nav-link>
@endhasrole
@can('view_accounting_transactions')
<x-responsive-nav-link :href="route('admin.transactions.index')" :active="request()->routeIs('admin.transactions.*')"> <x-responsive-nav-link :href="route('admin.transactions.index')" :active="request()->routeIs('admin.transactions.*')">
{{ __('Admin: Transactions') }} {{ __('Admin: Transactions') }}
</x-responsive-nav-link> </x-responsive-nav-link>
@endcan
<x-responsive-nav-link :href="route('admin.issues.index')" :active="request()->routeIs('admin.issues.*')"> <x-responsive-nav-link :href="route('admin.issues.index')" :active="request()->routeIs('admin.issues.*')">
{{ __('Admin: Issues') }} {{ __('Admin: Issues') }}
</x-responsive-nav-link> </x-responsive-nav-link>
@role('admin')
<x-responsive-nav-link :href="route('admin.audit.index')" :active="request()->routeIs('admin.audit.*')"> <x-responsive-nav-link :href="route('admin.audit.index')" :active="request()->routeIs('admin.audit.*')">
{{ __('Admin: Audit Logs') }} {{ __('Admin: Audit Logs') }}
</x-responsive-nav-link> </x-responsive-nav-link>
<x-responsive-nav-link :href="route('admin.document-categories.index')" :active="request()->routeIs('admin.document-categories.*')"> <x-responsive-nav-link :href="route('admin.document-categories.index')" :active="request()->routeIs('admin.document-categories.*')">
{{ __('Admin: Document Categories') }} {{ __('Admin: Document Categories') }}
</x-responsive-nav-link> </x-responsive-nav-link>
@endrole
@hasrole('admin|staff')
<x-responsive-nav-link :href="route('admin.documents.index')" :active="request()->routeIs('admin.documents.*')"> <x-responsive-nav-link :href="route('admin.documents.index')" :active="request()->routeIs('admin.documents.*')">
{{ __('Admin: Documents') }} {{ __('Admin: Documents') }}
</x-responsive-nav-link> </x-responsive-nav-link>
@endhasrole
@can('manage_system_settings') @can('manage_system_settings')
<x-responsive-nav-link :href="route('admin.settings.general')" :active="request()->routeIs('admin.settings.*')"> <x-responsive-nav-link :href="route('admin.settings.general')" :active="request()->routeIs('admin.settings.*')">
{{ __('Admin: System Settings') }} {{ __('Admin: System Settings') }}