Initial commit

This commit is contained in:
2025-11-20 23:21:05 +08:00
commit 13bc6db529
378 changed files with 54527 additions and 0 deletions

View File

@@ -0,0 +1,149 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
{{ __('Audit Logs') }}
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8">
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6 space-y-6">
<form method="GET" action="{{ route('admin.audit.index') }}" class="space-y-4" role="search" aria-label="{{ __('Filter audit logs') }}">
<div class="grid gap-4 sm:grid-cols-2">
<div>
<label for="search" class="block text-sm font-medium text-gray-700">
{{ __('Search actions or metadata') }}
</label>
<input
type="text"
id="search"
name="search"
value="{{ $search }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
>
</div>
<div>
<label for="action" class="block text-sm font-medium text-gray-700">
{{ __('Action') }}
</label>
<select
id="action"
name="action"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
>
<option value="">{{ __('All') }}</option>
@foreach ($actions as $actionOption)
<option value="{{ $actionOption }}" @selected($actionFilter === $actionOption)>{{ $actionOption }}</option>
@endforeach
</select>
</div>
<div>
<label for="user_id" class="block text-sm font-medium text-gray-700">
{{ __('User') }}
</label>
<select
id="user_id"
name="user_id"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
>
<option value="">{{ __('All') }}</option>
@foreach ($users as $user)
<option value="{{ $user->id }}" @selected($userFilter == $user->id)>{{ $user->name }} ({{ $user->email }})</option>
@endforeach
</select>
</div>
<div class="grid sm:grid-cols-2 gap-4">
<div>
<label for="start_date" class="block text-sm font-medium text-gray-700">
{{ __('Start date') }}
</label>
<input
type="date"
id="start_date"
name="start_date"
value="{{ $startDate }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
>
</div>
<div>
<label for="end_date" class="block text-sm font-medium text-gray-700">
{{ __('End date') }}
</label>
<input
type="date"
id="end_date"
name="end_date"
value="{{ $endDate }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
>
</div>
</div>
</div>
<div class="flex flex-wrap gap-2">
<button type="submit" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
{{ __('Apply filters') }}
</button>
<a href="{{ route('admin.audit.export', request()->only('search','action','user_id','start_date','end_date')) }}" class="inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
{{ __('Export CSV') }}
</a>
</div>
</form>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200" role="table">
<thead class="bg-gray-50">
<tr>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
{{ __('Time') }}
</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
{{ __('User') }}
</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
{{ __('Action') }}
</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
{{ __('Metadata') }}
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
@forelse ($logs as $log)
<tr>
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900">
{{ $log->created_at->toDateTimeString() }}
</td>
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900">
{{ $log->user?->name ?? __('System') }}
</td>
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900">
{{ $log->action }}
</td>
<td class="px-4 py-3 text-sm text-gray-900">
<pre class="whitespace-pre-wrap break-all text-xs bg-gray-50 p-2 rounded">{{ json_encode($log->metadata, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) }}</pre>
</td>
</tr>
@empty
<tr>
<td colspan="4" class="px-4 py-4 text-sm text-gray-500">
{{ __('No audit logs found.') }}
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div>
{{ $logs->links() }}
</div>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,287 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
製作銀行調節表
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-5xl sm:px-6 lg:px-8 space-y-4">
@if (session('error'))
<div class="rounded-md bg-red-50 p-4">
<p class="text-sm font-medium text-red-800">{{ session('error') }}</p>
</div>
@endif
<!-- Help Info -->
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4">
<div class="flex">
<div class="flex-shrink-0">
<svg class="h-5 w-5 text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div class="ml-3">
<h3 class="text-sm font-medium text-blue-800">銀行調節表說明</h3>
<div class="mt-2 text-sm text-blue-700">
<p>銀行調節表用於核對銀行對帳單餘額與內部現金簿餘額的差異。請準備好:</p>
<ul class="list-disc pl-5 mt-2 space-y-1">
<li>銀行對帳單 (PDF/圖片檔)</li>
<li>當月的現金簿記錄</li>
<li>未兌現支票清單</li>
<li>在途存款記錄</li>
</ul>
</div>
</div>
</div>
</div>
<form method="POST" action="{{ route('admin.bank-reconciliations.store') }}" enctype="multipart/form-data" class="space-y-4">
@csrf
<!-- Basic Information -->
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium leading-6 text-gray-900 mb-4">基本資訊</h3>
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2">
<!-- Reconciliation Month -->
<div>
<label for="reconciliation_month" class="block text-sm font-medium text-gray-700">
調節月份 <span class="text-red-500">*</span>
</label>
<input type="month" name="reconciliation_month" id="reconciliation_month" required
value="{{ old('reconciliation_month', $month) }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm @error('reconciliation_month') border-red-300 @enderror">
@error('reconciliation_month')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Bank Statement Date -->
<div>
<label for="bank_statement_date" class="block text-sm font-medium text-gray-700">
對帳單日期 <span class="text-red-500">*</span>
</label>
<input type="date" name="bank_statement_date" id="bank_statement_date" required
value="{{ old('bank_statement_date') }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm @error('bank_statement_date') border-red-300 @enderror">
@error('bank_statement_date')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Bank Statement Balance -->
<div>
<label for="bank_statement_balance" class="block text-sm font-medium text-gray-700">
銀行對帳單餘額 <span class="text-red-500">*</span>
</label>
<div class="relative mt-1 rounded-md shadow-sm">
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
<span class="text-gray-500 sm:text-sm">NT$</span>
</div>
<input type="number" name="bank_statement_balance" id="bank_statement_balance" step="0.01" required
value="{{ old('bank_statement_balance') }}"
class="block w-full rounded-md border-gray-300 pl-12 focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm @error('bank_statement_balance') border-red-300 @enderror">
</div>
@error('bank_statement_balance')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- System Book Balance -->
<div>
<label for="system_book_balance" class="block text-sm font-medium text-gray-700">
系統帳面餘額 <span class="text-red-500">*</span>
</label>
<div class="relative mt-1 rounded-md shadow-sm">
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
<span class="text-gray-500 sm:text-sm">NT$</span>
</div>
<input type="number" name="system_book_balance" id="system_book_balance" step="0.01" required
value="{{ old('system_book_balance', $systemBalance) }}"
class="block w-full rounded-md border-gray-300 pl-12 focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm @error('system_book_balance') border-red-300 @enderror">
</div>
<p class="mt-1 text-xs text-gray-500">從現金簿自動帶入: NT$ {{ number_format($systemBalance, 2) }}</p>
@error('system_book_balance')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Bank Statement File -->
<div class="sm:col-span-2">
<label for="bank_statement_file" class="block text-sm font-medium text-gray-700">
銀行對帳單檔案
</label>
<input type="file" name="bank_statement_file" id="bank_statement_file" accept=".pdf,.jpg,.jpeg,.png"
class="mt-1 block w-full text-sm text-gray-500
file:mr-4 file:py-2 file:px-4
file:rounded-md file:border-0
file:text-sm file:font-semibold
file:bg-indigo-50 file:text-indigo-700
hover:file:bg-indigo-100">
<p class="mt-1 text-xs text-gray-500">支援格式: PDF, JPG, PNG (最大 10MB)</p>
@error('bank_statement_file')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
</div>
</div>
</div>
<!-- Outstanding Checks -->
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium leading-6 text-gray-900 mb-4">未兌現支票</h3>
<div id="outstanding-checks-container" class="space-y-3">
<!-- Template will be added by JavaScript -->
</div>
<button type="button" onclick="addOutstandingCheck()" class="mt-3 inline-flex items-center rounded-md border border-gray-300 bg-white px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50">
<svg class="-ml-1 mr-2 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
</svg>
新增支票
</button>
</div>
</div>
<!-- Deposits in Transit -->
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium leading-6 text-gray-900 mb-4">在途存款</h3>
<div id="deposits-container" class="space-y-3">
<!-- Template will be added by JavaScript -->
</div>
<button type="button" onclick="addDeposit()" class="mt-3 inline-flex items-center rounded-md border border-gray-300 bg-white px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50">
<svg class="-ml-1 mr-2 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
</svg>
新增存款
</button>
</div>
</div>
<!-- Bank Charges -->
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium leading-6 text-gray-900 mb-4">銀行手續費</h3>
<div id="charges-container" class="space-y-3">
<!-- Template will be added by JavaScript -->
</div>
<button type="button" onclick="addCharge()" class="mt-3 inline-flex items-center rounded-md border border-gray-300 bg-white px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50">
<svg class="-ml-1 mr-2 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
</svg>
新增手續費
</button>
</div>
</div>
<!-- Notes -->
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<label for="notes" class="block text-sm font-medium text-gray-700">備註</label>
<textarea name="notes" id="notes" rows="3"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">{{ old('notes') }}</textarea>
</div>
</div>
<!-- Form Actions -->
<div class="flex justify-end space-x-3">
<a href="{{ route('admin.bank-reconciliations.index') }}" class="inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
取消
</a>
<button type="submit" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
製作調節表
</button>
</div>
</form>
</div>
</div>
@push('scripts')
<script>
let checkIndex = 0;
let depositIndex = 0;
let chargeIndex = 0;
function addOutstandingCheck() {
const container = document.getElementById('outstanding-checks-container');
const div = document.createElement('div');
div.className = 'grid grid-cols-1 gap-4 sm:grid-cols-3 p-4 border border-gray-200 rounded-md';
div.innerHTML = `
<div>
<label class="block text-sm font-medium text-gray-700">支票號碼</label>
<input type="text" name="outstanding_checks[${checkIndex}][check_number]"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
</div>
<div>
<label class="block text-sm font-medium text-gray-700">金額 <span class="text-red-500">*</span></label>
<input type="number" name="outstanding_checks[${checkIndex}][amount]" step="0.01" min="0" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
</div>
<div>
<label class="block text-sm font-medium text-gray-700">說明</label>
<input type="text" name="outstanding_checks[${checkIndex}][description]"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
</div>
`;
container.appendChild(div);
checkIndex++;
}
function addDeposit() {
const container = document.getElementById('deposits-container');
const div = document.createElement('div');
div.className = 'grid grid-cols-1 gap-4 sm:grid-cols-3 p-4 border border-gray-200 rounded-md';
div.innerHTML = `
<div>
<label class="block text-sm font-medium text-gray-700">存款日期</label>
<input type="date" name="deposits_in_transit[${depositIndex}][date]"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
</div>
<div>
<label class="block text-sm font-medium text-gray-700">金額 <span class="text-red-500">*</span></label>
<input type="number" name="deposits_in_transit[${depositIndex}][amount]" step="0.01" min="0" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
</div>
<div>
<label class="block text-sm font-medium text-gray-700">說明</label>
<input type="text" name="deposits_in_transit[${depositIndex}][description]"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
</div>
`;
container.appendChild(div);
depositIndex++;
}
function addCharge() {
const container = document.getElementById('charges-container');
const div = document.createElement('div');
div.className = 'grid grid-cols-1 gap-4 sm:grid-cols-2 p-4 border border-gray-200 rounded-md';
div.innerHTML = `
<div>
<label class="block text-sm font-medium text-gray-700">金額 <span class="text-red-500">*</span></label>
<input type="number" name="bank_charges[${chargeIndex}][amount]" step="0.01" min="0" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
</div>
<div>
<label class="block text-sm font-medium text-gray-700">說明</label>
<input type="text" name="bank_charges[${chargeIndex}][description]"
placeholder="例如: 轉帳手續費"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
</div>
`;
container.appendChild(div);
chargeIndex++;
}
// Add one of each by default
window.addEventListener('load', function() {
addOutstandingCheck();
addDeposit();
addCharge();
});
</script>
@endpush
</x-app-layout>

View File

@@ -0,0 +1,165 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
銀行調節表
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8 space-y-4">
@if (session('status'))
<div class="rounded-md bg-green-50 p-4">
<p class="text-sm font-medium text-green-800">{{ session('status') }}</p>
</div>
@endif
@if (session('error'))
<div class="rounded-md bg-red-50 p-4">
<p class="text-sm font-medium text-red-800">{{ session('error') }}</p>
</div>
@endif
<!-- Action Button -->
@can('prepare_bank_reconciliation')
<div class="flex justify-end">
<a href="{{ route('admin.bank-reconciliations.create') }}" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
<svg class="-ml-1 mr-2 h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
</svg>
製作調節表
</a>
</div>
@endcan
<!-- Filters -->
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<form method="GET" action="{{ route('admin.bank-reconciliations.index') }}" class="grid grid-cols-1 gap-4 sm:grid-cols-3">
<div>
<label for="reconciliation_status" class="block text-sm font-medium text-gray-700">狀態</label>
<select name="reconciliation_status" id="reconciliation_status" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
<option value="">全部</option>
<option value="pending" {{ request('reconciliation_status') == 'pending' ? 'selected' : '' }}>待覆核</option>
<option value="completed" {{ request('reconciliation_status') == 'completed' ? 'selected' : '' }}>已完成</option>
<option value="discrepancy" {{ request('reconciliation_status') == 'discrepancy' ? 'selected' : '' }}>有差異</option>
</select>
</div>
<div>
<label for="month" class="block text-sm font-medium text-gray-700">調節月份</label>
<input type="month" name="month" id="month" value="{{ request('month') }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
</div>
<div class="flex items-end">
<button type="submit" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
篩選
</button>
<a href="{{ route('admin.bank-reconciliations.index') }}" class="ml-2 inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
清除
</a>
</div>
</form>
</div>
</div>
<!-- Reconciliations Table -->
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
調節月份
</th>
<th scope="col" class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500">
銀行餘額
</th>
<th scope="col" class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500">
帳面餘額
</th>
<th scope="col" class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500">
差異金額
</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
狀態
</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
製表人
</th>
<th scope="col" class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500">
<span class="sr-only">操作</span>
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
@forelse ($reconciliations as $reconciliation)
<tr>
<td class="whitespace-nowrap px-4 py-4 text-sm font-medium text-gray-900">
{{ $reconciliation->reconciliation_month->format('Y年m月') }}
</td>
<td class="whitespace-nowrap px-4 py-4 text-sm text-right text-gray-500">
NT$ {{ number_format($reconciliation->bank_statement_balance, 2) }}
</td>
<td class="whitespace-nowrap px-4 py-4 text-sm text-right text-gray-500">
NT$ {{ number_format($reconciliation->system_book_balance, 2) }}
</td>
<td class="whitespace-nowrap px-4 py-4 text-sm text-right {{ $reconciliation->discrepancy_amount > 0 ? 'text-red-600 font-semibold' : 'text-gray-500' }}">
NT$ {{ number_format($reconciliation->discrepancy_amount, 2) }}
</td>
<td class="whitespace-nowrap px-4 py-4 text-sm">
<span class="inline-flex rounded-full px-2 text-xs font-semibold leading-5
@if($reconciliation->reconciliation_status === 'completed') bg-green-100 text-green-800
@elseif($reconciliation->reconciliation_status === 'discrepancy') bg-red-100 text-red-800
@else bg-yellow-100 text-yellow-800
@endif">
{{ $reconciliation->getStatusText() }}
</span>
</td>
<td class="whitespace-nowrap px-4 py-4 text-sm text-gray-500">
{{ $reconciliation->preparedByCashier->name ?? 'N/A' }}
</td>
<td class="whitespace-nowrap px-4 py-4 text-right text-sm font-medium">
<a href="{{ route('admin.bank-reconciliations.show', $reconciliation) }}" class="text-indigo-600 hover:text-indigo-900">
查看
</a>
</td>
</tr>
@empty
<tr>
<td colspan="7" class="px-4 py-8 text-center text-sm text-gray-500">
沒有銀行調節表記錄
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div class="mt-4">
{{ $reconciliations->links() }}
</div>
</div>
</div>
<!-- Help Info -->
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4">
<div class="flex">
<div class="flex-shrink-0">
<svg class="h-5 w-5 text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div class="ml-3">
<h3 class="text-sm font-medium text-blue-800">關於銀行調節表</h3>
<div class="mt-2 text-sm text-blue-700">
<p>銀行調節表用於核對銀行對帳單與內部現金簿的差異。建議每月定期製作,以確保帳務正確。</p>
<p class="mt-1">調節流程:出納製作 會計覆核 主管核准</p>
</div>
</div>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,482 @@
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>銀行調節表 - {{ $reconciliation->reconciliation_month->format('Y年m月') }}</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: "Microsoft JhengHei", "PingFang TC", sans-serif;
font-size: 12pt;
line-height: 1.6;
color: #333;
padding: 2cm;
print-color-adjust: exact;
-webkit-print-color-adjust: exact;
}
.header {
text-align: center;
margin-bottom: 30px;
border-bottom: 3px solid #333;
padding-bottom: 20px;
}
.header h1 {
font-size: 24pt;
font-weight: bold;
margin-bottom: 10px;
}
.header .subtitle {
font-size: 14pt;
color: #666;
}
.info-section {
margin-bottom: 25px;
}
.info-section h2 {
font-size: 16pt;
font-weight: bold;
margin-bottom: 15px;
padding-bottom: 5px;
border-bottom: 2px solid #666;
}
.info-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
margin-bottom: 15px;
}
.info-item {
padding: 10px;
background-color: #f9f9f9;
border-left: 3px solid #4f46e5;
}
.info-item dt {
font-size: 10pt;
color: #666;
margin-bottom: 5px;
}
.info-item dd {
font-size: 12pt;
font-weight: bold;
}
.balance-summary {
margin: 25px 0;
padding: 20px;
background-color: #f0f4ff;
border: 2px solid #4f46e5;
border-radius: 8px;
}
.balance-row {
display: flex;
justify-content: space-between;
padding: 8px 0;
border-bottom: 1px solid #ddd;
}
.balance-row:last-child {
border-bottom: none;
font-weight: bold;
font-size: 14pt;
}
.balance-row.highlight {
background-color: #fff;
padding: 12px;
margin-top: 10px;
border: 2px solid #333;
}
table {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
}
table thead {
background-color: #4f46e5;
color: white;
}
table th {
padding: 10px;
text-align: left;
font-weight: bold;
font-size: 11pt;
}
table th.text-right {
text-align: right;
}
table tbody tr {
border-bottom: 1px solid #ddd;
}
table tbody tr:nth-child(even) {
background-color: #f9f9f9;
}
table td {
padding: 8px 10px;
font-size: 11pt;
}
table td.text-right {
text-align: right;
}
table td.font-mono {
font-family: "Courier New", monospace;
}
table tfoot {
background-color: #f0f0f0;
font-weight: bold;
}
table tfoot td {
padding: 12px 10px;
font-size: 12pt;
}
.status-badge {
display: inline-block;
padding: 5px 12px;
border-radius: 12px;
font-size: 10pt;
font-weight: bold;
}
.status-completed {
background-color: #d1fae5;
color: #065f46;
}
.status-discrepancy {
background-color: #fee2e2;
color: #991b1b;
}
.status-pending {
background-color: #fef3c7;
color: #92400e;
}
.amount-positive {
color: #059669;
}
.amount-negative {
color: #dc2626;
}
.signatures {
margin-top: 40px;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 20px;
}
.signature-box {
border: 1px solid #999;
padding: 15px;
text-align: center;
min-height: 100px;
}
.signature-box .title {
font-weight: bold;
margin-bottom: 10px;
font-size: 11pt;
}
.signature-box .name {
margin-top: 20px;
border-top: 1px solid #999;
padding-top: 10px;
font-size: 10pt;
}
.signature-box .date {
margin-top: 5px;
font-size: 9pt;
color: #666;
}
.footer {
margin-top: 40px;
padding-top: 20px;
border-top: 1px solid #ddd;
text-align: center;
font-size: 9pt;
color: #666;
}
.warning-box {
background-color: #fef2f2;
border-left: 4px solid #dc2626;
padding: 15px;
margin-bottom: 20px;
}
.warning-box .title {
font-weight: bold;
color: #991b1b;
margin-bottom: 8px;
}
.warning-box .content {
color: #7f1d1d;
font-size: 11pt;
}
@media print {
body {
padding: 1cm;
}
.page-break {
page-break-after: always;
}
@page {
margin: 1.5cm;
}
}
</style>
</head>
<body>
<!-- Header -->
<div class="header">
<h1>銀行調節表</h1>
<div class="subtitle">Bank Reconciliation Statement</div>
<div class="subtitle">{{ $reconciliation->reconciliation_month->format('Y年m月') }}</div>
</div>
<!-- Discrepancy Warning -->
@if($reconciliation->hasUnresolvedDiscrepancy())
<div class="warning-box">
<div class="title"> 發現差異</div>
<div class="content">
調節後餘額與銀行對帳單餘額不符,差異金額: NT$ {{ number_format($reconciliation->discrepancy_amount, 2) }}
</div>
</div>
@endif
<!-- Basic Information -->
<div class="info-section">
<h2>一、基本資訊</h2>
<div class="info-grid">
<div class="info-item">
<dt>調節月份</dt>
<dd>{{ $reconciliation->reconciliation_month->format('Y年m月') }}</dd>
</div>
<div class="info-item">
<dt>對帳單日期</dt>
<dd>{{ $reconciliation->bank_statement_date->format('Y-m-d') }}</dd>
</div>
<div class="info-item">
<dt>製表人</dt>
<dd>{{ $reconciliation->preparedByCashier->name }}</dd>
</div>
<div class="info-item">
<dt>製表時間</dt>
<dd>{{ $reconciliation->prepared_at->format('Y-m-d H:i') }}</dd>
</div>
</div>
<div class="info-grid">
<div class="info-item">
<dt>調節狀態</dt>
<dd>
<span class="status-badge
@if($reconciliation->reconciliation_status === 'completed') status-completed
@elseif($reconciliation->reconciliation_status === 'discrepancy') status-discrepancy
@else status-pending
@endif">
{{ $reconciliation->getStatusText() }}
</span>
</dd>
</div>
@if($reconciliation->notes)
<div class="info-item">
<dt>備註</dt>
<dd>{{ $reconciliation->notes }}</dd>
</div>
@endif
</div>
</div>
<!-- Balance Reconciliation -->
<div class="info-section">
<h2>二、餘額調節</h2>
<div class="balance-summary">
<div class="balance-row">
<span>銀行對帳單餘額</span>
<span>NT$ {{ number_format($reconciliation->bank_statement_balance, 2) }}</span>
</div>
<div class="balance-row">
<span>系統帳面餘額</span>
<span>NT$ {{ number_format($reconciliation->system_book_balance, 2) }}</span>
</div>
<div class="balance-row">
<span>調節後餘額</span>
<span class="amount-positive">NT$ {{ number_format($reconciliation->calculateAdjustedBalance(), 2) }}</span>
</div>
<div class="balance-row highlight">
<span>差異金額</span>
<span class="{{ $reconciliation->discrepancy_amount > 0 ? 'amount-negative' : 'amount-positive' }}">
NT$ {{ number_format($reconciliation->discrepancy_amount, 2) }}
</span>
</div>
</div>
</div>
<!-- Outstanding Items -->
@php
$summary = $reconciliation->getOutstandingItemsSummary();
@endphp
<div class="info-section">
<h2>三、調節項目</h2>
<!-- Outstanding Checks -->
@if($reconciliation->outstanding_checks && count($reconciliation->outstanding_checks) > 0)
<h3 style="font-size: 13pt; margin: 15px 0 10px 0;">3.1 未兌現支票 ({{ $summary['outstanding_checks_count'] }} )</h3>
<table>
<thead>
<tr>
<th style="width: 30%;">支票號碼</th>
<th class="text-right" style="width: 25%;">金額 (NT$)</th>
<th style="width: 45%;">說明</th>
</tr>
</thead>
<tbody>
@foreach($reconciliation->outstanding_checks as $check)
<tr>
<td class="font-mono">{{ $check['check_number'] ?? 'N/A' }}</td>
<td class="text-right amount-negative">{{ number_format($check['amount'], 2) }}</td>
<td>{{ $check['description'] ?? '' }}</td>
</tr>
@endforeach
</tbody>
<tfoot>
<tr>
<td>小計</td>
<td class="text-right amount-negative">{{ number_format($summary['total_outstanding_checks'], 2) }}</td>
<td></td>
</tr>
</tfoot>
</table>
@endif
<!-- Deposits in Transit -->
@if($reconciliation->deposits_in_transit && count($reconciliation->deposits_in_transit) > 0)
<h3 style="font-size: 13pt; margin: 15px 0 10px 0;">3.2 在途存款 ({{ $summary['deposits_in_transit_count'] }} )</h3>
<table>
<thead>
<tr>
<th style="width: 25%;">存款日期</th>
<th class="text-right" style="width: 25%;">金額 (NT$)</th>
<th style="width: 50%;">說明</th>
</tr>
</thead>
<tbody>
@foreach($reconciliation->deposits_in_transit as $deposit)
<tr>
<td>{{ $deposit['date'] ?? 'N/A' }}</td>
<td class="text-right amount-positive">{{ number_format($deposit['amount'], 2) }}</td>
<td>{{ $deposit['description'] ?? '' }}</td>
</tr>
@endforeach
</tbody>
<tfoot>
<tr>
<td>小計</td>
<td class="text-right amount-positive">{{ number_format($summary['total_deposits_in_transit'], 2) }}</td>
<td></td>
</tr>
</tfoot>
</table>
@endif
<!-- Bank Charges -->
@if($reconciliation->bank_charges && count($reconciliation->bank_charges) > 0)
<h3 style="font-size: 13pt; margin: 15px 0 10px 0;">3.3 銀行手續費 ({{ $summary['bank_charges_count'] }} )</h3>
<table>
<thead>
<tr>
<th class="text-right" style="width: 30%;">金額 (NT$)</th>
<th style="width: 70%;">說明</th>
</tr>
</thead>
<tbody>
@foreach($reconciliation->bank_charges as $charge)
<tr>
<td class="text-right amount-negative">{{ number_format($charge['amount'], 2) }}</td>
<td>{{ $charge['description'] ?? '' }}</td>
</tr>
@endforeach
</tbody>
<tfoot>
<tr>
<td class="text-right amount-negative">{{ number_format($summary['total_bank_charges'], 2) }}</td>
<td>小計</td>
</tr>
</tfoot>
</table>
@endif
</div>
<!-- Signatures -->
<div class="signatures">
<div class="signature-box">
<div class="title">製表人(出納)</div>
<div class="name">{{ $reconciliation->preparedByCashier->name }}</div>
<div class="date">{{ $reconciliation->prepared_at->format('Y-m-d') }}</div>
</div>
<div class="signature-box">
<div class="title">覆核人(會計)</div>
@if($reconciliation->reviewed_at)
<div class="name">{{ $reconciliation->reviewedByAccountant->name }}</div>
<div class="date">{{ $reconciliation->reviewed_at->format('Y-m-d') }}</div>
@else
<div class="name" style="color: #999;">待覆核</div>
@endif
</div>
<div class="signature-box">
<div class="title">核准人(主管)</div>
@if($reconciliation->approved_at)
<div class="name">{{ $reconciliation->approvedByManager->name }}</div>
<div class="date">{{ $reconciliation->approved_at->format('Y-m-d') }}</div>
@else
<div class="name" style="color: #999;">待核准</div>
@endif
</div>
</div>
<!-- Footer -->
<div class="footer">
<p>本調節表由系統自動產生 - {{ now()->format('Y-m-d H:i:s') }}</p>
<p>此文件為正式財務記錄,請妥善保存</p>
</div>
</body>
</html>

View File

@@ -0,0 +1,348 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
銀行調節表詳情: {{ $reconciliation->reconciliation_month->format('Y年m月') }}
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-5xl sm:px-6 lg:px-8 space-y-4">
@if (session('status'))
<div class="rounded-md bg-green-50 p-4">
<p class="text-sm font-medium text-green-800">{{ session('status') }}</p>
</div>
@endif
@if (session('error'))
<div class="rounded-md bg-red-50 p-4">
<p class="text-sm font-medium text-red-800">{{ session('error') }}</p>
</div>
@endif
<!-- Reconciliation Status Warning -->
@if($reconciliation->hasUnresolvedDiscrepancy())
<div class="rounded-md bg-red-50 p-4 border border-red-200">
<div class="flex">
<div class="flex-shrink-0">
<svg class="h-5 w-5 text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
</div>
<div class="ml-3">
<h3 class="text-sm font-medium text-red-800">發現差異</h3>
<p class="mt-1 text-sm text-red-700">
調節後餘額與銀行對帳單餘額不符,差異金額: NT$ {{ number_format($reconciliation->discrepancy_amount, 2) }}
</p>
</div>
</div>
</div>
@endif
<!-- Basic Information -->
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-medium leading-6 text-gray-900">調節表資訊</h3>
<span class="inline-flex rounded-full px-3 py-1 text-sm font-semibold
@if($reconciliation->reconciliation_status === 'completed') bg-green-100 text-green-800
@elseif($reconciliation->reconciliation_status === 'discrepancy') bg-red-100 text-red-800
@else bg-yellow-100 text-yellow-800
@endif">
{{ $reconciliation->getStatusText() }}
</span>
</div>
<dl class="grid grid-cols-1 gap-x-4 gap-y-4 sm:grid-cols-2">
<div>
<dt class="text-sm font-medium text-gray-500">調節月份</dt>
<dd class="mt-1 text-sm text-gray-900 font-semibold">{{ $reconciliation->reconciliation_month->format('Y年m月') }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">對帳單日期</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $reconciliation->bank_statement_date->format('Y-m-d') }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">銀行對帳單餘額</dt>
<dd class="mt-1 text-lg font-semibold text-blue-600">NT$ {{ number_format($reconciliation->bank_statement_balance, 2) }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">系統帳面餘額</dt>
<dd class="mt-1 text-lg font-semibold text-indigo-600">NT$ {{ number_format($reconciliation->system_book_balance, 2) }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">調節後餘額</dt>
<dd class="mt-1 text-lg font-semibold text-green-600">NT$ {{ number_format($reconciliation->calculateAdjustedBalance(), 2) }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">差異金額</dt>
<dd class="mt-1 text-lg font-semibold {{ $reconciliation->discrepancy_amount > 0 ? 'text-red-600' : 'text-green-600' }}">
NT$ {{ number_format($reconciliation->discrepancy_amount, 2) }}
</dd>
</div>
@if($reconciliation->bank_statement_file_path)
<div class="sm:col-span-2">
<dt class="text-sm font-medium text-gray-500">銀行對帳單</dt>
<dd class="mt-1">
<a href="{{ route('admin.bank-reconciliations.download', $reconciliation) }}" class="text-indigo-600 hover:text-indigo-900">
<svg class="inline h-5 w-5 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
下載對帳單
</a>
</dd>
</div>
@endif
<div class="sm:col-span-2">
<dt class="text-sm font-medium text-gray-500">製表人(出納)</dt>
<dd class="mt-1 text-sm text-gray-900">
{{ $reconciliation->preparedByCashier->name }} - {{ $reconciliation->prepared_at->format('Y-m-d H:i') }}
</dd>
</div>
@if($reconciliation->notes)
<div class="sm:col-span-2">
<dt class="text-sm font-medium text-gray-500">備註</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $reconciliation->notes }}</dd>
</div>
@endif
</dl>
</div>
</div>
<!-- Outstanding Items Summary -->
@php
$summary = $reconciliation->getOutstandingItemsSummary();
@endphp
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium leading-6 text-gray-900 mb-4">調節項目明細</h3>
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3 mb-6">
<!-- Outstanding Checks -->
<div class="rounded-lg border border-red-200 bg-red-50 p-4">
<dt class="text-sm font-medium text-gray-700">未兌現支票</dt>
<dd class="mt-2 text-2xl font-semibold text-red-700">
- NT$ {{ number_format($summary['total_outstanding_checks'], 2) }}
</dd>
<dd class="mt-1 text-xs text-gray-600">{{ $summary['outstanding_checks_count'] }} </dd>
</div>
<!-- Deposits in Transit -->
<div class="rounded-lg border border-green-200 bg-green-50 p-4">
<dt class="text-sm font-medium text-gray-700">在途存款</dt>
<dd class="mt-2 text-2xl font-semibold text-green-700">
+ NT$ {{ number_format($summary['total_deposits_in_transit'], 2) }}
</dd>
<dd class="mt-1 text-xs text-gray-600">{{ $summary['deposits_in_transit_count'] }} </dd>
</div>
<!-- Bank Charges -->
<div class="rounded-lg border border-yellow-200 bg-yellow-50 p-4">
<dt class="text-sm font-medium text-gray-700">銀行手續費</dt>
<dd class="mt-2 text-2xl font-semibold text-yellow-700">
- NT$ {{ number_format($summary['total_bank_charges'], 2) }}
</dd>
<dd class="mt-1 text-xs text-gray-600">{{ $summary['bank_charges_count'] }} </dd>
</div>
</div>
<!-- Detailed Outstanding Checks -->
@if($reconciliation->outstanding_checks && count($reconciliation->outstanding_checks) > 0)
<div class="mb-4">
<h4 class="text-sm font-semibold text-gray-700 mb-2">未兌現支票明細</h4>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500">支票號碼</th>
<th class="px-3 py-2 text-right text-xs font-medium text-gray-500">金額</th>
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500">說明</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
@foreach($reconciliation->outstanding_checks as $check)
<tr>
<td class="whitespace-nowrap px-3 py-2 text-sm text-gray-900 font-mono">{{ $check['check_number'] ?? 'N/A' }}</td>
<td class="whitespace-nowrap px-3 py-2 text-sm text-right text-red-600">NT$ {{ number_format($check['amount'], 2) }}</td>
<td class="px-3 py-2 text-sm text-gray-500">{{ $check['description'] ?? '' }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@endif
<!-- Detailed Deposits in Transit -->
@if($reconciliation->deposits_in_transit && count($reconciliation->deposits_in_transit) > 0)
<div class="mb-4">
<h4 class="text-sm font-semibold text-gray-700 mb-2">在途存款明細</h4>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500">存款日期</th>
<th class="px-3 py-2 text-right text-xs font-medium text-gray-500">金額</th>
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500">說明</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
@foreach($reconciliation->deposits_in_transit as $deposit)
<tr>
<td class="whitespace-nowrap px-3 py-2 text-sm text-gray-900">{{ $deposit['date'] ?? 'N/A' }}</td>
<td class="whitespace-nowrap px-3 py-2 text-sm text-right text-green-600">NT$ {{ number_format($deposit['amount'], 2) }}</td>
<td class="px-3 py-2 text-sm text-gray-500">{{ $deposit['description'] ?? '' }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@endif
<!-- Detailed Bank Charges -->
@if($reconciliation->bank_charges && count($reconciliation->bank_charges) > 0)
<div>
<h4 class="text-sm font-semibold text-gray-700 mb-2">銀行手續費明細</h4>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-3 py-2 text-right text-xs font-medium text-gray-500">金額</th>
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500">說明</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
@foreach($reconciliation->bank_charges as $charge)
<tr>
<td class="whitespace-nowrap px-3 py-2 text-sm text-right text-yellow-600">NT$ {{ number_format($charge['amount'], 2) }}</td>
<td class="px-3 py-2 text-sm text-gray-500">{{ $charge['description'] ?? '' }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@endif
</div>
</div>
<!-- Accountant Review Section -->
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium leading-6 text-gray-900 mb-4">會計覆核</h3>
@if($reconciliation->reviewed_at)
<dl class="grid grid-cols-1 gap-x-4 gap-y-4 sm:grid-cols-2">
<div>
<dt class="text-sm font-medium text-gray-500">覆核人</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $reconciliation->reviewedByAccountant->name }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">覆核時間</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $reconciliation->reviewed_at->format('Y-m-d H:i') }}</dd>
</div>
@if($reconciliation->review_notes)
<div class="sm:col-span-2">
<dt class="text-sm font-medium text-gray-500">覆核備註</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $reconciliation->review_notes }}</dd>
</div>
@endif
</dl>
@else
<p class="text-sm text-gray-500 mb-4">此調節表待會計覆核</p>
@can('review_bank_reconciliation')
@if($reconciliation->canBeReviewed())
<form method="POST" action="{{ route('admin.bank-reconciliations.review', $reconciliation) }}" class="space-y-4">
@csrf
<div>
<label for="review_notes" class="block text-sm font-medium text-gray-700">覆核備註</label>
<textarea name="review_notes" id="review_notes" rows="3"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"></textarea>
</div>
<div>
<button type="submit"
class="inline-flex items-center rounded-md border border-transparent bg-green-600 px-4 py-2 text-sm font-medium text-white hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2">
確認覆核
</button>
</div>
</form>
@endif
@endcan
@endif
</div>
</div>
<!-- Manager Approval Section -->
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium leading-6 text-gray-900 mb-4">主管核准</h3>
@if($reconciliation->approved_at)
<dl class="grid grid-cols-1 gap-x-4 gap-y-4 sm:grid-cols-2">
<div>
<dt class="text-sm font-medium text-gray-500">核准人</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $reconciliation->approvedByManager->name }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">核准時間</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $reconciliation->approved_at->format('Y-m-d H:i') }}</dd>
</div>
@if($reconciliation->approval_notes)
<div class="sm:col-span-2">
<dt class="text-sm font-medium text-gray-500">核准備註</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $reconciliation->approval_notes }}</dd>
</div>
@endif
</dl>
@else
@if($reconciliation->reviewed_at)
<p class="text-sm text-gray-500 mb-4">此調節表待主管核准</p>
@can('approve_bank_reconciliation')
@if($reconciliation->canBeApproved())
<form method="POST" action="{{ route('admin.bank-reconciliations.approve', $reconciliation) }}" class="space-y-4">
@csrf
<div>
<label for="approval_notes" class="block text-sm font-medium text-gray-700">核准備註</label>
<textarea name="approval_notes" id="approval_notes" rows="3"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"></textarea>
</div>
<div>
<button type="submit"
class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
確認核准
</button>
</div>
</form>
@endif
@endcan
@else
<p class="text-sm text-gray-500">等待會計覆核完成後,方可進行主管核准</p>
@endif
@endif
</div>
</div>
<!-- Actions -->
<div class="flex justify-between">
<a href="{{ route('admin.bank-reconciliations.index') }}" class="inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
返回列表
</a>
@if($reconciliation->isCompleted())
<a href="{{ route('admin.bank-reconciliations.pdf', $reconciliation) }}" target="_blank" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
<svg class="-ml-1 mr-2 h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
匯出PDF
</a>
@endif
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,175 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
{{ __('Create Budget') }} (úË)
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8">
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800">
<div class="px-4 py-5 sm:p-6">
<form method="POST" action="{{ route('admin.budgets.store') }}" class="space-y-6" aria-label="{{ __('Create budget form') }}">
@csrf
<!-- Fiscal Year -->
<div>
<label for="fiscal_year" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Fiscal Year') }} <span class="text-red-500" aria-label="{{ __('required') }}">*</span>
</label>
<input type="number"
name="fiscal_year"
id="fiscal_year"
value="{{ old('fiscal_year', now()->year) }}"
min="2000"
max="2100"
required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100 dark:focus:ring-offset-gray-800 @error('fiscal_year') border-red-300 dark:border-red-500 @enderror"
aria-describedby="fiscal_year_help @error('fiscal_year') fiscal_year_error @enderror">
<p id="fiscal_year_help" class="mt-1 text-sm text-gray-500 dark:text-gray-400">
{{ __('The fiscal year this budget applies to') }}
</p>
@error('fiscal_year')
<p id="fiscal_year_error" class="mt-1 text-sm text-red-600 dark:text-red-400" role="alert">{{ $message }}</p>
@enderror
</div>
<!-- Name -->
<div>
<label for="name" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Budget Name') }} <span class="text-red-500" aria-label="{{ __('required') }}">*</span>
</label>
<input type="text"
name="name"
id="name"
value="{{ old('name') }}"
required
maxlength="255"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100 dark:focus:ring-offset-gray-800 @error('name') border-red-300 dark:border-red-500 @enderror"
placeholder="{{ __('e.g., Annual Budget 2025') }}"
aria-describedby="name_help @error('name') name_error @enderror">
<p id="name_help" class="mt-1 text-sm text-gray-500 dark:text-gray-400">
{{ __('Descriptive name for this budget') }}
</p>
@error('name')
<p id="name_error" class="mt-1 text-sm text-red-600 dark:text-red-400" role="alert">{{ $message }}</p>
@enderror
</div>
<!-- Period Type -->
<div>
<label for="period_type" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Period Type') }} <span class="text-red-500" aria-label="{{ __('required') }}">*</span>
</label>
<select name="period_type"
id="period_type"
required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100 dark:focus:ring-offset-gray-800 @error('period_type') border-red-300 dark:border-red-500 @enderror"
aria-describedby="period_type_help @error('period_type') period_type_error @enderror">
<option value="annual" @selected(old('period_type', 'annual') === 'annual')>{{ __('Annual') }} ()</option>
<option value="quarterly" @selected(old('period_type') === 'quarterly')>{{ __('Quarterly') }} ()</option>
<option value="monthly" @selected(old('period_type') === 'monthly')>{{ __('Monthly') }} (¦)</option>
</select>
<p id="period_type_help" class="mt-1 text-sm text-gray-500 dark:text-gray-400">
{{ __('Budget period duration') }}
</p>
@error('period_type')
<p id="period_type_error" class="mt-1 text-sm text-red-600 dark:text-red-400" role="alert">{{ $message }}</p>
@enderror
</div>
<!-- Period Dates -->
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2">
<div>
<label for="period_start" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Period Start Date') }} <span class="text-red-500" aria-label="{{ __('required') }}">*</span>
</label>
<input type="date"
name="period_start"
id="period_start"
value="{{ old('period_start') }}"
required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100 dark:focus:ring-offset-gray-800 @error('period_start') border-red-300 dark:border-red-500 @enderror"
aria-describedby="@error('period_start') period_start_error @enderror">
@error('period_start')
<p id="period_start_error" class="mt-1 text-sm text-red-600 dark:text-red-400" role="alert">{{ $message }}</p>
@enderror
</div>
<div>
<label for="period_end" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Period End Date') }} <span class="text-red-500" aria-label="{{ __('required') }}">*</span>
</label>
<input type="date"
name="period_end"
id="period_end"
value="{{ old('period_end') }}"
required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100 dark:focus:ring-offset-gray-800 @error('period_end') border-red-300 dark:border-red-500 @enderror"
aria-describedby="@error('period_end') period_end_error @enderror">
@error('period_end')
<p id="period_end_error" class="mt-1 text-sm text-red-600 dark:text-red-400" role="alert">{{ $message }}</p>
@enderror
</div>
</div>
<!-- Notes -->
<div>
<label for="notes" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Notes') }} (;)
</label>
<textarea name="notes"
id="notes"
rows="3"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100 dark:focus:ring-offset-gray-800 @error('notes') border-red-300 dark:border-red-500 @enderror"
placeholder="{{ __('Optional notes about this budget...') }}"
aria-describedby="notes_help @error('notes') notes_error @enderror">{{ old('notes') }}</textarea>
<p id="notes_help" class="mt-1 text-sm text-gray-500 dark:text-gray-400">
{{ __('Additional information about this budget (optional)') }}
</p>
@error('notes')
<p id="notes_error" class="mt-1 text-sm text-red-600 dark:text-red-400" role="alert">{{ $message }}</p>
@enderror
</div>
<!-- Actions -->
<div class="flex items-center justify-end gap-x-4 border-t border-gray-200 pt-6 dark:border-gray-700">
<a href="{{ route('admin.budgets.index') }}"
class="rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:bg-gray-700 dark:text-gray-100 dark:ring-gray-600 dark:hover:bg-gray-600 dark:focus:ring-offset-gray-800">
{{ __('Cancel') }}
</a>
<button type="submit"
class="inline-flex justify-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:bg-indigo-500 dark:hover:bg-indigo-400 dark:focus:ring-offset-gray-800">
{{ __('Create Budget') }}
</button>
</div>
</form>
</div>
</div>
<!-- Help Section -->
<div class="mt-6 rounded-lg bg-blue-50 p-4 dark:bg-blue-900/30 border-l-4 border-blue-400 dark:border-blue-500">
<div class="flex">
<div class="flex-shrink-0">
<svg class="h-5 w-5 text-blue-400" fill="currentColor" viewBox="0 0 20 20" aria-hidden="true">
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z" clip-rule="evenodd" />
</svg>
</div>
<div class="ml-3">
<h3 class="text-sm font-medium text-blue-800 dark:text-blue-200">
{{ __('Next Steps') }}
</h3>
<div class="mt-2 text-sm text-blue-700 dark:text-blue-300">
<p>{{ __('After creating the budget, you will be able to:') }}</p>
<ul class="list-disc pl-5 mt-2 space-y-1">
<li>{{ __('Add budget items for income and expense accounts') }}</li>
<li>{{ __('Submit the budget for chair approval') }}</li>
<li>{{ __('Activate the budget to start tracking actual amounts') }}</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,154 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
{{ __('Edit Budget') }} - {{ $budget->fiscal_year }}
</h2>
</x-slot>
<div class="py-12" x-data="budgetEditor()">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8">
<form method="POST" action="{{ route('admin.budgets.update', $budget) }}" class="space-y-6">
@csrf
@method('PATCH')
<!-- Basic Info -->
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">{{ __('Basic Information') }}</h3>
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2">
<div>
<label for="name" class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ __('Budget Name') }} *</label>
<input type="text" name="name" id="name" value="{{ old('name', $budget->name) }}" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
@error('name')<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>@enderror
</div>
<div>
<label for="period_start" class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ __('Period Start') }} *</label>
<input type="date" name="period_start" id="period_start" value="{{ old('period_start', $budget->period_start->format('Y-m-d')) }}" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
</div>
<div>
<label for="period_end" class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ __('Period End') }} *</label>
<input type="date" name="period_end" id="period_end" value="{{ old('period_end', $budget->period_end->format('Y-m-d')) }}" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
</div>
<div class="sm:col-span-2">
<label for="notes" class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ __('Notes') }}</label>
<textarea name="notes" id="notes" rows="3" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">{{ old('notes', $budget->notes) }}</textarea>
</div>
</div>
</div>
<!-- Income Items -->
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">{{ __('Income') }} (6e)</h3>
<button type="button" @click="addItem('income')" class="btn-secondary text-sm">+ {{ __('Add Income Item') }}</button>
</div>
<div class="space-y-4">
<template x-for="(item, index) in incomeItems" :key="index">
<div class="flex gap-4 items-start bg-gray-50 dark:bg-gray-900 p-4 rounded-md">
<div class="flex-1">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ __('Account') }}</label>
<select :name="'budget_items[income_' + index + '][chart_of_account_id]'" x-model="item.account_id" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
<option value="">{{ __('Select account...') }}</option>
@foreach($incomeAccounts as $account)
<option value="{{ $account->id }}">{{ $account->account_code }} - {{ $account->account_name_zh }}</option>
@endforeach
</select>
</div>
<div class="w-48">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ __('Amount') }}</label>
<input type="number" :name="'budget_items[income_' + index + '][budgeted_amount]'" x-model="item.amount" step="0.01" min="0" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
</div>
<div class="pt-6">
<button type="button" @click="removeItem('income', index)" class="text-red-600 hover:text-red-800 dark:text-red-400">
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd"/></svg>
</button>
</div>
</div>
</template>
<div x-show="incomeItems.length === 0" class="text-center py-8 text-gray-500 dark:text-gray-400">
{{ __('No income items. Click "Add Income Item" to get started.') }}
</div>
</div>
</div>
<!-- Expense Items -->
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">{{ __('Expenses') }} (/ú)</h3>
<button type="button" @click="addItem('expense')" class="btn-secondary text-sm">+ {{ __('Add Expense Item') }}</button>
</div>
<div class="space-y-4">
<template x-for="(item, index) in expenseItems" :key="index">
<div class="flex gap-4 items-start bg-gray-50 dark:bg-gray-900 p-4 rounded-md">
<div class="flex-1">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ __('Account') }}</label>
<select :name="'budget_items[expense_' + index + '][chart_of_account_id]'" x-model="item.account_id" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
<option value="">{{ __('Select account...') }}</option>
@foreach($expenseAccounts as $account)
<option value="{{ $account->id }}">{{ $account->account_code }} - {{ $account->account_name_zh }}</option>
@endforeach
</select>
</div>
<div class="w-48">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ __('Amount') }}</label>
<input type="number" :name="'budget_items[expense_' + index + '][budgeted_amount]'" x-model="item.amount" step="0.01" min="0" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
</div>
<div class="pt-6">
<button type="button" @click="removeItem('expense', index)" class="text-red-600 hover:text-red-800 dark:text-red-400">
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd"/></svg>
</button>
</div>
</div>
</template>
<div x-show="expenseItems.length === 0" class="text-center py-8 text-gray-500 dark:text-gray-400">
{{ __('No expense items. Click "Add Expense Item" to get started.') }}
</div>
</div>
</div>
<!-- Actions -->
<div class="flex items-center justify-end gap-x-4">
<a href="{{ route('admin.budgets.show', $budget) }}" class="btn-secondary">{{ __('Cancel') }}</a>
<button type="submit" class="btn-primary">{{ __('Save Budget') }}</button>
</div>
</form>
</div>
</div>
<script>
function budgetEditor() {
return {
incomeItems: @json($budget->budgetItems->filter(fn($item) => $item->chartOfAccount->isIncome())->map(fn($item) => ['account_id' => $item->chart_of_account_id, 'amount' => $item->budgeted_amount])->values()),
expenseItems: @json($budget->budgetItems->filter(fn($item) => $item->chartOfAccount->isExpense())->map(fn($item) => ['account_id' => $item->chart_of_account_id, 'amount' => $item->budgeted_amount])->values()),
addItem(type) {
if (type === 'income') {
this.incomeItems.push({ account_id: '', amount: 0 });
} else {
this.expenseItems.push({ account_id: '', amount: 0 });
}
},
removeItem(type, index) {
if (type === 'income') {
this.incomeItems.splice(index, 1);
} else {
this.expenseItems.splice(index, 1);
}
}
}
}
</script>
</x-app-layout>

View File

@@ -0,0 +1,210 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
{{ __('Budgets') }} (—¡)
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8">
<!-- Success Message -->
@if (session('status'))
<div class="mb-4 rounded-md bg-green-50 p-4 dark:bg-green-900/30 border-l-4 border-green-400 dark:border-green-500" role="status" aria-live="polite">
<div class="flex">
<div class="flex-shrink-0">
<svg class="h-5 w-5 text-green-400" fill="currentColor" viewBox="0 0 20 20" aria-hidden="true">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" />
</svg>
</div>
<div class="ml-3">
<p class="text-sm font-medium text-green-800 dark:text-green-200">
{{ session('status') }}
</p>
</div>
</div>
</div>
@endif
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800">
<div class="px-4 py-5 sm:p-6">
<!-- Header with Create Button -->
<div class="sm:flex sm:items-center sm:justify-between mb-6">
<div>
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100">
{{ __('Budget List') }}
</h3>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
{{ __('Manage annual budgets and track financial performance') }}
</p>
</div>
<div class="mt-4 sm:mt-0">
<a href="{{ route('admin.budgets.create') }}"
class="inline-flex items-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:bg-indigo-500 dark:hover:bg-indigo-400 dark:focus:ring-offset-gray-800"
aria-label="{{ __('Create new budget') }}">
<svg class="-ml-0.5 mr-1.5 h-5 w-5" fill="currentColor" viewBox="0 0 20 20" aria-hidden="true">
<path d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z" />
</svg>
{{ __('Create Budget') }}
</a>
</div>
</div>
<!-- Filters -->
<form method="GET" action="{{ route('admin.budgets.index') }}" class="mb-6 space-y-4" role="search" aria-label="{{ __('Filter budgets') }}">
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
<div>
<label for="fiscal_year" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Fiscal Year') }} ()
</label>
<select id="fiscal_year" name="fiscal_year"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
<option value="">{{ __('All Years') }}</option>
@foreach($fiscalYears as $year)
<option value="{{ $year }}" @selected(request('fiscal_year') == $year)>
{{ $year }}
</option>
@endforeach
</select>
</div>
<div>
<label for="status" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Status') }} (ÀK)
</label>
<select id="status" name="status"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
<option value="">{{ __('All Statuses') }}</option>
<option value="draft" @selected(request('status') === 'draft')>{{ __('Draft') }}</option>
<option value="submitted" @selected(request('status') === 'submitted')>{{ __('Submitted') }}</option>
<option value="approved" @selected(request('status') === 'approved')>{{ __('Approved') }}</option>
<option value="active" @selected(request('status') === 'active')>{{ __('Active') }}</option>
<option value="closed" @selected(request('status') === 'closed')>{{ __('Closed') }}</option>
</select>
</div>
<div class="flex items-end">
<button type="submit"
class="inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:bg-gray-700 dark:text-gray-100 dark:ring-gray-600 dark:hover:bg-gray-600 dark:focus:ring-offset-gray-800">
{{ __('Filter') }}
</button>
</div>
</div>
</form>
<!-- Budgets Table -->
<div class="overflow-hidden shadow ring-1 ring-black ring-opacity-5 sm:rounded-lg dark:ring-gray-700">
<table class="min-w-full divide-y divide-gray-300 dark:divide-gray-600">
<caption class="sr-only">
{{ __('List of budgets showing fiscal year, name, period, and status') }}
</caption>
<thead class="bg-gray-50 dark:bg-gray-900">
<tr>
<th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 dark:text-gray-100 sm:pl-6">
{{ __('Fiscal Year') }}
</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">
{{ __('Name') }}
</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">
{{ __('Period') }}
</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">
{{ __('Status') }}
</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">
{{ __('Created By') }}
</th>
<th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-6">
<span class="sr-only">{{ __('Actions') }}</span>
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white dark:divide-gray-700 dark:bg-gray-800">
@forelse($budgets as $budget)
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
<td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 dark:text-gray-100 sm:pl-6">
{{ $budget->fiscal_year }}
</td>
<td class="px-3 py-4 text-sm text-gray-900 dark:text-gray-100">
{{ $budget->name }}
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400">
{{ $budget->period_start->format('Y-m-d') }} ~ {{ $budget->period_end->format('Y-m-d') }}
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm">
@if($budget->status === 'draft')
<span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-800 dark:bg-gray-700 dark:text-gray-200" role="status" aria-label="{{ __('Status: Draft') }}">
{{ __('Draft') }}
</span>
@elseif($budget->status === 'submitted')
<span class="inline-flex items-center rounded-full bg-yellow-100 px-2.5 py-0.5 text-xs font-medium text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200" role="status" aria-label="{{ __('Status: Submitted') }}">
{{ __('Submitted') }}
</span>
@elseif($budget->status === 'approved')
<span class="inline-flex items-center rounded-full bg-blue-100 px-2.5 py-0.5 text-xs font-medium text-blue-800 dark:bg-blue-900 dark:text-blue-200" role="status" aria-label="{{ __('Status: Approved') }}">
{{ __('Approved') }}
</span>
@elseif($budget->status === 'active')
<span class="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-800 dark:bg-green-900 dark:text-green-200" role="status" aria-label="{{ __('Status: Active') }}">
 {{ __('Active') }}
</span>
@elseif($budget->status === 'closed')
<span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-800 dark:bg-gray-700 dark:text-gray-200" role="status" aria-label="{{ __('Status: Closed') }}">
{{ __('Closed') }}
</span>
@endif
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400">
{{ $budget->createdBy->name }}
</td>
<td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6">
<a href="{{ route('admin.budgets.show', $budget) }}"
class="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800"
aria-label="{{ __('View budget for fiscal year :year', ['year' => $budget->fiscal_year]) }}">
{{ __('View') }}
</a>
@if($budget->canBeEdited())
<span class="text-gray-300 dark:text-gray-600" aria-hidden="true"> | </span>
<a href="{{ route('admin.budgets.edit', $budget) }}"
class="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800"
aria-label="{{ __('Edit budget for fiscal year :year', ['year' => $budget->fiscal_year]) }}">
{{ __('Edit') }}
</a>
@endif
</td>
</tr>
@empty
<tr>
<td colspan="6" class="px-3 py-8 text-center text-sm text-gray-500 dark:text-gray-400">
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
<p class="mt-2 text-sm font-semibold">{{ __('No budgets found') }}</p>
<p class="mt-1 text-sm">{{ __('Get started by creating a new budget.') }}</p>
<div class="mt-6">
<a href="{{ route('admin.budgets.create') }}"
class="inline-flex items-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:bg-indigo-500 dark:hover:bg-indigo-400 dark:focus:ring-offset-gray-800">
<svg class="-ml-0.5 mr-1.5 h-5 w-5" fill="currentColor" viewBox="0 0 20 20" aria-hidden="true">
<path d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z" />
</svg>
{{ __('Create Budget') }}
</a>
</div>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<!-- Pagination -->
@if($budgets->hasPages())
<div class="mt-6">
{{ $budgets->links() }}
</div>
@endif
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,127 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
{{ __('Budget Details') }} - {{ $budget->fiscal_year }}
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8 space-y-6">
@if (session('status'))
<div class="rounded-md bg-green-50 p-4 dark:bg-green-900/30 border-l-4 border-green-400" role="status" aria-live="polite">
<p class="text-sm text-green-800 dark:text-green-200">{{ session('status') }}</p>
</div>
@endif
<!-- Budget Info -->
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<div class="sm:flex sm:items-center sm:justify-between mb-4">
<div>
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">{{ $budget->name }}</h3>
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
{{ $budget->period_start->format('Y-m-d') }} ~ {{ $budget->period_end->format('Y-m-d') }}
</p>
</div>
<div>
@if($budget->status === 'active')
<span class="inline-flex rounded-full bg-green-100 px-3 py-1 text-sm font-medium text-green-800 dark:bg-green-900 dark:text-green-200"> {{ __('Active') }}</span>
@elseif($budget->status === 'approved')
<span class="inline-flex rounded-full bg-blue-100 px-3 py-1 text-sm font-medium text-blue-800 dark:bg-blue-900 dark:text-blue-200">{{ __('Approved') }}</span>
@else
<span class="inline-flex rounded-full bg-gray-100 px-3 py-1 text-sm font-medium text-gray-800 dark:bg-gray-700 dark:text-gray-200">{{ __(ucfirst($budget->status)) }}</span>
@endif
</div>
</div>
<div class="mt-6 flex gap-3">
@if($budget->canBeEdited())
<a href="{{ route('admin.budgets.edit', $budget) }}" class="btn-secondary">{{ __('Edit') }}</a>
@endif
@if($budget->isDraft())
<form method="POST" action="{{ route('admin.budgets.submit', $budget) }}">
@csrf
<button type="submit" class="btn-primary">{{ __('Submit') }}</button>
</form>
@endif
</div>
</div>
<!-- Summary Cards -->
<div class="grid grid-cols-1 gap-6 sm:grid-cols-4">
<div class="bg-white shadow rounded-lg dark:bg-gray-800 p-5">
<dt class="text-sm text-gray-500 dark:text-gray-400">{{ __('Budgeted Income') }}</dt>
<dd class="text-2xl font-bold text-gray-900 dark:text-gray-100">NT$ {{ number_format($budget->total_budgeted_income) }}</dd>
</div>
<div class="bg-white shadow rounded-lg dark:bg-gray-800 p-5">
<dt class="text-sm text-gray-500 dark:text-gray-400">{{ __('Budgeted Expense') }}</dt>
<dd class="text-2xl font-bold text-gray-900 dark:text-gray-100">NT$ {{ number_format($budget->total_budgeted_expense) }}</dd>
</div>
<div class="bg-white shadow rounded-lg dark:bg-gray-800 p-5">
<dt class="text-sm text-gray-500 dark:text-gray-400">{{ __('Actual Income') }}</dt>
<dd class="text-2xl font-bold text-gray-900 dark:text-gray-100">NT$ {{ number_format($budget->total_actual_income) }}</dd>
</div>
<div class="bg-white shadow rounded-lg dark:bg-gray-800 p-5">
<dt class="text-sm text-gray-500 dark:text-gray-400">{{ __('Actual Expense') }}</dt>
<dd class="text-2xl font-bold text-gray-900 dark:text-gray-100">NT$ {{ number_format($budget->total_actual_expense) }}</dd>
</div>
</div>
<!-- Income Items -->
@if($incomeItems->count() > 0)
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<h3 class="text-base font-semibold text-gray-900 dark:text-gray-100 mb-4">{{ __('Income') }} (6e)</h3>
<table class="min-w-full divide-y divide-gray-300 dark:divide-gray-600">
<thead class="bg-gray-50 dark:bg-gray-900">
<tr>
<th scope="col" class="py-3 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Account') }}</th>
<th scope="col" class="px-3 py-3 text-right text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Budgeted') }}</th>
<th scope="col" class="px-3 py-3 text-right text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Actual') }}</th>
<th scope="col" class="px-3 py-3 text-right text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Variance') }}</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
@foreach($incomeItems as $item)
<tr>
<td class="py-4 text-sm text-gray-900 dark:text-gray-100">{{ $item->chartOfAccount->account_name_zh }}</td>
<td class="px-3 py-4 text-sm text-right tabular-nums text-gray-900 dark:text-gray-100">NT$ {{ number_format($item->budgeted_amount, 2) }}</td>
<td class="px-3 py-4 text-sm text-right tabular-nums text-gray-900 dark:text-gray-100">NT$ {{ number_format($item->actual_amount, 2) }}</td>
<td class="px-3 py-4 text-sm text-right tabular-nums {{ $item->variance >= 0 ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400' }}">{{ $item->variance >= 0 ? '+' : '' }}NT$ {{ number_format($item->variance, 2) }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@endif
<!-- Expense Items -->
@if($expenseItems->count() > 0)
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<h3 class="text-base font-semibold text-gray-900 dark:text-gray-100 mb-4">{{ __('Expenses') }} (/ú)</h3>
<table class="min-w-full divide-y divide-gray-300 dark:divide-gray-600">
<thead class="bg-gray-50 dark:bg-gray-900">
<tr>
<th scope="col" class="py-3 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Account') }}</th>
<th scope="col" class="px-3 py-3 text-right text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Budgeted') }}</th>
<th scope="col" class="px-3 py-3 text-right text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Actual') }}</th>
<th scope="col" class="px-3 py-3 text-right text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Utilization') }}</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
@foreach($expenseItems as $item)
<tr class="{{ $item->isOverBudget() ? 'bg-red-50 dark:bg-red-900/20' : '' }}">
<td class="py-4 text-sm text-gray-900 dark:text-gray-100">
{{ $item->chartOfAccount->account_name_zh }}
@if($item->isOverBudget()) <span class="text-red-600"> </span> @endif
</td>
<td class="px-3 py-4 text-sm text-right tabular-nums text-gray-900 dark:text-gray-100">NT$ {{ number_format($item->budgeted_amount, 2) }}</td>
<td class="px-3 py-4 text-sm text-right tabular-nums text-gray-900 dark:text-gray-100">NT$ {{ number_format($item->actual_amount, 2) }}</td>
<td class="px-3 py-4 text-sm text-right {{ $item->utilization_percentage > 100 ? 'text-red-600 dark:text-red-400 font-semibold' : 'text-gray-900 dark:text-gray-100' }}">{{ number_format($item->utilization_percentage, 1) }}%</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@endif
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,194 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
現金簿餘額報表
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8 space-y-4">
<!-- Action Buttons -->
<div class="flex justify-between">
<a href="{{ route('admin.cashier-ledger.index') }}" class="inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
返回現金簿
</a>
<button onclick="window.print()" class="inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
<svg class="-ml-1 mr-2 h-5 w-5 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z" />
</svg>
列印報表
</button>
</div>
<!-- Report Header -->
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6 text-center">
<h3 class="text-2xl font-bold text-gray-900">現金簿餘額報表</h3>
<p class="mt-1 text-sm text-gray-500">報表日期: {{ now()->format('Y-m-d H:i') }}</p>
</div>
</div>
<!-- Account Balances -->
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium leading-6 text-gray-900 mb-4">各帳戶餘額</h3>
@if($accounts->isNotEmpty())
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
@php
$totalBalance = 0;
@endphp
@foreach($accounts as $account)
@php
$totalBalance += $account['balance'];
@endphp
<div class="rounded-lg border-2 {{ $account['balance'] >= 0 ? 'border-green-300 bg-green-50' : 'border-red-300 bg-red-50' }} p-4">
<div class="flex items-center justify-between">
<div class="flex-1">
<dt class="text-sm font-medium text-gray-700">{{ $account['bank_account'] }}</dt>
<dd class="mt-2 text-3xl font-bold {{ $account['balance'] >= 0 ? 'text-green-700' : 'text-red-700' }}">
NT$ {{ number_format($account['balance'], 2) }}
</dd>
@if($account['last_updated'])
<p class="mt-1 text-xs text-gray-500">
最後更新: {{ $account['last_updated']->format('Y-m-d') }}
</p>
@endif
</div>
<div class="flex-shrink-0">
<svg class="h-12 w-12 {{ $account['balance'] >= 0 ? 'text-green-400' : 'text-red-400' }}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@if($account['balance'] >= 0)
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
@else
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
@endif
</svg>
</div>
</div>
</div>
@endforeach
</div>
<!-- Total Balance -->
<div class="mt-6 rounded-lg border-2 border-indigo-300 bg-indigo-50 p-6">
<div class="flex items-center justify-between">
<div>
<dt class="text-base font-medium text-indigo-900">總餘額</dt>
<dd class="mt-2 text-4xl font-bold {{ $totalBalance >= 0 ? 'text-indigo-700' : 'text-red-700' }}">
NT$ {{ number_format($totalBalance, 2) }}
</dd>
</div>
<svg class="h-16 w-16 text-indigo-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 9V7a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2m2 4h10a2 2 0 002-2v-6a2 2 0 00-2-2H9a2 2 0 00-2 2v6a2 2 0 002 2zm7-5a2 2 0 11-4 0 2 2 0 014 0z" />
</svg>
</div>
</div>
@else
<div class="text-center py-8 text-gray-500">
暫無帳戶餘額記錄
</div>
@endif
</div>
</div>
<!-- Monthly Summary -->
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium leading-6 text-gray-900 mb-4">本月交易摘要 ({{ now()->format('Y年m月') }})</h3>
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
<!-- Monthly Receipts -->
<div class="rounded-lg border border-green-200 bg-green-50 p-4">
<div class="flex items-center">
<div class="flex-shrink-0">
<svg class="h-10 w-10 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 11l5-5m0 0l5 5m-5-5v12" />
</svg>
</div>
<div class="ml-4 flex-1">
<dt class="text-sm font-medium text-gray-700">本月收入</dt>
<dd class="mt-1 text-2xl font-semibold text-green-700">
NT$ {{ number_format($monthlySummary['receipts'], 2) }}
</dd>
</div>
</div>
</div>
<!-- Monthly Payments -->
<div class="rounded-lg border border-red-200 bg-red-50 p-4">
<div class="flex items-center">
<div class="flex-shrink-0">
<svg class="h-10 w-10 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 13l-5 5m0 0l-5-5m5 5V6" />
</svg>
</div>
<div class="ml-4 flex-1">
<dt class="text-sm font-medium text-gray-700">本月支出</dt>
<dd class="mt-1 text-2xl font-semibold text-red-700">
NT$ {{ number_format($monthlySummary['payments'], 2) }}
</dd>
</div>
</div>
</div>
<!-- Net Change -->
<div class="rounded-lg border border-blue-200 bg-blue-50 p-4">
<div class="flex items-center">
<div class="flex-shrink-0">
<svg class="h-10 w-10 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
</svg>
</div>
<div class="ml-4 flex-1">
<dt class="text-sm font-medium text-gray-700">淨變動</dt>
@php
$netChange = $monthlySummary['receipts'] - $monthlySummary['payments'];
@endphp
<dd class="mt-1 text-2xl font-semibold {{ $netChange >= 0 ? 'text-green-700' : 'text-red-700' }}">
{{ $netChange >= 0 ? '+' : '' }} NT$ {{ number_format($netChange, 2) }}
</dd>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Notes -->
<div class="bg-yellow-50 border border-yellow-200 rounded-lg p-4 print:hidden">
<div class="flex">
<div class="flex-shrink-0">
<svg class="h-5 w-5 text-yellow-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div class="ml-3">
<h3 class="text-sm font-medium text-yellow-800">報表說明</h3>
<div class="mt-2 text-sm text-yellow-700">
<ul class="list-disc pl-5 space-y-1">
<li>餘額為即時數據,以最後一筆分錄的交易後餘額為準</li>
<li>本月交易摘要統計當月 ({{ now()->format('Y-m') }}) 的所有交易</li>
<li>建議每日核對餘額,確保記錄正確</li>
<li>如發現餘額異常,請檢查分錄記錄是否有誤</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
@push('styles')
<style>
@media print {
.print\:hidden {
display: none !important;
}
body {
print-color-adjust: exact;
-webkit-print-color-adjust: exact;
}
}
</style>
@endpush
</x-app-layout>

View File

@@ -0,0 +1,212 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
記錄現金簿分錄
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-4xl sm:px-6 lg:px-8 space-y-4">
@if (session('error'))
<div class="rounded-md bg-red-50 p-4">
<p class="text-sm font-medium text-red-800">{{ session('error') }}</p>
</div>
@endif
<!-- Related Finance Document Info (if applicable) -->
@if($financeDocument)
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4">
<h3 class="text-sm font-medium text-blue-900 mb-2">關聯財務申請單</h3>
<dl class="grid grid-cols-1 gap-x-4 gap-y-2 sm:grid-cols-2 text-sm">
<div>
<dt class="font-medium text-blue-700">標題</dt>
<dd class="text-blue-900">{{ $financeDocument->title }}</dd>
</div>
<div>
<dt class="font-medium text-blue-700">金額</dt>
<dd class="text-blue-900">NT$ {{ number_format($financeDocument->amount, 2) }}</dd>
</div>
@if($financeDocument->paymentOrder)
<div>
<dt class="font-medium text-blue-700">付款單號</dt>
<dd class="text-blue-900 font-mono">{{ $financeDocument->paymentOrder->payment_order_number }}</dd>
</div>
<div>
<dt class="font-medium text-blue-700">付款方式</dt>
<dd class="text-blue-900">{{ $financeDocument->paymentOrder->getPaymentMethodText() }}</dd>
</div>
@endif
</dl>
</div>
@endif
<!-- Ledger Entry Form -->
<form method="POST" action="{{ route('admin.cashier-ledger.store') }}" class="space-y-4">
@csrf
@if($financeDocument)
<input type="hidden" name="finance_document_id" value="{{ $financeDocument->id }}">
@endif
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium leading-6 text-gray-900 mb-4">分錄資訊</h3>
<div class="grid grid-cols-1 gap-6">
<!-- Entry Date -->
<div>
<label for="entry_date" class="block text-sm font-medium text-gray-700">
記帳日期 <span class="text-red-500">*</span>
</label>
<input type="date" name="entry_date" id="entry_date" required
value="{{ old('entry_date', now()->format('Y-m-d')) }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm @error('entry_date') border-red-300 @enderror">
@error('entry_date')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Entry Type -->
<div>
<label for="entry_type" class="block text-sm font-medium text-gray-700">
類型 <span class="text-red-500">*</span>
</label>
<select name="entry_type" id="entry_type" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm @error('entry_type') border-red-300 @enderror">
<option value="">請選擇類型</option>
<option value="receipt" {{ old('entry_type') == 'receipt' ? 'selected' : '' }}>收入</option>
<option value="payment" {{ old('entry_type', $financeDocument ? 'payment' : '') == 'payment' ? 'selected' : '' }}>支出</option>
</select>
@error('entry_type')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Payment Method -->
<div>
<label for="payment_method" class="block text-sm font-medium text-gray-700">
付款方式 <span class="text-red-500">*</span>
</label>
<select name="payment_method" id="payment_method" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm @error('payment_method') border-red-300 @enderror">
<option value="">請選擇付款方式</option>
<option value="bank_transfer" {{ old('payment_method', $financeDocument && $financeDocument->paymentOrder ? $financeDocument->paymentOrder->payment_method : '') == 'bank_transfer' ? 'selected' : '' }}>銀行轉帳</option>
<option value="check" {{ old('payment_method', $financeDocument && $financeDocument->paymentOrder ? $financeDocument->paymentOrder->payment_method : '') == 'check' ? 'selected' : '' }}>支票</option>
<option value="cash" {{ old('payment_method', $financeDocument && $financeDocument->paymentOrder ? $financeDocument->paymentOrder->payment_method : '') == 'cash' ? 'selected' : '' }}>現金</option>
</select>
@error('payment_method')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Bank Account -->
<div>
<label for="bank_account" class="block text-sm font-medium text-gray-700">
銀行帳戶
</label>
<input type="text" name="bank_account" id="bank_account"
value="{{ old('bank_account', 'Main Account') }}"
placeholder="例如: Main Account, Petty Cash"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm @error('bank_account') border-red-300 @enderror">
<p class="mt-1 text-xs text-gray-500">用於區分不同的現金/銀行帳戶</p>
@error('bank_account')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Amount -->
<div>
<label for="amount" class="block text-sm font-medium text-gray-700">
金額 <span class="text-red-500">*</span>
</label>
<div class="relative mt-1 rounded-md shadow-sm">
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
<span class="text-gray-500 sm:text-sm">NT$</span>
</div>
<input type="number" name="amount" id="amount" step="0.01" min="0.01" required
value="{{ old('amount', $financeDocument && $financeDocument->paymentOrder ? $financeDocument->paymentOrder->payment_amount : '') }}"
class="block w-full rounded-md border-gray-300 pl-12 focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm @error('amount') border-red-300 @enderror">
</div>
@error('amount')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Receipt Number -->
<div>
<label for="receipt_number" class="block text-sm font-medium text-gray-700">
收據/憑證編號
</label>
<input type="text" name="receipt_number" id="receipt_number"
value="{{ old('receipt_number') }}"
placeholder="例如: RCP-2025-001"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm @error('receipt_number') border-red-300 @enderror">
@error('receipt_number')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Transaction Reference -->
<div>
<label for="transaction_reference" class="block text-sm font-medium text-gray-700">
交易參考號
</label>
<input type="text" name="transaction_reference" id="transaction_reference"
value="{{ old('transaction_reference', $financeDocument && $financeDocument->paymentOrder ? $financeDocument->paymentOrder->transaction_reference : '') }}"
placeholder="銀行交易編號或支票號碼"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm @error('transaction_reference') border-red-300 @enderror">
@error('transaction_reference')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Notes -->
<div>
<label for="notes" class="block text-sm font-medium text-gray-700">
備註
</label>
<textarea name="notes" id="notes" rows="3"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">{{ old('notes') }}</textarea>
@error('notes')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
</div>
</div>
</div>
<!-- Help Text -->
<div class="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
<div class="flex">
<div class="flex-shrink-0">
<svg class="h-5 w-5 text-yellow-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div class="ml-3">
<h3 class="text-sm font-medium text-yellow-800">注意事項</h3>
<div class="mt-2 text-sm text-yellow-700">
<ul class="list-disc pl-5 space-y-1">
<li>提交後將自動計算交易前後餘額</li>
<li>請確認金額和類型(收入/支出)正確</li>
<li>銀行帳戶用於區分不同帳戶的餘額</li>
<li>記錄後無法修改,請仔細確認</li>
</ul>
</div>
</div>
</div>
</div>
<!-- Form Actions -->
<div class="flex justify-end space-x-3">
<a href="{{ route('admin.cashier-ledger.index') }}" class="inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
取消
</a>
<button type="submit" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
記錄分錄
</button>
</div>
</form>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,204 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
出納現金簿
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8 space-y-4">
@if (session('status'))
<div class="rounded-md bg-green-50 p-4">
<p class="text-sm font-medium text-green-800">{{ session('status') }}</p>
</div>
@endif
@if (session('error'))
<div class="rounded-md bg-red-50 p-4">
<p class="text-sm font-medium text-red-800">{{ session('error') }}</p>
</div>
@endif
<!-- Action Buttons -->
<div class="flex justify-between">
<a href="{{ route('admin.cashier-ledger.balance-report') }}" class="inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
<svg class="-ml-1 mr-2 h-5 w-5 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
</svg>
餘額報表
</a>
<div class="flex space-x-3">
<a href="{{ route('admin.cashier-ledger.export', request()->all()) }}" class="inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
<svg class="-ml-1 mr-2 h-5 w-5 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
匯出 CSV
</a>
@can('record_cashier_ledger')
<a href="{{ route('admin.cashier-ledger.create') }}" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
<svg class="-ml-1 mr-2 h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
</svg>
記錄新分錄
</a>
@endcan
</div>
</div>
<!-- Current Balances Summary -->
@if(isset($balances) && $balances->isNotEmpty())
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium leading-6 text-gray-900 mb-4">當前餘額</h3>
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
@foreach($balances as $account => $balance)
<div class="rounded-lg border border-gray-200 p-4">
<dt class="text-sm font-medium text-gray-500">{{ $account }}</dt>
<dd class="mt-1 text-2xl font-semibold {{ $balance >= 0 ? 'text-green-600' : 'text-red-600' }}">
NT$ {{ number_format($balance, 2) }}
</dd>
</div>
@endforeach
</div>
</div>
</div>
@endif
<!-- Filters -->
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<form method="GET" action="{{ route('admin.cashier-ledger.index') }}" class="grid grid-cols-1 gap-4 sm:grid-cols-5">
<div>
<label for="entry_type" class="block text-sm font-medium text-gray-700">類型</label>
<select name="entry_type" id="entry_type" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
<option value="">全部</option>
<option value="receipt" {{ request('entry_type') == 'receipt' ? 'selected' : '' }}>收入</option>
<option value="payment" {{ request('entry_type') == 'payment' ? 'selected' : '' }}>支出</option>
</select>
</div>
<div>
<label for="payment_method" class="block text-sm font-medium text-gray-700">付款方式</label>
<select name="payment_method" id="payment_method" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
<option value="">全部</option>
<option value="bank_transfer" {{ request('payment_method') == 'bank_transfer' ? 'selected' : '' }}>銀行轉帳</option>
<option value="check" {{ request('payment_method') == 'check' ? 'selected' : '' }}>支票</option>
<option value="cash" {{ request('payment_method') == 'cash' ? 'selected' : '' }}>現金</option>
</select>
</div>
<div>
<label for="date_from" class="block text-sm font-medium text-gray-700">開始日期</label>
<input type="date" name="date_from" id="date_from" value="{{ request('date_from') }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
</div>
<div>
<label for="date_to" class="block text-sm font-medium text-gray-700">結束日期</label>
<input type="date" name="date_to" id="date_to" value="{{ request('date_to') }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
</div>
<div class="flex items-end">
<button type="submit" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
篩選
</button>
<a href="{{ route('admin.cashier-ledger.index') }}" class="ml-2 inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
清除
</a>
</div>
</form>
</div>
</div>
<!-- Ledger Entries Table -->
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
日期
</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
類型
</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
付款方式
</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
銀行帳戶
</th>
<th scope="col" class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500">
金額
</th>
<th scope="col" class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500">
交易前餘額
</th>
<th scope="col" class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500">
交易後餘額
</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
記錄人
</th>
<th scope="col" class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500">
<span class="sr-only">操作</span>
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
@forelse ($entries as $entry)
<tr class="{{ $entry->isReceipt() ? 'bg-green-50' : 'bg-red-50' }} bg-opacity-20">
<td class="whitespace-nowrap px-4 py-4 text-sm text-gray-900">
{{ $entry->entry_date->format('Y-m-d') }}
</td>
<td class="whitespace-nowrap px-4 py-4 text-sm">
<span class="inline-flex rounded-full px-2 text-xs font-semibold leading-5
{{ $entry->isReceipt() ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800' }}">
{{ $entry->getEntryTypeText() }}
</span>
</td>
<td class="whitespace-nowrap px-4 py-4 text-sm text-gray-500">
{{ $entry->getPaymentMethodText() }}
</td>
<td class="whitespace-nowrap px-4 py-4 text-sm text-gray-500">
{{ $entry->bank_account ?? 'N/A' }}
</td>
<td class="whitespace-nowrap px-4 py-4 text-sm text-right font-medium {{ $entry->isReceipt() ? 'text-green-600' : 'text-red-600' }}">
{{ $entry->isReceipt() ? '+' : '-' }} NT$ {{ number_format($entry->amount, 2) }}
</td>
<td class="whitespace-nowrap px-4 py-4 text-sm text-right text-gray-500">
NT$ {{ number_format($entry->balance_before, 2) }}
</td>
<td class="whitespace-nowrap px-4 py-4 text-sm text-right font-semibold text-gray-900">
NT$ {{ number_format($entry->balance_after, 2) }}
</td>
<td class="whitespace-nowrap px-4 py-4 text-sm text-gray-500">
{{ $entry->recordedByCashier->name ?? 'N/A' }}
</td>
<td class="whitespace-nowrap px-4 py-4 text-right text-sm font-medium">
<a href="{{ route('admin.cashier-ledger.show', $entry) }}" class="text-indigo-600 hover:text-indigo-900">
查看
</a>
</td>
</tr>
@empty
<tr>
<td colspan="9" class="px-4 py-8 text-center text-sm text-gray-500">
沒有現金簿記錄
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div class="mt-4">
{{ $entries->links() }}
</div>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,171 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
現金簿分錄詳情
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-4xl sm:px-6 lg:px-8 space-y-4">
<!-- Entry Info -->
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-medium leading-6 text-gray-900">分錄資訊</h3>
<span class="inline-flex rounded-full px-3 py-1 text-sm font-semibold
{{ $entry->isReceipt() ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800' }}">
{{ $entry->getEntryTypeText() }}
</span>
</div>
<dl class="grid grid-cols-1 gap-x-4 gap-y-4 sm:grid-cols-2">
<div>
<dt class="text-sm font-medium text-gray-500">記帳日期</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $entry->entry_date->format('Y-m-d') }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">付款方式</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $entry->getPaymentMethodText() }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">銀行帳戶</dt>
<dd class="mt-1 text-sm text-gray-900 font-mono">{{ $entry->bank_account ?? 'N/A' }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">金額</dt>
<dd class="mt-1 text-lg font-semibold {{ $entry->isReceipt() ? 'text-green-600' : 'text-red-600' }}">
{{ $entry->isReceipt() ? '+' : '-' }} NT$ {{ number_format($entry->amount, 2) }}
</dd>
</div>
@if($entry->receipt_number)
<div>
<dt class="text-sm font-medium text-gray-500">收據/憑證編號</dt>
<dd class="mt-1 text-sm text-gray-900 font-mono">{{ $entry->receipt_number }}</dd>
</div>
@endif
@if($entry->transaction_reference)
<div>
<dt class="text-sm font-medium text-gray-500">交易參考號</dt>
<dd class="mt-1 text-sm text-gray-900 font-mono">{{ $entry->transaction_reference }}</dd>
</div>
@endif
<div>
<dt class="text-sm font-medium text-gray-500">記錄人</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $entry->recordedByCashier->name }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">記錄時間</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $entry->recorded_at->format('Y-m-d H:i') }}</dd>
</div>
@if($entry->notes)
<div class="sm:col-span-2">
<dt class="text-sm font-medium text-gray-500">備註</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $entry->notes }}</dd>
</div>
@endif
</dl>
</div>
</div>
<!-- Balance Information -->
<div class="bg-gradient-to-r from-blue-50 to-indigo-50 shadow sm:rounded-lg border border-blue-200">
<div class="px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium leading-6 text-gray-900 mb-4">餘額變動</h3>
<div class="grid grid-cols-3 gap-4">
<div class="text-center p-4 bg-white rounded-lg shadow-sm">
<dt class="text-xs font-medium text-gray-500 uppercase">交易前餘額</dt>
<dd class="mt-2 text-2xl font-semibold text-gray-700">
{{ number_format($entry->balance_before, 2) }}
</dd>
</div>
<div class="text-center p-4 bg-white rounded-lg shadow-sm flex items-center justify-center">
<div>
<svg class="h-8 w-8 mx-auto {{ $entry->isReceipt() ? 'text-green-500' : 'text-red-500' }}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@if($entry->isReceipt())
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v3m0 0v3m0-3h3m-3 0H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z" />
@else
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z" />
@endif
</svg>
<div class="mt-1 text-lg font-semibold {{ $entry->isReceipt() ? 'text-green-600' : 'text-red-600' }}">
{{ $entry->isReceipt() ? '+' : '-' }} {{ number_format($entry->amount, 2) }}
</div>
</div>
</div>
<div class="text-center p-4 bg-white rounded-lg shadow-sm">
<dt class="text-xs font-medium text-gray-500 uppercase">交易後餘額</dt>
<dd class="mt-2 text-2xl font-semibold {{ $entry->balance_after >= 0 ? 'text-green-600' : 'text-red-600' }}">
{{ number_format($entry->balance_after, 2) }}
</dd>
</div>
</div>
</div>
</div>
<!-- Related Finance Document -->
@if($entry->financeDocument)
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium leading-6 text-gray-900 mb-4">關聯財務申請單</h3>
<dl class="grid grid-cols-1 gap-x-4 gap-y-4 sm:grid-cols-2">
<div>
<dt class="text-sm font-medium text-gray-500">申請標題</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $entry->financeDocument->title }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">申請類型</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $entry->financeDocument->getRequestTypeText() }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">申請金額</dt>
<dd class="mt-1 text-sm text-gray-900">NT$ {{ number_format($entry->financeDocument->amount, 2) }}</dd>
</div>
@if($entry->financeDocument->member)
<div>
<dt class="text-sm font-medium text-gray-500">關聯會員</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $entry->financeDocument->member->full_name }}</dd>
</div>
@endif
@if($entry->financeDocument->paymentOrder)
<div class="sm:col-span-2">
<dt class="text-sm font-medium text-gray-500">付款單號</dt>
<dd class="mt-1">
<a href="{{ route('admin.payment-orders.show', $entry->financeDocument->paymentOrder) }}" class="text-indigo-600 hover:text-indigo-900 font-mono">
{{ $entry->financeDocument->paymentOrder->payment_order_number }}
</a>
</dd>
</div>
@endif
</dl>
<div class="mt-4">
<a href="{{ route('admin.finance.show', $entry->financeDocument) }}" class="text-indigo-600 hover:text-indigo-900">
查看完整申請單
</a>
</div>
</div>
</div>
@endif
<!-- Actions -->
<div class="flex justify-between">
<a href="{{ route('admin.cashier-ledger.index') }}" class="inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
返回列表
</a>
@if($entry->financeDocument)
<a href="{{ route('admin.finance.show', $entry->financeDocument) }}" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
查看財務申請單
</a>
@endif
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,283 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
{{ __('Admin Dashboard') }}
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8">
<div class="space-y-6">
{{-- My Pending Approvals Alert --}}
@if ($myPendingApprovals > 0)
<div class="rounded-md bg-yellow-50 p-4" role="alert" aria-live="polite">
<div class="flex">
<div class="flex-shrink-0">
<svg class="h-5 w-5 text-yellow-400" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
</svg>
</div>
<div class="ml-3">
<p class="text-sm font-medium text-yellow-800">
{{ __('You have :count finance document(s) waiting for your approval.', ['count' => $myPendingApprovals]) }}
<a href="{{ route('admin.finance.index') }}" class="font-semibold underline hover:text-yellow-700">
{{ __('View pending approvals') }}
</a>
</p>
</div>
</div>
</div>
@endif
{{-- Stats Grid --}}
<div class="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-4">
{{-- Total Members --}}
<div class="overflow-hidden rounded-lg bg-white shadow">
<div class="p-5">
<div class="flex items-center">
<div class="flex-shrink-0">
<svg class="h-6 w-6 text-gray-400" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z" />
</svg>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="truncate text-sm font-medium text-gray-500">{{ __('Total Members') }}</dt>
<dd class="text-2xl font-semibold text-gray-900">{{ number_format($totalMembers) }}</dd>
</dl>
</div>
</div>
</div>
<div class="bg-gray-50 px-5 py-3">
<div class="text-sm">
<a href="{{ route('admin.members.index') }}" class="font-medium text-indigo-600 hover:text-indigo-900">
{{ __('View all') }}
</a>
</div>
</div>
</div>
{{-- Active Members --}}
<div class="overflow-hidden rounded-lg bg-white shadow">
<div class="p-5">
<div class="flex items-center">
<div class="flex-shrink-0">
<svg class="h-6 w-6 text-green-400" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="truncate text-sm font-medium text-gray-500">{{ __('Active Members') }}</dt>
<dd class="text-2xl font-semibold text-green-600">{{ number_format($activeMembers) }}</dd>
</dl>
</div>
</div>
</div>
<div class="bg-gray-50 px-5 py-3">
<div class="text-sm">
<a href="{{ route('admin.members.index', ['status' => 'active']) }}" class="font-medium text-indigo-600 hover:text-indigo-900">
{{ __('View active') }}
</a>
</div>
</div>
</div>
{{-- Expired Members --}}
<div class="overflow-hidden rounded-lg bg-white shadow">
<div class="p-5">
<div class="flex items-center">
<div class="flex-shrink-0">
<svg class="h-6 w-6 text-red-400" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" />
</svg>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="truncate text-sm font-medium text-gray-500">{{ __('Expired Members') }}</dt>
<dd class="text-2xl font-semibold text-red-600">{{ number_format($expiredMembers) }}</dd>
</dl>
</div>
</div>
</div>
<div class="bg-gray-50 px-5 py-3">
<div class="text-sm">
<a href="{{ route('admin.members.index', ['status' => 'expired']) }}" class="font-medium text-indigo-600 hover:text-indigo-900">
{{ __('View expired') }}
</a>
</div>
</div>
</div>
{{-- Expiring Soon --}}
<div class="overflow-hidden rounded-lg bg-white shadow">
<div class="p-5">
<div class="flex items-center">
<div class="flex-shrink-0">
<svg class="h-6 w-6 text-yellow-400" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="truncate text-sm font-medium text-gray-500">{{ __('Expiring in 30 Days') }}</dt>
<dd class="text-2xl font-semibold text-yellow-600">{{ number_format($expiringSoon) }}</dd>
</dl>
</div>
</div>
</div>
<div class="bg-gray-50 px-5 py-3">
<div class="text-sm">
<span class="text-gray-500">{{ __('Renewal reminders needed') }}</span>
</div>
</div>
</div>
</div>
{{-- Revenue Stats --}}
<div class="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3">
{{-- Total Revenue --}}
<div class="overflow-hidden rounded-lg bg-white shadow">
<div class="p-5">
<div class="flex items-center">
<div class="flex-shrink-0">
<svg class="h-6 w-6 text-gray-400" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v12m-3-2.818l.879.659c1.171.879 3.07.879 4.242 0 1.172-.879 1.172-2.303 0-3.182C13.536 12.219 12.768 12 12 12c-.725 0-1.45-.22-2.003-.659-1.106-.879-1.106-2.303 0-3.182s2.9-.879 4.006 0l.415.33M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="truncate text-sm font-medium text-gray-500">{{ __('Total Revenue') }}</dt>
<dd class="text-2xl font-semibold text-gray-900">${{ number_format($totalRevenue, 2) }}</dd>
</dl>
</div>
</div>
</div>
<div class="bg-gray-50 px-5 py-3">
<div class="text-sm text-gray-500">
{{ number_format($totalPayments) }} {{ __('total payments') }}
</div>
</div>
</div>
{{-- This Month Revenue --}}
<div class="overflow-hidden rounded-lg bg-white shadow">
<div class="p-5">
<div class="flex items-center">
<div class="flex-shrink-0">
<svg class="h-6 w-6 text-green-400" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 18.75a60.07 60.07 0 0115.797 2.101c.727.198 1.453-.342 1.453-1.096V18.75M3.75 4.5v.75A.75.75 0 013 6h-.75m0 0v-.375c0-.621.504-1.125 1.125-1.125H20.25M2.25 6v9m18-10.5v.75c0 .414.336.75.75.75h.75m-1.5-1.5h.375c.621 0 1.125.504 1.125 1.125v9.75c0 .621-.504 1.125-1.125 1.125h-.375m1.5-1.5H21a.75.75 0 00-.75.75v.75m0 0H3.75m0 0h-.375a1.125 1.125 0 01-1.125-1.125V15m1.5 1.5v-.75A.75.75 0 003 15h-.75M15 10.5a3 3 0 11-6 0 3 3 0 016 0zm3 0h.008v.008H18V10.5zm-12 0h.008v.008H6V10.5z" />
</svg>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="truncate text-sm font-medium text-gray-500">{{ __('This Month') }}</dt>
<dd class="text-2xl font-semibold text-green-600">${{ number_format($revenueThisMonth, 2) }}</dd>
</dl>
</div>
</div>
</div>
<div class="bg-gray-50 px-5 py-3">
<div class="text-sm text-gray-500">
{{ number_format($paymentsThisMonth) }} {{ __('payments this month') }}
</div>
</div>
</div>
{{-- Pending Approvals --}}
<div class="overflow-hidden rounded-lg bg-white shadow">
<div class="p-5">
<div class="flex items-center">
<div class="flex-shrink-0">
<svg class="h-6 w-6 text-blue-400" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12h3.75M9 15h3.75M9 18h3.75m3 .75H18a2.25 2.25 0 002.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 00-1.123-.08m-5.801 0c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 00.75-.75 2.25 2.25 0 00-.1-.664m-5.8 0A2.251 2.251 0 0113.5 2.25H15c1.012 0 1.867.668 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m0 0H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V9.375c0-.621-.504-1.125-1.125-1.125H8.25zM6.75 12h.008v.008H6.75V12zm0 3h.008v.008H6.75V15zm0 3h.008v.008H6.75V18z" />
</svg>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="truncate text-sm font-medium text-gray-500">{{ __('Finance Documents') }}</dt>
<dd class="text-2xl font-semibold text-blue-600">{{ number_format($pendingApprovals) }}</dd>
</dl>
</div>
</div>
</div>
<div class="bg-gray-50 px-5 py-3">
<div class="text-sm">
<a href="{{ route('admin.finance.index') }}" class="font-medium text-indigo-600 hover:text-indigo-900">
{{ __('View pending') }}
</a>
</div>
</div>
</div>
</div>
{{-- Recent Payments & Finance Stats --}}
<div class="grid grid-cols-1 gap-5 lg:grid-cols-2">
{{-- Recent Payments --}}
<div class="overflow-hidden rounded-lg bg-white shadow">
<div class="px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium leading-6 text-gray-900">{{ __('Recent Payments') }}</h3>
<div class="mt-4 flow-root">
@if ($recentPayments->count() > 0)
<ul role="list" class="-my-5 divide-y divide-gray-200">
@foreach ($recentPayments as $payment)
<li class="py-4">
<div class="flex items-center space-x-4">
<div class="min-w-0 flex-1">
<p class="truncate text-sm font-medium text-gray-900">
{{ $payment->member?->full_name ?? __('N/A') }}
</p>
<p class="truncate text-sm text-gray-500">
{{ $payment->paid_at?->format('Y-m-d') ?? __('N/A') }}
</p>
</div>
<div>
<span class="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-800">
${{ number_format($payment->amount, 2) }}
</span>
</div>
</div>
</li>
@endforeach
</ul>
@else
<p class="text-sm text-gray-500">{{ __('No recent payments.') }}</p>
@endif
</div>
</div>
</div>
{{-- Finance Document Stats --}}
<div class="overflow-hidden rounded-lg bg-white shadow">
<div class="px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium leading-6 text-gray-900">{{ __('Finance Document Status') }}</h3>
<div class="mt-6 space-y-4">
<div class="flex items-center justify-between">
<div class="flex items-center">
<span class="flex h-3 w-3 rounded-full bg-yellow-400"></span>
<span class="ml-3 text-sm font-medium text-gray-900">{{ __('Pending Approval') }}</span>
</div>
<span class="text-sm font-semibold text-gray-900">{{ number_format($pendingApprovals) }}</span>
</div>
<div class="flex items-center justify-between">
<div class="flex items-center">
<span class="flex h-3 w-3 rounded-full bg-green-400"></span>
<span class="ml-3 text-sm font-medium text-gray-900">{{ __('Fully Approved') }}</span>
</div>
<span class="text-sm font-semibold text-gray-900">{{ number_format($fullyApprovedDocs) }}</span>
</div>
<div class="flex items-center justify-between">
<div class="flex items-center">
<span class="flex h-3 w-3 rounded-full bg-red-400"></span>
<span class="ml-3 text-sm font-medium text-gray-900">{{ __('Rejected') }}</span>
</div>
<span class="text-sm font-semibold text-gray-900">{{ number_format($rejectedDocs) }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,100 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
新增文件類別
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-3xl sm:px-6 lg:px-8">
<div class="bg-white shadow sm:rounded-lg">
<form action="{{ route('admin.document-categories.store') }}" method="POST" class="p-6 space-y-6">
@csrf
<div>
<label for="name" class="block text-sm font-medium text-gray-700">
類別名稱 <span class="text-red-500">*</span>
</label>
<input type="text" name="name" id="name" value="{{ old('name') }}" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 @error('name') border-red-500 @enderror">
@error('name')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="slug" class="block text-sm font-medium text-gray-700">
代碼 (URL slug)
</label>
<input type="text" name="slug" id="slug" value="{{ old('slug') }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 @error('slug') border-red-500 @enderror">
<p class="mt-1 text-sm text-gray-500">留空則自動產生</p>
@error('slug')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="description" class="block text-sm font-medium text-gray-700">
說明
</label>
<textarea name="description" id="description" rows="3"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">{{ old('description') }}</textarea>
@error('description')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="icon" class="block text-sm font-medium text-gray-700">
圖示 (emoji)
</label>
<input type="text" name="icon" id="icon" value="{{ old('icon') }}" placeholder="📄"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
<p class="mt-1 text-sm text-gray-500">輸入 emoji例如📄 📝 📊 📋</p>
@error('icon')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="default_access_level" class="block text-sm font-medium text-gray-700">
預設存取權限 <span class="text-red-500">*</span>
</label>
<select name="default_access_level" id="default_access_level" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
<option value="public" {{ old('default_access_level') === 'public' ? 'selected' : '' }}>公開 (任何人可查看)</option>
<option value="members" {{ old('default_access_level') === 'members' ? 'selected' : '' }}>會員 (需登入且為會員)</option>
<option value="admin" {{ old('default_access_level') === 'admin' ? 'selected' : '' }}>管理員 (僅管理員可查看)</option>
<option value="board" {{ old('default_access_level') === 'board' ? 'selected' : '' }}>理事會 (僅理事會成員)</option>
</select>
@error('default_access_level')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="sort_order" class="block text-sm font-medium text-gray-700">
排序順序
</label>
<input type="number" name="sort_order" id="sort_order" value="{{ old('sort_order', 0) }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
<p class="mt-1 text-sm text-gray-500">數字越小越前面</p>
@error('sort_order')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div class="flex items-center justify-end space-x-4 pt-4">
<a href="{{ route('admin.document-categories.index') }}" class="rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50">
取消
</a>
<button type="submit" class="rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700">
建立類別
</button>
</div>
</form>
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,100 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
編輯文件類別
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-3xl sm:px-6 lg:px-8">
<div class="bg-white shadow sm:rounded-lg">
<form action="{{ route('admin.document-categories.update', $documentCategory) }}" method="POST" class="p-6 space-y-6">
@csrf
@method('PATCH')
<div>
<label for="name" class="block text-sm font-medium text-gray-700">
類別名稱 <span class="text-red-500">*</span>
</label>
<input type="text" name="name" id="name" value="{{ old('name', $documentCategory->name) }}" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 @error('name') border-red-500 @enderror">
@error('name')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="slug" class="block text-sm font-medium text-gray-700">
代碼 (URL slug)
</label>
<input type="text" name="slug" id="slug" value="{{ old('slug', $documentCategory->slug) }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 @error('slug') border-red-500 @enderror">
@error('slug')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="description" class="block text-sm font-medium text-gray-700">
說明
</label>
<textarea name="description" id="description" rows="3"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">{{ old('description', $documentCategory->description) }}</textarea>
@error('description')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="icon" class="block text-sm font-medium text-gray-700">
圖示 (emoji)
</label>
<input type="text" name="icon" id="icon" value="{{ old('icon', $documentCategory->icon) }}" placeholder="📄"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
<p class="mt-1 text-sm text-gray-500">輸入 emoji例如📄 📝 📊 📋</p>
@error('icon')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="default_access_level" class="block text-sm font-medium text-gray-700">
預設存取權限 <span class="text-red-500">*</span>
</label>
<select name="default_access_level" id="default_access_level" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
<option value="public" {{ old('default_access_level', $documentCategory->default_access_level) === 'public' ? 'selected' : '' }}>公開 (任何人可查看)</option>
<option value="members" {{ old('default_access_level', $documentCategory->default_access_level) === 'members' ? 'selected' : '' }}>會員 (需登入且為會員)</option>
<option value="admin" {{ old('default_access_level', $documentCategory->default_access_level) === 'admin' ? 'selected' : '' }}>管理員 (僅管理員可查看)</option>
<option value="board" {{ old('default_access_level', $documentCategory->default_access_level) === 'board' ? 'selected' : '' }}>理事會 (僅理事會成員)</option>
</select>
@error('default_access_level')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="sort_order" class="block text-sm font-medium text-gray-700">
排序順序
</label>
<input type="number" name="sort_order" id="sort_order" value="{{ old('sort_order', $documentCategory->sort_order) }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
<p class="mt-1 text-sm text-gray-500">數字越小越前面</p>
@error('sort_order')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div class="flex items-center justify-end space-x-4 pt-4">
<a href="{{ route('admin.document-categories.index') }}" class="rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50">
取消
</a>
<button type="submit" class="rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700">
更新類別
</button>
</div>
</form>
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,108 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
文件類別管理
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8 space-y-6">
@if (session('status'))
<div class="rounded-md bg-green-50 p-4">
<p class="text-sm font-medium text-green-800">{{ session('status') }}</p>
</div>
@endif
@if (session('error'))
<div class="rounded-md bg-red-50 p-4">
<p class="text-sm font-medium text-red-800">{{ session('error') }}</p>
</div>
@endif
<div class="flex justify-between items-center">
<div>
<h3 class="text-lg font-medium text-gray-900">文件類別</h3>
<p class="mt-1 text-sm text-gray-600">管理文件分類,設定預設存取權限</p>
</div>
<a href="{{ route('admin.document-categories.create') }}" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700">
<svg class="-ml-1 mr-2 h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
</svg>
新增類別
</a>
</div>
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">圖示</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">名稱</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">代碼</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">預設存取</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">文件數量</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">排序</th>
<th scope="col" class="px-6 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500">操作</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
@forelse ($categories as $category)
<tr>
<td class="px-6 py-4 whitespace-nowrap text-2xl">
{{ $category->getIconDisplay() }}
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm font-medium text-gray-900">{{ $category->name }}</div>
@if($category->description)
<div class="text-sm text-gray-500">{{ $category->description }}</div>
@endif
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<code class="px-2 py-1 bg-gray-100 rounded">{{ $category->slug }}</code>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="inline-flex rounded-full px-2 py-1 text-xs font-semibold
@if($category->default_access_level === 'public') bg-green-100 text-green-800
@elseif($category->default_access_level === 'members') bg-blue-100 text-blue-800
@elseif($category->default_access_level === 'admin') bg-purple-100 text-purple-800
@else bg-gray-100 text-gray-800
@endif">
{{ $category->getAccessLevelLabel() }}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{{ $category->active_documents_count }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{{ $category->sort_order }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium space-x-2">
<a href="{{ route('admin.document-categories.edit', $category) }}" class="text-indigo-600 hover:text-indigo-900">
編輯
</a>
<form action="{{ route('admin.document-categories.destroy', $category) }}" method="POST" class="inline" onsubmit="return confirm('確定要刪除此類別嗎?');">
@csrf
@method('DELETE')
<button type="submit" class="text-red-600 hover:text-red-900">
刪除
</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="7" class="px-6 py-4 text-center text-sm text-gray-500">
尚無類別資料
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,127 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
上傳文件
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-3xl sm:px-6 lg:px-8">
<div class="bg-white shadow sm:rounded-lg">
<form action="{{ route('admin.documents.store') }}" method="POST" enctype="multipart/form-data" class="p-6 space-y-6">
@csrf
<div>
<label for="document_category_id" class="block text-sm font-medium text-gray-700">
文件類別 <span class="text-red-500">*</span>
</label>
<select name="document_category_id" id="document_category_id" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 @error('document_category_id') border-red-500 @enderror">
<option value="">請選擇類別</option>
@foreach($categories as $category)
<option value="{{ $category->id }}" {{ old('document_category_id') == $category->id ? 'selected' : '' }}>
{{ $category->icon }} {{ $category->name }}
</option>
@endforeach
</select>
@error('document_category_id')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="title" class="block text-sm font-medium text-gray-700">
文件標題 <span class="text-red-500">*</span>
</label>
<input type="text" name="title" id="title" value="{{ old('title') }}" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 @error('title') border-red-500 @enderror">
@error('title')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="document_number" class="block text-sm font-medium text-gray-700">
文件編號
</label>
<input type="text" name="document_number" id="document_number" value="{{ old('document_number') }}"
placeholder="例如BYL-2024-001"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 @error('document_number') border-red-500 @enderror">
<p class="mt-1 text-sm text-gray-500">選填,用於正式文件編號</p>
@error('document_number')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="description" class="block text-sm font-medium text-gray-700">
文件說明
</label>
<textarea name="description" id="description" rows="3"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 @error('description') border-red-500 @enderror">{{ old('description') }}</textarea>
@error('description')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="access_level" class="block text-sm font-medium text-gray-700">
存取權限 <span class="text-red-500">*</span>
</label>
<select name="access_level" id="access_level" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 @error('access_level') border-red-500 @enderror">
<option value="public" {{ old('access_level') === 'public' ? 'selected' : '' }}>公開 (任何人可查看)</option>
<option value="members" {{ old('access_level') === 'members' ? 'selected' : '' }}>會員 (需登入且為會員)</option>
<option value="admin" {{ old('access_level') === 'admin' ? 'selected' : '' }}>管理員 (僅管理員可查看)</option>
<option value="board" {{ old('access_level') === 'board' ? 'selected' : '' }}>理事會 (僅理事會成員)</option>
</select>
@error('access_level')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="file" class="block text-sm font-medium text-gray-700">
上傳檔案 <span class="text-red-500">*</span>
</label>
<input type="file" name="file" id="file" required accept=".pdf,.doc,.docx,.xls,.xlsx,.txt"
class="mt-1 block w-full text-sm text-gray-500
file:mr-4 file:py-2 file:px-4
file:rounded-md file:border-0
file:text-sm file:font-semibold
file:bg-indigo-50 file:text-indigo-700
hover:file:bg-indigo-100
@error('file') border-red-500 @enderror">
<p class="mt-1 text-sm text-gray-500">支援格式PDF, Word, Excel, 文字檔,最大 10MB</p>
@error('file')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="version_notes" class="block text-sm font-medium text-gray-700">
版本說明
</label>
<textarea name="version_notes" id="version_notes" rows="2" placeholder="例如:初始版本"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">{{ old('version_notes') }}</textarea>
<p class="mt-1 text-sm text-gray-500">說明此版本的內容或變更</p>
@error('version_notes')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div class="border-t border-gray-200 pt-4">
<div class="flex items-center justify-end space-x-4">
<a href="{{ route('admin.documents.index') }}" class="rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50">
取消
</a>
<button type="submit" class="rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700">
上傳文件
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,109 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
編輯文件資訊
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-3xl sm:px-6 lg:px-8">
<div class="bg-white shadow sm:rounded-lg">
<form action="{{ route('admin.documents.update', $document) }}" method="POST" class="p-6 space-y-6">
@csrf
@method('PATCH')
<div>
<label for="document_category_id" class="block text-sm font-medium text-gray-700">
文件類別 <span class="text-red-500">*</span>
</label>
<select name="document_category_id" id="document_category_id" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
@foreach($categories as $category)
<option value="{{ $category->id }}" {{ old('document_category_id', $document->document_category_id) == $category->id ? 'selected' : '' }}>
{{ $category->icon }} {{ $category->name }}
</option>
@endforeach
</select>
@error('document_category_id')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="title" class="block text-sm font-medium text-gray-700">
文件標題 <span class="text-red-500">*</span>
</label>
<input type="text" name="title" id="title" value="{{ old('title', $document->title) }}" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
@error('title')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="document_number" class="block text-sm font-medium text-gray-700">
文件編號
</label>
<input type="text" name="document_number" id="document_number" value="{{ old('document_number', $document->document_number) }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
@error('document_number')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="description" class="block text-sm font-medium text-gray-700">
文件說明
</label>
<textarea name="description" id="description" rows="3"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">{{ old('description', $document->description) }}</textarea>
@error('description')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="access_level" class="block text-sm font-medium text-gray-700">
存取權限 <span class="text-red-500">*</span>
</label>
<select name="access_level" id="access_level" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
<option value="public" {{ old('access_level', $document->access_level) === 'public' ? 'selected' : '' }}>公開 (任何人可查看)</option>
<option value="members" {{ old('access_level', $document->access_level) === 'members' ? 'selected' : '' }}>會員 (需登入且為會員)</option>
<option value="admin" {{ old('access_level', $document->access_level) === 'admin' ? 'selected' : '' }}>管理員 (僅管理員可查看)</option>
<option value="board" {{ old('access_level', $document->access_level) === 'board' ? 'selected' : '' }}>理事會 (僅理事會成員)</option>
</select>
@error('access_level')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div class="bg-yellow-50 border border-yellow-200 rounded-md p-4">
<div class="flex">
<div class="flex-shrink-0">
<svg class="h-5 w-5 text-yellow-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
</svg>
</div>
<div class="ml-3">
<h3 class="text-sm font-medium text-yellow-800">注意</h3>
<div class="mt-2 text-sm text-yellow-700">
<p>此處僅更新文件資訊,不會變更檔案內容。如需更新檔案,請使用「上傳新版本」功能。</p>
</div>
</div>
</div>
</div>
<div class="flex items-center justify-end space-x-4 pt-4">
<a href="{{ route('admin.documents.show', $document) }}" class="rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50">
取消
</a>
<button type="submit" class="rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700">
更新資訊
</button>
</div>
</form>
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,186 @@
<x-app-layout>
<x-slot name="header">
<div class="flex items-center justify-between">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
文件管理
</h2>
<div class="flex items-center space-x-2">
<a href="{{ route('admin.documents.statistics') }}" class="inline-flex items-center rounded-md border border-gray-300 bg-white px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50">
📊 統計分析
</a>
<a href="{{ route('admin.documents.create') }}" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-3 py-2 text-sm font-medium text-white hover:bg-indigo-700">
+ 上傳文件
</a>
</div>
</div>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8 space-y-6">
@if (session('status'))
<div class="rounded-md bg-green-50 p-4">
<p class="text-sm font-medium text-green-800">{{ session('status') }}</p>
</div>
@endif
@if (session('error'))
<div class="rounded-md bg-red-50 p-4">
<p class="text-sm font-medium text-red-800">{{ session('error') }}</p>
</div>
@endif
<!-- Search and Filter -->
<div class="bg-white shadow sm:rounded-lg p-6">
<form method="GET" action="{{ route('admin.documents.index') }}" class="space-y-4">
<div class="grid grid-cols-1 gap-4 md:grid-cols-4">
<div>
<label for="search" class="block text-sm font-medium text-gray-700">搜尋</label>
<input type="text" name="search" id="search" value="{{ request('search') }}" placeholder="標題、文號..."
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
</div>
<div>
<label for="category" class="block text-sm font-medium text-gray-700">類別</label>
<select name="category" id="category" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
<option value="">全部類別</option>
@foreach($categories as $cat)
<option value="{{ $cat->id }}" {{ request('category') == $cat->id ? 'selected' : '' }}>
{{ $cat->icon }} {{ $cat->name }}
</option>
@endforeach
</select>
</div>
<div>
<label for="access_level" class="block text-sm font-medium text-gray-700">存取權限</label>
<select name="access_level" id="access_level" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
<option value="">全部</option>
<option value="public" {{ request('access_level') === 'public' ? 'selected' : '' }}>公開</option>
<option value="members" {{ request('access_level') === 'members' ? 'selected' : '' }}>會員</option>
<option value="admin" {{ request('access_level') === 'admin' ? 'selected' : '' }}>管理員</option>
<option value="board" {{ request('access_level') === 'board' ? 'selected' : '' }}>理事會</option>
</select>
</div>
<div>
<label for="status" class="block text-sm font-medium text-gray-700">狀態</label>
<select name="status" id="status" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
<option value="">全部</option>
<option value="active" {{ request('status') === 'active' ? 'selected' : '' }}>啟用</option>
<option value="archived" {{ request('status') === 'archived' ? 'selected' : '' }}>封存</option>
</select>
</div>
</div>
<div class="flex justify-end space-x-2">
<a href="{{ route('admin.documents.index') }}" class="rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50">
清除
</a>
<button type="submit" class="rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700">
搜尋
</button>
</div>
</form>
</div>
<div class="flex justify-between items-center">
<div>
<p class="text-sm text-gray-600"> {{ $documents->total() }} 個文件</p>
</div>
<a href="{{ route('admin.documents.create') }}" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700">
<svg class="-ml-1 mr-2 h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
</svg>
上傳文件
</a>
</div>
<!-- Documents Table -->
<div class="bg-white shadow overflow-hidden sm:rounded-lg">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">文件</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">類別</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">存取</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">版本</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">統計</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">狀態</th>
<th scope="col" class="px-6 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500">操作</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
@forelse ($documents as $document)
<tr class="hover:bg-gray-50">
<td class="px-6 py-4">
<div class="flex items-center">
<div class="text-2xl mr-3">
{{ $document->currentVersion?->getFileIcon() ?? '📄' }}
</div>
<div>
<div class="text-sm font-medium text-gray-900">{{ $document->title }}</div>
@if($document->document_number)
<div class="text-xs text-gray-500">{{ $document->document_number }}</div>
@endif
@if($document->description)
<div class="text-xs text-gray-500 mt-1">{{ Str::limit($document->description, 60) }}</div>
@endif
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{{ $document->category->icon }} {{ $document->category->name }}
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="inline-flex rounded-full px-2 py-1 text-xs font-semibold
@if($document->access_level === 'public') bg-green-100 text-green-800
@elseif($document->access_level === 'members') bg-blue-100 text-blue-800
@elseif($document->access_level === 'admin') bg-purple-100 text-purple-800
@else bg-gray-100 text-gray-800
@endif">
{{ $document->getAccessLevelLabel() }}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<div>v{{ $document->currentVersion?->version_number ?? '—' }}</div>
<div class="text-xs text-gray-400"> {{ $document->version_count }} </div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<div class="flex items-center space-x-3">
<span title="檢視次數">👁️ {{ $document->view_count }}</span>
<span title="下載次數">⬇️ {{ $document->download_count }}</span>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="inline-flex rounded-full px-2 py-1 text-xs font-semibold
{{ $document->status === 'active' ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800' }}">
{{ $document->getStatusLabel() }}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<a href="{{ route('admin.documents.show', $document) }}" class="text-indigo-600 hover:text-indigo-900">
查看
</a>
</td>
</tr>
@empty
<tr>
<td colspan="7" class="px-6 py-12 text-center">
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
<p class="mt-2 text-sm text-gray-500">尚無文件</p>
<p class="mt-1 text-sm text-gray-500">
<a href="{{ route('admin.documents.create') }}" class="text-indigo-600 hover:text-indigo-900">上傳第一個文件</a>
</p>
</td>
</tr>
@endforelse
</tbody>
</table>
@if($documents->hasPages())
<div class="px-6 py-4 border-t border-gray-200">
{{ $documents->links() }}
</div>
@endif
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,280 @@
<x-app-layout>
<x-slot name="header">
<div class="flex items-center justify-between">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
{{ $document->title }}
</h2>
<div class="flex items-center space-x-2">
<a href="{{ route('admin.documents.edit', $document) }}" class="inline-flex items-center rounded-md border border-gray-300 bg-white px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50">
編輯資訊
</a>
@if($document->status === 'active')
<form action="{{ route('admin.documents.archive', $document) }}" method="POST" class="inline">
@csrf
<button type="submit" class="inline-flex items-center rounded-md border border-gray-300 bg-white px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50">
封存
</button>
</form>
@else
<form action="{{ route('admin.documents.restore', $document) }}" method="POST" class="inline">
@csrf
<button type="submit" class="inline-flex items-center rounded-md border border-gray-300 bg-white px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50">
恢復
</button>
</form>
@endif
</div>
</div>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8 space-y-6">
@if (session('status'))
<div class="rounded-md bg-green-50 p-4">
<p class="text-sm font-medium text-green-800">{{ session('status') }}</p>
</div>
@endif
@if (session('error'))
<div class="rounded-md bg-red-50 p-4">
<p class="text-sm font-medium text-red-800">{{ session('error') }}</p>
</div>
@endif
<!-- Document Info -->
<div class="bg-white shadow sm:rounded-lg">
<div class="px-6 py-5">
<h3 class="text-lg font-medium leading-6 text-gray-900">文件資訊</h3>
</div>
<div class="border-t border-gray-200 px-6 py-5">
<dl class="grid grid-cols-1 gap-x-4 gap-y-6 sm:grid-cols-2">
<div>
<dt class="text-sm font-medium text-gray-500">類別</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $document->category->icon }} {{ $document->category->name }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">文件編號</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $document->document_number ?? '—' }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">存取權限</dt>
<dd class="mt-1">
<span class="inline-flex rounded-full px-2 py-1 text-xs font-semibold
@if($document->access_level === 'public') bg-green-100 text-green-800
@elseif($document->access_level === 'members') bg-blue-100 text-blue-800
@elseif($document->access_level === 'admin') bg-purple-100 text-purple-800
@else bg-gray-100 text-gray-800
@endif">
{{ $document->getAccessLevelLabel() }}
</span>
</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">狀態</dt>
<dd class="mt-1">
<span class="inline-flex rounded-full px-2 py-1 text-xs font-semibold
{{ $document->status === 'active' ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800' }}">
{{ $document->getStatusLabel() }}
</span>
</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">當前版本</dt>
<dd class="mt-1 text-sm text-gray-900">v{{ $document->currentVersion->version_number }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">總版本數</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $document->version_count }} 個版本</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">檢視 / 下載次數</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $document->view_count }} / {{ $document->download_count }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">公開連結</dt>
<dd class="mt-1 text-sm">
<a href="{{ $document->getPublicUrl() }}" target="_blank" class="text-indigo-600 hover:text-indigo-900">
{{ $document->getPublicUrl() }}
</a>
</dd>
</div>
@if($document->description)
<div class="sm:col-span-2">
<dt class="text-sm font-medium text-gray-500">說明</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $document->description }}</dd>
</div>
@endif
<div>
<dt class="text-sm font-medium text-gray-500">建立者</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $document->createdBy->name }} · {{ $document->created_at->format('Y-m-d H:i') }}</dd>
</div>
@if($document->lastUpdatedBy)
<div>
<dt class="text-sm font-medium text-gray-500">最後更新</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $document->lastUpdatedBy->name }} · {{ $document->updated_at->format('Y-m-d H:i') }}</dd>
</div>
@endif
</dl>
</div>
</div>
<!-- Upload New Version -->
<div class="bg-white shadow sm:rounded-lg">
<div class="px-6 py-5">
<h3 class="text-lg font-medium leading-6 text-gray-900">上傳新版本</h3>
</div>
<div class="border-t border-gray-200 px-6 py-5">
<form action="{{ route('admin.documents.upload-version', $document) }}" method="POST" enctype="multipart/form-data" class="space-y-4">
@csrf
<div>
<label for="file" class="block text-sm font-medium text-gray-700">選擇檔案 <span class="text-red-500">*</span></label>
<input type="file" name="file" id="file" required
class="mt-1 block w-full text-sm text-gray-500
file:mr-4 file:py-2 file:px-4
file:rounded-md file:border-0
file:text-sm file:font-semibold
file:bg-indigo-50 file:text-indigo-700
hover:file:bg-indigo-100">
<p class="mt-1 text-sm text-gray-500">最大 10MB</p>
</div>
<div>
<label for="version_notes" class="block text-sm font-medium text-gray-700">版本說明 <span class="text-red-500">*</span></label>
<textarea name="version_notes" id="version_notes" rows="2" required placeholder="說明此版本的變更內容"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"></textarea>
</div>
<div class="flex justify-end">
<button type="submit" class="rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700">
上傳新版本
</button>
</div>
</form>
</div>
</div>
<!-- Version History -->
<div class="bg-white shadow sm:rounded-lg">
<div class="px-6 py-5">
<h3 class="text-lg font-medium leading-6 text-gray-900">版本歷史</h3>
<p class="mt-1 text-sm text-gray-600">所有版本永久保留,無法刪除</p>
</div>
<div class="border-t border-gray-200">
<ul class="divide-y divide-gray-200">
@foreach($versionHistory as $history)
@php $version = $history['version']; @endphp
<li class="px-6 py-5">
<div class="flex items-center justify-between">
<div class="flex-1">
<div class="flex items-center space-x-3">
<span class="text-2xl">{{ $version->getFileIcon() }}</span>
<div>
<div class="flex items-center space-x-2">
<span class="text-sm font-medium text-gray-900">版本 {{ $version->version_number }}</span>
@if($version->is_current)
<span class="inline-flex rounded-full bg-green-100 px-2 py-1 text-xs font-semibold text-green-800">
當前版本
</span>
@endif
</div>
<div class="mt-1 text-sm text-gray-900">{{ $version->original_filename }}</div>
<div class="mt-1 flex items-center space-x-4 text-xs text-gray-500">
<span>{{ $version->getFileSizeHuman() }}</span>
<span>{{ $version->uploadedBy->name }}</span>
<span>{{ $version->uploaded_at->format('Y-m-d H:i') }}</span>
@if($history['days_since_previous'])
<span>({{ $history['days_since_previous'] }} 天前)</span>
@endif
</div>
@if($version->version_notes)
<div class="mt-2 text-sm text-gray-600">
<span class="font-medium">變更說明:</span>{{ $version->version_notes }}
</div>
@endif
<div class="mt-2 flex items-center space-x-2 text-xs text-gray-500">
<span>檔案雜湊:</span>
<code class="px-2 py-1 bg-gray-100 rounded">{{ substr($version->file_hash, 0, 16) }}...</code>
@if($version->verifyIntegrity())
<span class="text-green-600"> 完整</span>
@else
<span class="text-red-600"> 損壞</span>
@endif
</div>
</div>
</div>
</div>
<div class="ml-6 flex flex-col space-y-2">
<a href="{{ route('admin.documents.download-version', [$document, $version]) }}"
class="inline-flex items-center rounded-md border border-gray-300 bg-white px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50">
下載
</a>
@if(!$version->is_current)
<form action="{{ route('admin.documents.promote-version', [$document, $version]) }}" method="POST">
@csrf
<button type="submit" class="w-full inline-flex items-center justify-center rounded-md border border-indigo-300 bg-indigo-50 px-3 py-2 text-sm font-medium text-indigo-700 hover:bg-indigo-100"
onclick="return confirm('確定要將此版本設為當前版本嗎?');">
設為當前
</button>
</form>
@endif
</div>
</div>
</li>
@endforeach
</ul>
</div>
</div>
<!-- Access Logs -->
<div class="bg-white shadow sm:rounded-lg">
<div class="px-6 py-5">
<h3 class="text-lg font-medium leading-6 text-gray-900">存取記錄</h3>
<p class="mt-1 text-sm text-gray-600">最近 20 </p>
</div>
<div class="border-t border-gray-200">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">時間</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">使用者</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">動作</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">IP</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">瀏覽器</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
@forelse($document->accessLogs->take(20) as $log)
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{{ $log->accessed_at->format('Y-m-d H:i:s') }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{{ $log->getUserDisplay() }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm">
<span class="inline-flex rounded-full px-2 py-1 text-xs font-semibold
{{ $log->action === 'view' ? 'bg-blue-100 text-blue-800' : 'bg-green-100 text-green-800' }}">
{{ $log->getActionLabel() }}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{{ $log->ip_address }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{{ $log->getBrowser() }}
</td>
</tr>
@empty
<tr>
<td colspan="5" class="px-6 py-4 text-center text-sm text-gray-500">
尚無存取記錄
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,258 @@
<x-app-layout>
<x-slot name="header">
<div class="flex items-center justify-between">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
文件統計分析
</h2>
<a href="{{ route('admin.documents.index') }}" class="text-sm text-gray-600 hover:text-gray-900">
返回文件列表
</a>
</div>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8 space-y-6">
<!-- Summary Stats -->
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-5">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<div class="flex items-center">
<div class="flex-shrink-0 text-3xl">📚</div>
<div class="ml-4">
<div class="text-2xl font-bold text-gray-900">{{ $stats['total_documents'] }}</div>
<div class="text-sm text-gray-500">活躍文件</div>
</div>
</div>
</div>
</div>
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<div class="flex items-center">
<div class="flex-shrink-0 text-3xl">🔄</div>
<div class="ml-4">
<div class="text-2xl font-bold text-gray-900">{{ $stats['total_versions'] }}</div>
<div class="text-sm text-gray-500">總版本數</div>
</div>
</div>
</div>
</div>
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<div class="flex items-center">
<div class="flex-shrink-0 text-3xl">👁️</div>
<div class="ml-4">
<div class="text-2xl font-bold text-gray-900">{{ number_format($stats['total_views']) }}</div>
<div class="text-sm text-gray-500">總檢視次數</div>
</div>
</div>
</div>
</div>
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<div class="flex items-center">
<div class="flex-shrink-0 text-3xl">⬇️</div>
<div class="ml-4">
<div class="text-2xl font-bold text-gray-900">{{ number_format($stats['total_downloads']) }}</div>
<div class="text-sm text-gray-500">總下載次數</div>
</div>
</div>
</div>
</div>
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<div class="flex items-center">
<div class="flex-shrink-0 text-3xl">📦</div>
<div class="ml-4">
<div class="text-2xl font-bold text-gray-900">{{ $stats['archived_documents'] }}</div>
<div class="text-sm text-gray-500">已封存</div>
</div>
</div>
</div>
</div>
</div>
<!-- Documents by Category -->
<div class="bg-white shadow sm:rounded-lg">
<div class="px-6 py-5 border-b border-gray-200">
<h3 class="text-lg font-medium text-gray-900">各類別文件數量</h3>
</div>
<div class="px-6 py-5">
<div class="space-y-4">
@foreach($documentsByCategory as $category)
<div>
<div class="flex items-center justify-between mb-1">
<span class="text-sm font-medium text-gray-700">
{{ $category->icon }} {{ $category->name }}
</span>
<span class="text-sm font-bold text-gray-900">{{ $category->active_documents_count }}</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2">
<div class="bg-indigo-600 h-2 rounded-full"
style="width: {{ $stats['total_documents'] > 0 ? ($category->active_documents_count / $stats['total_documents'] * 100) : 0 }}%">
</div>
</div>
</div>
@endforeach
</div>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- Most Viewed -->
<div class="bg-white shadow sm:rounded-lg">
<div class="px-6 py-5 border-b border-gray-200">
<h3 class="text-lg font-medium text-gray-900">最常檢視文件</h3>
</div>
<div class="divide-y divide-gray-200">
@forelse($mostViewed as $document)
<div class="px-6 py-4">
<div class="flex items-center justify-between">
<div class="flex-1 min-w-0">
<a href="{{ route('admin.documents.show', $document) }}" class="text-sm font-medium text-gray-900 hover:text-indigo-600">
{{ $document->title }}
</a>
<p class="text-xs text-gray-500 mt-1">{{ $document->category->name }}</p>
</div>
<div class="ml-4 flex-shrink-0">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
{{ number_format($document->view_count) }}
</span>
</div>
</div>
</div>
@empty
<div class="px-6 py-8 text-center text-sm text-gray-500">尚無資料</div>
@endforelse
</div>
</div>
<!-- Most Downloaded -->
<div class="bg-white shadow sm:rounded-lg">
<div class="px-6 py-5 border-b border-gray-200">
<h3 class="text-lg font-medium text-gray-900">最常下載文件</h3>
</div>
<div class="divide-y divide-gray-200">
@forelse($mostDownloaded as $document)
<div class="px-6 py-4">
<div class="flex items-center justify-between">
<div class="flex-1 min-w-0">
<a href="{{ route('admin.documents.show', $document) }}" class="text-sm font-medium text-gray-900 hover:text-indigo-600">
{{ $document->title }}
</a>
<p class="text-xs text-gray-500 mt-1">{{ $document->category->name }}</p>
</div>
<div class="ml-4 flex-shrink-0">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
{{ number_format($document->download_count) }}
</span>
</div>
</div>
</div>
@empty
<div class="px-6 py-8 text-center text-sm text-gray-500">尚無資料</div>
@endforelse
</div>
</div>
</div>
<!-- Upload Trends -->
@if($uploadTrends->isNotEmpty())
<div class="bg-white shadow sm:rounded-lg">
<div class="px-6 py-5 border-b border-gray-200">
<h3 class="text-lg font-medium text-gray-900">上傳趨勢最近6個月</h3>
</div>
<div class="px-6 py-5">
<div class="space-y-3">
@foreach($uploadTrends as $trend)
<div class="flex items-center">
<div class="w-24 text-sm font-medium text-gray-700">{{ $trend->month }}</div>
<div class="flex-1">
<div class="flex items-center">
<div class="w-full bg-gray-200 rounded-full h-6 mr-3">
<div class="bg-indigo-600 h-6 rounded-full flex items-center justify-end pr-2"
style="width: {{ $uploadTrends->max('count') > 0 ? ($trend->count / $uploadTrends->max('count') * 100) : 0 }}%">
<span class="text-xs font-medium text-white">{{ $trend->count }}</span>
</div>
</div>
</div>
</div>
</div>
@endforeach
</div>
</div>
</div>
@endif
<!-- Access Level Distribution -->
<div class="bg-white shadow sm:rounded-lg">
<div class="px-6 py-5 border-b border-gray-200">
<h3 class="text-lg font-medium text-gray-900">存取權限分布</h3>
</div>
<div class="px-6 py-5">
<div class="grid grid-cols-2 gap-4 md:grid-cols-4">
@foreach($accessLevelStats as $stat)
<div class="text-center p-4 border rounded-lg">
<div class="text-3xl font-bold text-indigo-600">{{ $stat->count }}</div>
<div class="mt-1 text-sm text-gray-500">
@if($stat->access_level === 'public') 公開
@elseif($stat->access_level === 'members') 會員
@elseif($stat->access_level === 'admin') 管理員
@else 理事會
@endif
</div>
</div>
@endforeach
</div>
</div>
</div>
<!-- Recent Activity -->
@if($recentActivity->isNotEmpty())
<div class="bg-white shadow sm:rounded-lg">
<div class="px-6 py-5 border-b border-gray-200">
<h3 class="text-lg font-medium text-gray-900">最近活動30天內</h3>
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">時間</th>
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">使用者</th>
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">文件</th>
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">動作</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
@foreach($recentActivity as $log)
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{{ $log->accessed_at->format('Y-m-d H:i') }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{{ $log->getUserDisplay() }}
</td>
<td class="px-6 py-4 text-sm text-gray-900">
<a href="{{ route('admin.documents.show', $log->document) }}" class="hover:text-indigo-600">
{{ $log->document->title }}
</a>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm">
<span class="inline-flex rounded-full px-2 py-1 text-xs font-semibold
{{ $log->action === 'view' ? 'bg-blue-100 text-blue-800' : 'bg-green-100 text-green-800' }}">
{{ $log->getActionLabel() }}
</span>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@endif
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,117 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
{{ __('New Finance Document') }}
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-3xl sm:px-6 lg:px-8">
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<form method="POST" action="{{ route('admin.finance.store') }}" enctype="multipart/form-data" class="space-y-6" aria-label="{{ __('Finance document submission form') }}">
@csrf
<div>
<label for="member_id" class="block text-sm font-medium text-gray-700">
{{ __('Member (optional)') }}
</label>
<select
name="member_id"
id="member_id"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
>
<option value="">{{ __('Not linked to a member') }}</option>
@foreach ($members as $member)
<option value="{{ $member->id }}" @selected(old('member_id') == $member->id)>
{{ $member->full_name }}
</option>
@endforeach
</select>
@error('member_id')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="title" class="block text-sm font-medium text-gray-700">
{{ __('Title') }}
</label>
<input
type="text"
name="title"
id="title"
value="{{ old('title') }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
required
>
@error('title')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="amount" class="block text-sm font-medium text-gray-700">
{{ __('Amount') }}
</label>
<input
type="number"
step="0.01"
name="amount"
id="amount"
value="{{ old('amount') }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
>
@error('amount')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="description" class="block text-sm font-medium text-gray-700">
{{ __('Description') }}
</label>
<textarea
name="description"
id="description"
rows="4"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
>{{ old('description') }}</textarea>
@error('description')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="attachment" class="block text-sm font-medium text-gray-700">
{{ __('Attachment (optional)') }}
</label>
<input
type="file"
name="attachment"
id="attachment"
class="mt-1 block w-full text-sm text-gray-900 border border-gray-300 rounded-md cursor-pointer focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
>
@error('attachment')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
<p class="mt-1 text-sm text-gray-500">
{{ __('Max file size: 10MB') }}
</p>
</div>
<div class="flex justify-end gap-3">
<a href="{{ route('admin.finance.index') }}" class="inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
{{ __('Cancel') }}
</a>
<button type="submit" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
{{ __('Submit Document') }}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,95 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
{{ __('Finance Documents') }}
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8 space-y-4">
@if (session('status'))
<div class="rounded-md bg-green-50 p-4">
<p class="text-sm font-medium text-green-800">{{ session('status') }}</p>
</div>
@endif
<div class="flex justify-end">
<a href="{{ route('admin.finance.create') }}" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
{{ __('New Document') }}
</a>
</div>
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200" role="table">
<thead class="bg-gray-50">
<tr>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
{{ __('Title') }}
</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
{{ __('Member') }}
</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
{{ __('Amount') }}
</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
{{ __('Status') }}
</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
{{ __('Submitted At') }}
</th>
<th scope="col" class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500">
<span class="sr-only">{{ __('View') }}</span>
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
@forelse ($documents as $document)
<tr>
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900">
{{ $document->title }}
</td>
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900">
{{ $document->member?->full_name ?? __('N/A') }}
</td>
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900">
@if (! is_null($document->amount))
{{ number_format($document->amount, 2) }}
@else
{{ __('N/A') }}
@endif
</td>
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900">
{{ ucfirst($document->status) }}
</td>
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900">
{{ optional($document->submitted_at)->toDateString() }}
</td>
<td class="whitespace-nowrap px-4 py-3 text-right text-sm">
<a href="{{ route('admin.finance.show', $document) }}" class="text-indigo-600 hover:text-indigo-900">
{{ __('View') }}
</a>
</td>
</tr>
@empty
<tr>
<td colspan="6" class="px-4 py-4 text-sm text-gray-500">
{{ __('No finance documents found.') }}
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div class="mt-4">
{{ $documents->links() }}
</div>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,377 @@
<x-app-layout>
<x-slot name="header">
<div class="flex items-center justify-between">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
{{ __('Finance Document Details') }}
</h2>
<a href="{{ route('admin.finance.index') }}" class="text-sm text-indigo-600 hover:text-indigo-900">
&larr; {{ __('Back to list') }}
</a>
</div>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-4xl sm:px-6 lg:px-8 space-y-6">
{{-- Status Message --}}
@if (session('status'))
<div class="rounded-md bg-green-50 p-4" role="status" aria-live="polite">
<p class="text-sm font-medium text-green-800">
{{ session('status') }}
</p>
</div>
@endif
{{-- Document Details --}}
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium leading-6 text-gray-900 mb-4">
{{ __('Document Information') }}
</h3>
<dl class="grid grid-cols-1 gap-x-4 gap-y-6 sm:grid-cols-2">
<div>
<dt class="text-sm font-medium text-gray-500">{{ __('Title') }}</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $document->title }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">{{ __('Status') }}</dt>
<dd class="mt-1">
@if ($document->isRejected())
<span class="inline-flex rounded-full bg-red-100 px-2 text-xs font-semibold leading-5 text-red-800">
{{ $document->status_label }}
</span>
@elseif ($document->isFullyApproved())
<span class="inline-flex rounded-full bg-green-100 px-2 text-xs font-semibold leading-5 text-green-800">
{{ $document->status_label }}
</span>
@else
<span class="inline-flex rounded-full bg-yellow-100 px-2 text-xs font-semibold leading-5 text-yellow-800">
{{ $document->status_label }}
</span>
@endif
</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">{{ __('Member') }}</dt>
<dd class="mt-1 text-sm text-gray-900">
@if ($document->member)
<a href="{{ route('admin.members.show', $document->member) }}" class="text-indigo-600 hover:text-indigo-900">
{{ $document->member->full_name }}
</a>
@else
<span class="text-gray-500">{{ __('Not linked to a member') }}</span>
@endif
</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">{{ __('Amount') }}</dt>
<dd class="mt-1 text-sm text-gray-900">
@if (!is_null($document->amount))
${{ number_format($document->amount, 2) }}
@else
<span class="text-gray-500">{{ __('N/A') }}</span>
@endif
</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">{{ __('Submitted by') }}</dt>
<dd class="mt-1 text-sm text-gray-900">
{{ $document->submittedBy?->name ?? __('N/A') }}
</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">{{ __('Submitted at') }}</dt>
<dd class="mt-1 text-sm text-gray-900">
{{ $document->submitted_at?->format('Y-m-d H:i:s') ?? __('N/A') }}
</dd>
</div>
@if ($document->attachment_path)
<div class="sm:col-span-2">
<dt class="text-sm font-medium text-gray-500">{{ __('Attachment') }}</dt>
<dd class="mt-1 text-sm text-gray-900">
<a href="{{ route('admin.finance.download', $document) }}" class="inline-flex items-center rounded-md border border-gray-300 bg-white px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
<svg class="mr-2 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
{{ __('Download Attachment') }}
</a>
</dd>
</div>
@endif
@if ($document->description)
<div class="sm:col-span-2">
<dt class="text-sm font-medium text-gray-500">{{ __('Description') }}</dt>
<dd class="mt-1 whitespace-pre-line text-sm text-gray-900">
{{ $document->description }}
</dd>
</div>
@endif
</dl>
</div>
</div>
{{-- Approval Timeline --}}
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium leading-6 text-gray-900 mb-4">
{{ __('Approval Timeline') }}
</h3>
<div class="flow-root">
<ul role="list" class="-mb-8">
{{-- Cashier Approval --}}
<li>
<div class="relative pb-8">
<span class="absolute left-4 top-4 -ml-px h-full w-0.5 bg-gray-200" aria-hidden="true"></span>
<div class="relative flex space-x-3">
<div>
@if ($document->cashier_approved_at)
<span class="flex h-8 w-8 items-center justify-center rounded-full bg-green-500 ring-8 ring-white">
<svg class="h-5 w-5 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
</svg>
</span>
@else
<span class="flex h-8 w-8 items-center justify-center rounded-full bg-gray-400 ring-8 ring-white">
<span class="h-2.5 w-2.5 rounded-full bg-white"></span>
</span>
@endif
</div>
<div class="flex min-w-0 flex-1 justify-between space-x-4 pt-1.5">
<div>
<p class="text-sm text-gray-900">
{{ __('Cashier Approval') }}
@if ($document->cashier_approved_at)
<span class="font-medium text-gray-900">{{ $document->approvedByCashier?->name }}</span>
@endif
</p>
</div>
<div class="whitespace-nowrap text-right text-sm text-gray-500">
@if ($document->cashier_approved_at)
{{ $document->cashier_approved_at->format('Y-m-d H:i') }}
@else
{{ __('Pending') }}
@endif
</div>
</div>
</div>
</div>
</li>
{{-- Accountant Approval --}}
<li>
<div class="relative pb-8">
<span class="absolute left-4 top-4 -ml-px h-full w-0.5 bg-gray-200" aria-hidden="true"></span>
<div class="relative flex space-x-3">
<div>
@if ($document->accountant_approved_at)
<span class="flex h-8 w-8 items-center justify-center rounded-full bg-green-500 ring-8 ring-white">
<svg class="h-5 w-5 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
</svg>
</span>
@else
<span class="flex h-8 w-8 items-center justify-center rounded-full bg-gray-400 ring-8 ring-white">
<span class="h-2.5 w-2.5 rounded-full bg-white"></span>
</span>
@endif
</div>
<div class="flex min-w-0 flex-1 justify-between space-x-4 pt-1.5">
<div>
<p class="text-sm text-gray-900">
{{ __('Accountant Approval') }}
@if ($document->accountant_approved_at)
<span class="font-medium text-gray-900">{{ $document->approvedByAccountant?->name }}</span>
@endif
</p>
</div>
<div class="whitespace-nowrap text-right text-sm text-gray-500">
@if ($document->accountant_approved_at)
{{ $document->accountant_approved_at->format('Y-m-d H:i') }}
@else
{{ __('Pending') }}
@endif
</div>
</div>
</div>
</div>
</li>
{{-- Chair Approval --}}
<li>
<div class="relative pb-8">
<div class="relative flex space-x-3">
<div>
@if ($document->chair_approved_at)
<span class="flex h-8 w-8 items-center justify-center rounded-full bg-green-500 ring-8 ring-white">
<svg class="h-5 w-5 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
</svg>
</span>
@else
<span class="flex h-8 w-8 items-center justify-center rounded-full bg-gray-400 ring-8 ring-white">
<span class="h-2.5 w-2.5 rounded-full bg-white"></span>
</span>
@endif
</div>
<div class="flex min-w-0 flex-1 justify-between space-x-4 pt-1.5">
<div>
<p class="text-sm text-gray-900">
{{ __('Chair Approval') }}
@if ($document->chair_approved_at)
<span class="font-medium text-gray-900">{{ $document->approvedByChair?->name }}</span>
@endif
</p>
</div>
<div class="whitespace-nowrap text-right text-sm text-gray-500">
@if ($document->chair_approved_at)
{{ $document->chair_approved_at->format('Y-m-d H:i') }}
@else
{{ __('Pending') }}
@endif
</div>
</div>
</div>
</div>
</li>
{{-- Rejection Info --}}
@if ($document->isRejected())
<li>
<div class="relative">
<div class="relative flex space-x-3">
<div>
<span class="flex h-8 w-8 items-center justify-center rounded-full bg-red-500 ring-8 ring-white">
<svg class="h-5 w-5 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" />
</svg>
</span>
</div>
<div class="flex min-w-0 flex-1 justify-between space-x-4 pt-1.5">
<div class="flex-1">
<p class="text-sm text-gray-900">
{{ __('Rejected by') }}
<span class="font-medium text-gray-900">{{ $document->rejectedBy?->name }}</span>
</p>
@if ($document->rejection_reason)
<p class="mt-2 text-sm text-red-600">
<strong>{{ __('Reason:') }}</strong> {{ $document->rejection_reason }}
</p>
@endif
</div>
<div class="whitespace-nowrap text-right text-sm text-gray-500">
{{ $document->rejected_at?->format('Y-m-d H:i') }}
</div>
</div>
</div>
</div>
</li>
@endif
</ul>
</div>
</div>
</div>
{{-- Approval Actions --}}
@if (!$document->isRejected() && !$document->isFullyApproved())
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium leading-6 text-gray-900 mb-4">
{{ __('Actions') }}
</h3>
<div class="flex gap-3">
{{-- Approve Button --}}
@php
$canApprove = false;
if (auth()->user()->hasRole('cashier') && $document->canBeApprovedByCashier()) {
$canApprove = true;
} elseif (auth()->user()->hasRole('accountant') && $document->canBeApprovedByAccountant()) {
$canApprove = true;
} elseif (auth()->user()->hasRole('chair') && $document->canBeApprovedByChair()) {
$canApprove = true;
}
@endphp
@if ($canApprove)
<form method="POST" action="{{ route('admin.finance.approve', $document) }}" class="inline">
@csrf
<button type="submit" class="inline-flex items-center rounded-md border border-transparent bg-green-600 px-4 py-2 text-sm font-medium text-white hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2">
<svg class="mr-2 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
</svg>
{{ __('Approve') }}
</button>
</form>
@endif
{{-- Reject Button (show for cashier, accountant, chair) --}}
@if (auth()->user()->hasRole('cashier') || auth()->user()->hasRole('accountant') || auth()->user()->hasRole('chair'))
<button type="button" onclick="document.getElementById('rejectModal').classList.remove('hidden')" class="inline-flex items-center rounded-md border border-transparent bg-red-600 px-4 py-2 text-sm font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2">
<svg class="mr-2 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
{{ __('Reject') }}
</button>
@endif
</div>
</div>
</div>
@endif
</div>
</div>
{{-- Rejection Modal --}}
<div id="rejectModal" class="fixed inset-0 z-10 hidden overflow-y-auto" aria-labelledby="modal-title" role="dialog" aria-modal="true">
<div class="flex min-h-screen items-end justify-center px-4 pb-20 pt-4 text-center sm:block sm:p-0">
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" onclick="document.getElementById('rejectModal').classList.add('hidden')"></div>
<div class="inline-block transform overflow-hidden rounded-lg bg-white text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:align-middle">
<form method="POST" action="{{ route('admin.finance.reject', $document) }}">
@csrf
<div class="bg-white px-4 pb-4 pt-5 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div class="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
<svg class="h-6 w-6 text-red-600" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" />
</svg>
</div>
<div class="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left flex-1">
<h3 class="text-base font-semibold leading-6 text-gray-900" id="modal-title">
{{ __('Reject Document') }}
</h3>
<div class="mt-4">
<label for="rejection_reason" class="block text-sm font-medium text-gray-700">
{{ __('Rejection Reason') }}
</label>
<textarea
name="rejection_reason"
id="rejection_reason"
rows="4"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-red-500 focus:ring-red-500 sm:text-sm"
required
></textarea>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6 gap-3">
<button type="submit" class="inline-flex w-full justify-center rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 sm:w-auto">
{{ __('Reject') }}
</button>
<button type="button" onclick="document.getElementById('rejectModal').classList.add('hidden')" class="mt-3 inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 sm:mt-0 sm:w-auto">
{{ __('Cancel') }}
</button>
</div>
</form>
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,80 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
{{ __('Create Label') }}
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-2xl sm:px-6 lg:px-8">
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<form method="POST" action="{{ route('admin.issue-labels.store') }}" class="space-y-6">
@csrf
<div>
<label for="name" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Name') }} <span class="text-red-500">*</span>
</label>
<input type="text" name="name" id="name" value="{{ old('name') }}" required maxlength="255"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100 @error('name') border-red-300 @enderror"
placeholder="{{ __('e.g., urgent, bug, enhancement') }}">
@error('name')<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>@enderror
</div>
<div>
<label for="color" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Color') }} <span class="text-red-500">*</span>
</label>
<div class="mt-1 flex gap-2">
<input type="color" name="color" id="color" value="{{ old('color', '#6B7280') }}" required
class="h-10 w-20 rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:border-gray-600">
<input type="text" id="color-text" value="{{ old('color', '#6B7280') }}" maxlength="7"
class="flex-1 rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100"
placeholder="#000000">
</div>
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">{{ __('Choose a color for this label') }}</p>
@error('color')<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>@enderror
</div>
<div>
<label for="description" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Description') }}
</label>
<textarea name="description" id="description" rows="3" maxlength="500"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100"
placeholder="{{ __('Optional description...') }}">{{ old('description') }}</textarea>
@error('description')<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>@enderror
</div>
<div class="flex items-center justify-end gap-x-4 border-t border-gray-200 pt-6 dark:border-gray-700">
<a href="{{ route('admin.issue-labels.index') }}"
class="rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 dark:bg-gray-700 dark:text-gray-100 dark:ring-gray-600">
{{ __('Cancel') }}
</a>
<button type="submit"
class="inline-flex justify-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 dark:bg-indigo-500 dark:hover:bg-indigo-400">
{{ __('Create Label') }}
</button>
</div>
</form>
</div>
</div>
</div>
<script>
// Sync color picker and text input
const colorPicker = document.getElementById('color');
const colorText = document.getElementById('color-text');
colorPicker.addEventListener('input', (e) => {
colorText.value = e.target.value.toUpperCase();
});
colorText.addEventListener('input', (e) => {
const value = e.target.value;
if (/^#[0-9A-Fa-f]{6}$/.test(value)) {
colorPicker.value = value;
}
});
</script>
</x-app-layout>

View File

@@ -0,0 +1,77 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
{{ __('Edit Label') }} - {{ $issueLabel->name }}
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-2xl sm:px-6 lg:px-8">
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<form method="POST" action="{{ route('admin.issue-labels.update', $issueLabel) }}" class="space-y-6">
@csrf
@method('PATCH')
<div>
<label for="name" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Name') }} <span class="text-red-500">*</span>
</label>
<input type="text" name="name" id="name" value="{{ old('name', $issueLabel->name) }}" required maxlength="255"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100 @error('name') border-red-300 @enderror">
@error('name')<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>@enderror
</div>
<div>
<label for="color" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Color') }} <span class="text-red-500">*</span>
</label>
<div class="mt-1 flex gap-2">
<input type="color" name="color" id="color" value="{{ old('color', $issueLabel->color) }}" required
class="h-10 w-20 rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:border-gray-600">
<input type="text" id="color-text" value="{{ old('color', $issueLabel->color) }}" maxlength="7"
class="flex-1 rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
</div>
@error('color')<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>@enderror
</div>
<div>
<label for="description" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Description') }}
</label>
<textarea name="description" id="description" rows="3" maxlength="500"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">{{ old('description', $issueLabel->description) }}</textarea>
@error('description')<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>@enderror
</div>
<div class="flex items-center justify-end gap-x-4 border-t border-gray-200 pt-6 dark:border-gray-700">
<a href="{{ route('admin.issue-labels.index') }}"
class="rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 dark:bg-gray-700 dark:text-gray-100 dark:ring-gray-600">
{{ __('Cancel') }}
</a>
<button type="submit"
class="inline-flex justify-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 dark:bg-indigo-500 dark:hover:bg-indigo-400">
{{ __('Update Label') }}
</button>
</div>
</form>
</div>
</div>
</div>
<script>
// Sync color picker and text input
const colorPicker = document.getElementById('color');
const colorText = document.getElementById('color-text');
colorPicker.addEventListener('input', (e) => {
colorText.value = e.target.value.toUpperCase();
});
colorText.addEventListener('input', (e) => {
const value = e.target.value;
if (/^#[0-9A-Fa-f]{6}$/.test(value)) {
colorPicker.value = value;
}
});
</script>
</x-app-layout>

View File

@@ -0,0 +1,79 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
{{ __('Issue Labels') }} (標籤管理)
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8">
@if (session('status'))
<div class="mb-4 rounded-md bg-green-50 p-4 dark:bg-green-900/30 border-l-4 border-green-400" role="status">
<p class="text-sm text-green-800 dark:text-green-200">{{ session('status') }}</p>
</div>
@endif
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<div class="sm:flex sm:items-center sm:justify-between mb-6">
<div>
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">{{ __('Manage Labels') }}</h3>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">{{ __('Create and manage labels for categorizing issues') }}</p>
</div>
<div class="mt-4 sm:mt-0">
<a href="{{ route('admin.issue-labels.create') }}" class="inline-flex items-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 dark:bg-indigo-500 dark:hover:bg-indigo-400">
<svg class="-ml-0.5 mr-1.5 h-5 w-5" fill="currentColor" viewBox="0 0 20 20"><path d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z"/></svg>
{{ __('Create Label') }}
</a>
</div>
</div>
<div class="overflow-hidden shadow ring-1 ring-black ring-opacity-5 sm:rounded-lg dark:ring-gray-700">
<table class="min-w-full divide-y divide-gray-300 dark:divide-gray-600">
<thead class="bg-gray-50 dark:bg-gray-900">
<tr>
<th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Label') }}</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Description') }}</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Issues') }}</th>
<th scope="col" class="relative py-3.5 pl-3 pr-4"><span class="sr-only">{{ __('Actions') }}</span></th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white dark:divide-gray-700 dark:bg-gray-800">
@forelse($labels as $label)
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
<td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm">
<span class="inline-flex items-center rounded-full px-3 py-1 text-sm font-medium"
style="background-color: {{ $label->color }}; color: {{ $label->text_color }}">
{{ $label->name }}
</span>
</td>
<td class="px-3 py-4 text-sm text-gray-900 dark:text-gray-100">
{{ $label->description ?? '—' }}
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900 dark:text-gray-100">
{{ $label->issues_count }}
</td>
<td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium">
<a href="{{ route('admin.issue-labels.edit', $label) }}" class="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300 mr-4">{{ __('Edit') }}</a>
@if($label->issues_count === 0)
<form method="POST" action="{{ route('admin.issue-labels.destroy', $label) }}" class="inline" onsubmit="return confirm('{{ __('Delete this label?') }}')">
@csrf
@method('DELETE')
<button type="submit" class="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300">{{ __('Delete') }}</button>
</form>
@endif
</td>
</tr>
@empty
<tr>
<td colspan="4" class="px-3 py-8 text-center text-sm text-gray-500 dark:text-gray-400">
<p>{{ __('No labels found') }}</p>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,280 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
{{ __('Issue Reports & Analytics') }}
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8 space-y-6">
{{-- Date Range Filter --}}
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<form method="GET" action="{{ route('admin.issue-reports.index') }}" class="flex flex-wrap gap-4 items-end">
<div class="flex-1 min-w-[200px]">
<label for="start_date" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Start Date') }}
</label>
<input type="date" name="start_date" id="start_date" value="{{ $startDate->format('Y-m-d') }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
</div>
<div class="flex-1 min-w-[200px]">
<label for="end_date" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('End Date') }}
</label>
<input type="date" name="end_date" id="end_date" value="{{ $endDate->format('Y-m-d') }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
</div>
<button type="submit" class="inline-flex justify-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 dark:bg-indigo-500 dark:hover:bg-indigo-400">
{{ __('Apply Filter') }}
</button>
</form>
</div>
{{-- Summary Statistics --}}
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg px-4 py-5 border-l-4 border-blue-400">
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Total Issues') }}</dt>
<dd class="mt-1 text-3xl font-semibold text-gray-900 dark:text-gray-100">{{ number_format($stats['total_issues']) }}</dd>
</div>
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg px-4 py-5 border-l-4 border-green-400">
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Open Issues') }}</dt>
<dd class="mt-1 text-3xl font-semibold text-gray-900 dark:text-gray-100">{{ number_format($stats['open_issues']) }}</dd>
</div>
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg px-4 py-5 border-l-4 border-gray-400">
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Closed Issues') }}</dt>
<dd class="mt-1 text-3xl font-semibold text-gray-900 dark:text-gray-100">{{ number_format($stats['closed_issues']) }}</dd>
</div>
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg px-4 py-5 border-l-4 border-red-400">
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Overdue Issues') }}</dt>
<dd class="mt-1 text-3xl font-semibold text-gray-900 dark:text-gray-100">{{ number_format($stats['overdue_issues']) }}</dd>
</div>
</div>
{{-- Charts Section --}}
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
{{-- Issues by Status --}}
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">{{ __('Issues by Status') }}</h3>
<div class="space-y-2">
@foreach(['new', 'assigned', 'in_progress', 'review', 'closed'] as $status)
<div class="flex items-center justify-between">
<span class="text-sm text-gray-600 dark:text-gray-400">{{ ucfirst(str_replace('_', ' ', $status)) }}</span>
<span class="text-sm font-semibold text-gray-900 dark:text-gray-100">{{ $issuesByStatus[$status] ?? 0 }}</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2 dark:bg-gray-700">
@php
$percentage = $stats['total_issues'] > 0 ? (($issuesByStatus[$status] ?? 0) / $stats['total_issues']) * 100 : 0;
@endphp
<div class="bg-indigo-600 h-2 rounded-full" style="width: {{ $percentage }}%"></div>
</div>
@endforeach
</div>
</div>
{{-- Issues by Priority --}}
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">{{ __('Issues by Priority') }}</h3>
<div class="space-y-2">
@foreach(['low', 'medium', 'high', 'urgent'] as $priority)
@php
$colors = ['low' => 'green', 'medium' => 'yellow', 'high' => 'orange', 'urgent' => 'red'];
@endphp
<div class="flex items-center justify-between">
<span class="text-sm text-gray-600 dark:text-gray-400">{{ ucfirst($priority) }}</span>
<span class="text-sm font-semibold text-gray-900 dark:text-gray-100">{{ $issuesByPriority[$priority] ?? 0 }}</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2 dark:bg-gray-700">
@php
$percentage = $stats['total_issues'] > 0 ? (($issuesByPriority[$priority] ?? 0) / $stats['total_issues']) * 100 : 0;
@endphp
<div class="bg-{{ $colors[$priority] }}-600 h-2 rounded-full" style="width: {{ $percentage }}%"></div>
</div>
@endforeach
</div>
</div>
{{-- Issues by Type --}}
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">{{ __('Issues by Type') }}</h3>
<div class="space-y-2">
@foreach(['work_item', 'project_task', 'maintenance', 'member_request'] as $type)
<div class="flex items-center justify-between">
<span class="text-sm text-gray-600 dark:text-gray-400">{{ ucfirst(str_replace('_', ' ', $type)) }}</span>
<span class="text-sm font-semibold text-gray-900 dark:text-gray-100">{{ $issuesByType[$type] ?? 0 }}</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2 dark:bg-gray-700">
@php
$percentage = $stats['total_issues'] > 0 ? (($issuesByType[$type] ?? 0) / $stats['total_issues']) * 100 : 0;
@endphp
<div class="bg-blue-600 h-2 rounded-full" style="width: {{ $percentage }}%"></div>
</div>
@endforeach
</div>
</div>
</div>
{{-- Time Tracking Metrics --}}
@if($timeTrackingMetrics && $timeTrackingMetrics->total_estimated > 0)
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">{{ __('Time Tracking Metrics') }}</h3>
<div class="grid grid-cols-1 sm:grid-cols-4 gap-4">
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Total Estimated Hours') }}</dt>
<dd class="mt-1 text-2xl font-semibold text-gray-900 dark:text-gray-100">{{ number_format($timeTrackingMetrics->total_estimated, 1) }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Total Actual Hours') }}</dt>
<dd class="mt-1 text-2xl font-semibold text-gray-900 dark:text-gray-100">{{ number_format($timeTrackingMetrics->total_actual, 1) }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Avg Estimated Hours') }}</dt>
<dd class="mt-1 text-2xl font-semibold text-gray-900 dark:text-gray-100">{{ number_format($timeTrackingMetrics->avg_estimated, 1) }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Avg Actual Hours') }}</dt>
<dd class="mt-1 text-2xl font-semibold text-gray-900 dark:text-gray-100">{{ number_format($timeTrackingMetrics->avg_actual, 1) }}</dd>
</div>
</div>
@php
$variance = $timeTrackingMetrics->total_actual - $timeTrackingMetrics->total_estimated;
$variancePercentage = $timeTrackingMetrics->total_estimated > 0 ? ($variance / $timeTrackingMetrics->total_estimated) * 100 : 0;
@endphp
<div class="mt-4">
<p class="text-sm text-gray-600 dark:text-gray-400">
{{ __('Variance') }}:
<span class="font-semibold {{ $variance > 0 ? 'text-red-600 dark:text-red-400' : 'text-green-600 dark:text-green-400' }}">
{{ $variance > 0 ? '+' : '' }}{{ number_format($variance, 1) }} hours ({{ number_format($variancePercentage, 1) }}%)
</span>
</p>
</div>
</div>
@endif
{{-- Average Resolution Time --}}
@if($avgResolutionTime)
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-2">{{ __('Average Resolution Time') }}</h3>
<p class="text-3xl font-semibold text-gray-900 dark:text-gray-100">{{ number_format($avgResolutionTime, 1) }} {{ __('days') }}</p>
</div>
@endif
{{-- Assignee Performance --}}
@if($assigneePerformance->isNotEmpty())
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">{{ __('Assignee Performance (Top 10)') }}</h3>
<div class="overflow-hidden shadow ring-1 ring-black ring-opacity-5 sm:rounded-lg dark:ring-gray-700">
<table class="min-w-full divide-y divide-gray-300 dark:divide-gray-600">
<thead class="bg-gray-50 dark:bg-gray-900">
<tr>
<th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Assignee') }}</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Total Assigned') }}</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Completed') }}</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Overdue') }}</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Completion Rate') }}</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white dark:divide-gray-700 dark:bg-gray-800">
@foreach($assigneePerformance as $user)
<tr>
<td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 dark:text-gray-100">
{{ $user->name }}
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900 dark:text-gray-100">
{{ $user->total_assigned }}
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900 dark:text-gray-100">
{{ $user->completed }}
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900 dark:text-gray-100">
<span class="{{ $user->overdue > 0 ? 'text-red-600 dark:text-red-400' : '' }}">
{{ $user->overdue }}
</span>
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900 dark:text-gray-100">
<div class="flex items-center gap-2">
<div class="flex-1 bg-gray-200 rounded-full h-2 dark:bg-gray-700 min-w-[60px]">
<div class="bg-green-600 h-2 rounded-full" style="width: {{ $user->completion_rate }}%"></div>
</div>
<span class="text-sm font-medium">{{ $user->completion_rate }}%</span>
</div>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@endif
{{-- Top Labels Used --}}
@if($topLabels->isNotEmpty())
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">{{ __('Top Labels Used') }}</h3>
<div class="space-y-3">
@foreach($topLabels as $label)
<div class="flex items-center justify-between">
<div class="flex items-center gap-3">
<span class="inline-flex items-center rounded-full px-3 py-1 text-sm font-medium"
style="background-color: {{ $label->color }}; color: {{ \App\Models\IssueLabel::find($label->id)->text_color ?? '#000000' }}">
{{ $label->name }}
</span>
</div>
<span class="text-sm font-semibold text-gray-900 dark:text-gray-100">{{ $label->usage_count }} {{ __('uses') }}</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2 dark:bg-gray-700">
@php
$maxUsage = $topLabels->max('usage_count');
$percentage = $maxUsage > 0 ? ($label->usage_count / $maxUsage) * 100 : 0;
@endphp
<div class="h-2 rounded-full" style="width: {{ $percentage }}%; background-color: {{ $label->color }}"></div>
</div>
@endforeach
</div>
</div>
@endif
{{-- Recent Issues --}}
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">{{ __('Recent Issues') }}</h3>
<div class="overflow-hidden shadow ring-1 ring-black ring-opacity-5 sm:rounded-lg dark:ring-gray-700">
<table class="min-w-full divide-y divide-gray-300 dark:divide-gray-600">
<thead class="bg-gray-50 dark:bg-gray-900">
<tr>
<th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Issue') }}</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Status') }}</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Priority') }}</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Assignee') }}</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Created') }}</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white dark:divide-gray-700 dark:bg-gray-800">
@foreach($recentIssues as $issue)
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
<td class="py-4 pl-4 pr-3 text-sm">
<a href="{{ route('admin.issues.show', $issue) }}" class="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300">
{{ $issue->issue_number }}: {{ Str::limit($issue->title, 50) }}
</a>
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm">
<x-issue.status-badge :status="$issue->status" />
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm">
<x-issue.priority-badge :priority="$issue->priority" />
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900 dark:text-gray-100">
{{ $issue->assignee?->name ?? '—' }}
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400">
{{ $issue->created_at->format('Y-m-d') }}
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,197 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
{{ __('Create Issue') }} (建立問題)
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-3xl sm:px-6 lg:px-8">
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<form method="POST" action="{{ route('admin.issues.store') }}" class="space-y-6">
@csrf
<!-- Title -->
<div>
<label for="title" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Title') }} <span class="text-red-500">*</span>
</label>
<input type="text" name="title" id="title" value="{{ old('title') }}" required maxlength="255"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100 @error('title') border-red-300 @enderror"
placeholder="{{ __('Brief summary of the issue') }}">
@error('title')<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>@enderror
</div>
<!-- Description -->
<div>
<label for="description" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Description') }}
</label>
<textarea name="description" id="description" rows="5"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100"
placeholder="{{ __('Detailed description of the issue...') }}">{{ old('description') }}</textarea>
@error('description')<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>@enderror
</div>
<!-- Issue Type and Priority -->
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2">
<div>
<label for="issue_type" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Issue Type') }} <span class="text-red-500">*</span>
</label>
<select name="issue_type" id="issue_type" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100 @error('issue_type') border-red-300 @enderror">
<option value="">{{ __('Select type...') }}</option>
<option value="work_item" @selected(old('issue_type') === 'work_item')>{{ __('Work Item') }}</option>
<option value="project_task" @selected(old('issue_type') === 'project_task')>{{ __('Project Task') }}</option>
<option value="maintenance" @selected(old('issue_type') === 'maintenance')>{{ __('Maintenance') }}</option>
<option value="member_request" @selected(old('issue_type') === 'member_request')>{{ __('Member Request') }}</option>
</select>
@error('issue_type')<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>@enderror
</div>
<div>
<label for="priority" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Priority') }} <span class="text-red-500">*</span>
</label>
<select name="priority" id="priority" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100 @error('priority') border-red-300 @enderror">
<option value="">{{ __('Select priority...') }}</option>
<option value="low" @selected(old('priority') === 'low')>{{ __('Low') }} </option>
<option value="medium" @selected(old('priority', 'medium') === 'medium')>{{ __('Medium') }} </option>
<option value="high" @selected(old('priority') === 'high')>{{ __('High') }} </option>
<option value="urgent" @selected(old('priority') === 'urgent')>{{ __('Urgent') }} </option>
</select>
@error('priority')<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>@enderror
</div>
</div>
<!-- Assignee and Due Date -->
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2">
<div>
<label for="assigned_to_user_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Assign To') }}
</label>
<select name="assigned_to_user_id" id="assigned_to_user_id"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
<option value="">{{ __('Unassigned') }}</option>
@foreach($users as $user)
<option value="{{ $user->id }}" @selected(old('assigned_to_user_id') == $user->id)>{{ $user->name }}</option>
@endforeach
</select>
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">{{ __('Optional: Assign to a team member') }}</p>
</div>
<div>
<label for="due_date" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Due Date') }}
</label>
<input type="date" name="due_date" id="due_date" value="{{ old('due_date') }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
@error('due_date')<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>@enderror
</div>
</div>
<!-- Estimated Hours -->
<div>
<label for="estimated_hours" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Estimated Hours') }}
</label>
<input type="number" name="estimated_hours" id="estimated_hours" value="{{ old('estimated_hours') }}" step="0.5" min="0"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100"
placeholder="0.0">
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">{{ __('Estimated time to complete this issue') }}</p>
</div>
<!-- Member (for member requests) -->
<div>
<label for="member_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Related Member') }}
</label>
<select name="member_id" id="member_id"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
<option value="">{{ __('None') }}</option>
@foreach($members as $member)
<option value="{{ $member->id }}" @selected(old('member_id') == $member->id)>{{ $member->full_name }}</option>
@endforeach
</select>
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">{{ __('Link to a member for member requests') }}</p>
</div>
<!-- Parent Issue (for sub-tasks) -->
<div>
<label for="parent_issue_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Parent Issue') }}
</label>
<select name="parent_issue_id" id="parent_issue_id"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
<option value="">{{ __('None (top-level issue)') }}</option>
@foreach($openIssues as $parentIssue)
<option value="{{ $parentIssue->id }}" @selected(old('parent_issue_id') == $parentIssue->id)>
{{ $parentIssue->issue_number }} - {{ $parentIssue->title }}
</option>
@endforeach
</select>
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">{{ __('Make this a sub-task of another issue') }}</p>
</div>
<!-- Labels -->
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
{{ __('Labels') }}
</label>
<div class="grid grid-cols-2 sm:grid-cols-3 gap-2">
@foreach($labels as $label)
<label class="inline-flex items-center">
<input type="checkbox" name="labels[]" value="{{ $label->id }}"
@checked(in_array($label->id, old('labels', [])))
class="rounded border-gray-300 text-indigo-600 shadow-sm focus:ring-indigo-500 dark:bg-gray-700 dark:border-gray-600">
<span class="ml-2 inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium"
style="background-color: {{ $label->color }}20; color: {{ $label->color }}">
{{ $label->name }}
</span>
</label>
@endforeach
</div>
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">{{ __('Select one or more labels to categorize this issue') }}</p>
</div>
<!-- Actions -->
<div class="flex items-center justify-end gap-x-4 border-t border-gray-200 pt-6 dark:border-gray-700">
<a href="{{ route('admin.issues.index') }}"
class="rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 dark:bg-gray-700 dark:text-gray-100 dark:ring-gray-600 dark:hover:bg-gray-600">
{{ __('Cancel') }}
</a>
<button type="submit"
class="inline-flex justify-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:bg-indigo-500 dark:hover:bg-indigo-400 dark:focus:ring-offset-gray-800">
{{ __('Create Issue') }}
</button>
</div>
</form>
</div>
<!-- Help Section -->
<div class="mt-6 rounded-lg bg-blue-50 p-4 dark:bg-blue-900/30 border-l-4 border-blue-400">
<div class="flex">
<div class="flex-shrink-0">
<svg class="h-5 w-5 text-blue-400" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z" clip-rule="evenodd"/>
</svg>
</div>
<div class="ml-3">
<h3 class="text-sm font-medium text-blue-800 dark:text-blue-200">{{ __('Creating Issues') }}</h3>
<div class="mt-2 text-sm text-blue-700 dark:text-blue-300">
<ul class="list-disc pl-5 space-y-1">
<li>{{ __('Use work items for general tasks and todos') }}</li>
<li>{{ __('Project tasks are for specific project milestones') }}</li>
<li>{{ __('Member requests track inquiries or requests from members') }}</li>
<li>{{ __('Assign issues to team members to track responsibility') }}</li>
<li>{{ __('Use labels to categorize and filter issues easily') }}</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,184 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
{{ __('Edit Issue') }} - {{ $issue->issue_number }}
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-3xl sm:px-6 lg:px-8">
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<form method="POST" action="{{ route('admin.issues.update', $issue) }}" class="space-y-6">
@csrf
@method('PATCH')
<!-- Title -->
<div>
<label for="title" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Title') }} <span class="text-red-500">*</span>
</label>
<input type="text" name="title" id="title" value="{{ old('title', $issue->title) }}" required maxlength="255"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100 @error('title') border-red-300 @enderror"
placeholder="{{ __('Brief summary of the issue') }}">
@error('title')<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>@enderror
</div>
<!-- Description -->
<div>
<label for="description" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Description') }}
</label>
<textarea name="description" id="description" rows="5"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100"
placeholder="{{ __('Detailed description of the issue...') }}">{{ old('description', $issue->description) }}</textarea>
@error('description')<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>@enderror
</div>
<!-- Issue Type and Priority -->
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2">
<div>
<label for="issue_type" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Issue Type') }} <span class="text-red-500">*</span>
</label>
<select name="issue_type" id="issue_type" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100 @error('issue_type') border-red-300 @enderror">
<option value="">{{ __('Select type...') }}</option>
<option value="work_item" @selected(old('issue_type', $issue->issue_type) === 'work_item')>{{ __('Work Item') }}</option>
<option value="project_task" @selected(old('issue_type', $issue->issue_type) === 'project_task')>{{ __('Project Task') }}</option>
<option value="maintenance" @selected(old('issue_type', $issue->issue_type) === 'maintenance')>{{ __('Maintenance') }}</option>
<option value="member_request" @selected(old('issue_type', $issue->issue_type) === 'member_request')>{{ __('Member Request') }}</option>
</select>
@error('issue_type')<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>@enderror
</div>
<div>
<label for="priority" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Priority') }} <span class="text-red-500">*</span>
</label>
<select name="priority" id="priority" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100 @error('priority') border-red-300 @enderror">
<option value="">{{ __('Select priority...') }}</option>
<option value="low" @selected(old('priority', $issue->priority) === 'low')>{{ __('Low') }} </option>
<option value="medium" @selected(old('priority', $issue->priority) === 'medium')>{{ __('Medium') }} </option>
<option value="high" @selected(old('priority', $issue->priority) === 'high')>{{ __('High') }} </option>
<option value="urgent" @selected(old('priority', $issue->priority) === 'urgent')>{{ __('Urgent') }} </option>
</select>
@error('priority')<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>@enderror
</div>
</div>
<!-- Assignee and Reviewer -->
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2">
<div>
<label for="assigned_to_user_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Assign To') }}
</label>
<select name="assigned_to_user_id" id="assigned_to_user_id"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
<option value="">{{ __('Unassigned') }}</option>
@foreach($users as $user)
<option value="{{ $user->id }}" @selected(old('assigned_to_user_id', $issue->assigned_to_user_id) == $user->id)>{{ $user->name }}</option>
@endforeach
</select>
</div>
<div>
<label for="reviewer_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Reviewer') }}
</label>
<select name="reviewer_id" id="reviewer_id"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
<option value="">{{ __('None') }}</option>
@foreach($users as $user)
<option value="{{ $user->id }}" @selected(old('reviewer_id', $issue->reviewer_id) == $user->id)>{{ $user->name }}</option>
@endforeach
</select>
</div>
</div>
<!-- Due Date and Estimated Hours -->
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2">
<div>
<label for="due_date" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Due Date') }}
</label>
<input type="date" name="due_date" id="due_date" value="{{ old('due_date', $issue->due_date?->format('Y-m-d')) }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
@error('due_date')<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>@enderror
</div>
<div>
<label for="estimated_hours" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Estimated Hours') }}
</label>
<input type="number" name="estimated_hours" id="estimated_hours" value="{{ old('estimated_hours', $issue->estimated_hours) }}" step="0.5" min="0"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
</div>
</div>
<!-- Member (for member requests) -->
<div>
<label for="member_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Related Member') }}
</label>
<select name="member_id" id="member_id"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
<option value="">{{ __('None') }}</option>
@foreach($members as $member)
<option value="{{ $member->id }}" @selected(old('member_id', $issue->member_id) == $member->id)>{{ $member->full_name }}</option>
@endforeach
</select>
</div>
<!-- Parent Issue (for sub-tasks) -->
<div>
<label for="parent_issue_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Parent Issue') }}
</label>
<select name="parent_issue_id" id="parent_issue_id"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
<option value="">{{ __('None (top-level issue)') }}</option>
@foreach($openIssues as $parentIssue)
<option value="{{ $parentIssue->id }}" @selected(old('parent_issue_id', $issue->parent_issue_id) == $parentIssue->id)>
{{ $parentIssue->issue_number }} - {{ $parentIssue->title }}
</option>
@endforeach
</select>
</div>
<!-- Labels -->
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
{{ __('Labels') }}
</label>
<div class="grid grid-cols-2 sm:grid-cols-3 gap-2">
@foreach($labels as $label)
<label class="inline-flex items-center">
<input type="checkbox" name="labels[]" value="{{ $label->id }}"
@checked(in_array($label->id, old('labels', $issue->labels->pluck('id')->toArray())))
class="rounded border-gray-300 text-indigo-600 shadow-sm focus:ring-indigo-500 dark:bg-gray-700 dark:border-gray-600">
<span class="ml-2 inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium"
style="background-color: {{ $label->color }}20; color: {{ $label->color }}">
{{ $label->name }}
</span>
</label>
@endforeach
</div>
</div>
<!-- Actions -->
<div class="flex items-center justify-end gap-x-4 border-t border-gray-200 pt-6 dark:border-gray-700">
<a href="{{ route('admin.issues.show', $issue) }}"
class="rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 dark:bg-gray-700 dark:text-gray-100 dark:ring-gray-600 dark:hover:bg-gray-600">
{{ __('Cancel') }}
</a>
<button type="submit"
class="inline-flex justify-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:bg-indigo-500 dark:hover:bg-indigo-400 dark:focus:ring-offset-gray-800">
{{ __('Update Issue') }}
</button>
</div>
</form>
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,200 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
{{ __('Issues') }} (問題追蹤)
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8">
@if (session('status'))
<div class="mb-4 rounded-md bg-green-50 p-4 dark:bg-green-900/30 border-l-4 border-green-400" role="status" aria-live="polite">
<p class="text-sm text-green-800 dark:text-green-200">{{ session('status') }}</p>
</div>
@endif
@if (session('error'))
<div class="mb-4 rounded-md bg-red-50 p-4 dark:bg-red-900/30 border-l-4 border-red-400" role="alert">
<p class="text-sm text-red-800 dark:text-red-200">{{ session('error') }}</p>
</div>
@endif
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<!-- Header -->
<div class="sm:flex sm:items-center sm:justify-between mb-6">
<div>
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">{{ __('Issue Tracker') }}</h3>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">{{ __('Manage work items, tasks, and member requests') }}</p>
</div>
<div class="mt-4 sm:mt-0">
<a href="{{ route('admin.issues.create') }}" class="inline-flex items-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:bg-indigo-500 dark:hover:bg-indigo-400 dark:focus:ring-offset-gray-800">
<svg class="-ml-0.5 mr-1.5 h-5 w-5" fill="currentColor" viewBox="0 0 20 20"><path d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z"/></svg>
{{ __('Create Issue') }}
</a>
</div>
</div>
<!-- Summary Stats -->
<div class="grid grid-cols-1 gap-4 sm:grid-cols-4 mb-6">
<div class="bg-blue-50 dark:bg-blue-900/30 rounded-lg p-4 border-l-4 border-blue-400">
<dt class="text-sm font-medium text-blue-800 dark:text-blue-200">{{ __('Total Open') }}</dt>
<dd class="mt-1 text-2xl font-semibold text-blue-900 dark:text-blue-100">{{ $stats['total_open'] }}</dd>
</div>
<div class="bg-purple-50 dark:bg-purple-900/30 rounded-lg p-4 border-l-4 border-purple-400">
<dt class="text-sm font-medium text-purple-800 dark:text-purple-200">{{ __('Assigned to Me') }}</dt>
<dd class="mt-1 text-2xl font-semibold text-purple-900 dark:text-purple-100">{{ $stats['assigned_to_me'] }}</dd>
</div>
<div class="bg-red-50 dark:bg-red-900/30 rounded-lg p-4 border-l-4 border-red-400">
<dt class="text-sm font-medium text-red-800 dark:text-red-200">{{ __('Overdue') }}</dt>
<dd class="mt-1 text-2xl font-semibold text-red-900 dark:text-red-100">{{ $stats['overdue'] }}</dd>
</div>
<div class="bg-orange-50 dark:bg-orange-900/30 rounded-lg p-4 border-l-4 border-orange-400">
<dt class="text-sm font-medium text-orange-800 dark:text-orange-200">{{ __('High Priority') }}</dt>
<dd class="mt-1 text-2xl font-semibold text-orange-900 dark:text-orange-100">{{ $stats['high_priority'] }}</dd>
</div>
</div>
<!-- Filters -->
<form method="GET" class="mb-6 space-y-4" role="search">
<div class="grid grid-cols-1 gap-4 sm:grid-cols-4">
<div>
<label for="issue_type" class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ __('Type') }}</label>
<select name="issue_type" id="issue_type" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
<option value="">{{ __('All Types') }}</option>
<option value="work_item" @selected(request('issue_type') === 'work_item')>{{ __('Work Item') }}</option>
<option value="project_task" @selected(request('issue_type') === 'project_task')>{{ __('Project Task') }}</option>
<option value="maintenance" @selected(request('issue_type') === 'maintenance')>{{ __('Maintenance') }}</option>
<option value="member_request" @selected(request('issue_type') === 'member_request')>{{ __('Member Request') }}</option>
</select>
</div>
<div>
<label for="status" class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ __('Status') }}</label>
<select name="status" id="status" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
<option value="">{{ __('All Statuses') }}</option>
<option value="new" @selected(request('status') === 'new')>{{ __('New') }}</option>
<option value="assigned" @selected(request('status') === 'assigned')>{{ __('Assigned') }}</option>
<option value="in_progress" @selected(request('status') === 'in_progress')>{{ __('In Progress') }}</option>
<option value="review" @selected(request('status') === 'review')>{{ __('Review') }}</option>
<option value="closed" @selected(request('status') === 'closed')>{{ __('Closed') }}</option>
</select>
</div>
<div>
<label for="priority" class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ __('Priority') }}</label>
<select name="priority" id="priority" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
<option value="">{{ __('All Priorities') }}</option>
<option value="low" @selected(request('priority') === 'low')>{{ __('Low') }}</option>
<option value="medium" @selected(request('priority') === 'medium')>{{ __('Medium') }}</option>
<option value="high" @selected(request('priority') === 'high')>{{ __('High') }}</option>
<option value="urgent" @selected(request('priority') === 'urgent')>{{ __('Urgent') }}</option>
</select>
</div>
<div>
<label for="assigned_to" class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ __('Assignee') }}</label>
<select name="assigned_to" id="assigned_to" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
<option value="">{{ __('All Assignees') }}</option>
@foreach($users as $user)
<option value="{{ $user->id }}" @selected(request('assigned_to') == $user->id)>{{ $user->name }}</option>
@endforeach
</select>
</div>
</div>
<div class="flex gap-4">
<div class="flex-1">
<label for="search" class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ __('Search') }}</label>
<input type="text" name="search" id="search" value="{{ request('search') }}" placeholder="{{ __('Issue number, title, or description...') }}" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
</div>
<div class="flex items-end gap-2">
<button type="submit" class="inline-flex justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 dark:bg-gray-700 dark:text-gray-100 dark:ring-gray-600 dark:hover:bg-gray-600">{{ __('Filter') }}</button>
<label class="inline-flex items-center">
<input type="checkbox" name="show_closed" value="1" @checked(request('show_closed') === '1') class="rounded border-gray-300 text-indigo-600 shadow-sm focus:ring-indigo-500 dark:bg-gray-700 dark:border-gray-600">
<span class="ml-2 text-sm text-gray-700 dark:text-gray-300">{{ __('Show closed') }}</span>
</label>
</div>
</div>
</form>
<!-- Issues Table -->
<div class="overflow-hidden shadow ring-1 ring-black ring-opacity-5 sm:rounded-lg dark:ring-gray-700">
<table class="min-w-full divide-y divide-gray-300 dark:divide-gray-600">
<caption class="sr-only">{{ __('List of issues with their current status and assignment') }}</caption>
<thead class="bg-gray-50 dark:bg-gray-900">
<tr>
<th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Issue') }}</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Type') }}</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Status') }}</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Priority') }}</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Assignee') }}</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Due Date') }}</th>
<th scope="col" class="relative py-3.5 pl-3 pr-4"><span class="sr-only">{{ __('Actions') }}</span></th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white dark:divide-gray-700 dark:bg-gray-800">
@forelse($issues as $issue)
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
<td class="py-4 pl-4 pr-3 text-sm">
<div class="font-medium text-gray-900 dark:text-gray-100">
<a href="{{ route('admin.issues.show', $issue) }}" class="hover:underline">{{ $issue->issue_number }}</a>
</div>
<div class="text-gray-500 dark:text-gray-400 line-clamp-1">{{ $issue->title }}</div>
@if($issue->labels->count() > 0)
<div class="mt-1 flex gap-1 flex-wrap">
@foreach($issue->labels as $label)
<span class="inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium" style="background-color: {{ $label->color }}20; color: {{ $label->color }}">
{{ $label->name }}
</span>
@endforeach
</div>
@endif
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900 dark:text-gray-100">
{{ $issue->issue_type_label }}
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm">
<x-issue.status-badge :status="$issue->status" :label="$issue->status_label" />
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm">
<x-issue.priority-badge :priority="$issue->priority" :label="$issue->priority_label" />
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900 dark:text-gray-100">
{{ $issue->assignee?->name ?? __('Unassigned') }}
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900 dark:text-gray-100">
@if($issue->due_date)
<span class="{{ $issue->is_overdue ? 'text-red-600 dark:text-red-400 font-semibold' : '' }}">
{{ $issue->due_date->format('Y-m-d') }}
@if($issue->is_overdue)
<span class="text-xs">({{ __('Overdue') }})</span>
@endif
</span>
@else
<span class="text-gray-400"></span>
@endif
</td>
<td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium">
<a href="{{ route('admin.issues.show', $issue) }}" class="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300">{{ __('View') }}</a>
</td>
</tr>
@empty
<tr>
<td colspan="7" class="px-3 py-8 text-center text-sm text-gray-500 dark:text-gray-400">
<p>{{ __('No issues found') }}</p>
<div class="mt-4">
<a href="{{ route('admin.issues.create') }}" class="inline-flex items-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 dark:bg-indigo-500 dark:hover:bg-indigo-400">
+ {{ __('Create First Issue') }}
</a>
</div>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<!-- Pagination -->
@if($issues->hasPages())
<div class="mt-6">{{ $issues->links() }}</div>
@endif
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,371 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
{{ $issue->issue_number }} - {{ $issue->title }}
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8 space-y-6">
@if (session('status'))
<div class="rounded-md bg-green-50 p-4 dark:bg-green-900/30 border-l-4 border-green-400" role="status">
<p class="text-sm text-green-800 dark:text-green-200">{{ session('status') }}</p>
</div>
@endif
@if (session('error'))
<div class="rounded-md bg-red-50 p-4 dark:bg-red-900/30 border-l-4 border-red-400" role="alert">
<p class="text-sm text-red-800 dark:text-red-200">{{ session('error') }}</p>
</div>
@endif
<!-- Main Details -->
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<div class="sm:flex sm:items-start sm:justify-between mb-6">
<div class="flex-1">
<div class="flex items-center gap-3 mb-2">
<h3 class="text-2xl font-bold text-gray-900 dark:text-gray-100">{{ $issue->title }}</h3>
<x-issue.status-badge :status="$issue->status" :label="$issue->status_label" />
<x-issue.priority-badge :priority="$issue->priority" :label="$issue->priority_label" />
</div>
<div class="flex items-center gap-4 text-sm text-gray-500 dark:text-gray-400">
<span>{{ $issue->issue_type_label }}</span>
<span>"</span>
<span>{{ __('Created by') }} {{ $issue->creator->name }}</span>
<span>"</span>
<span>{{ $issue->created_at->diffForHumans() }}</span>
</div>
</div>
<div class="mt-4 sm:mt-0 flex gap-2">
@if(!$issue->isClosed() || Auth::user()->is_admin)
<a href="{{ route('admin.issues.edit', $issue) }}" class="inline-flex items-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 dark:bg-gray-700 dark:text-gray-100 dark:ring-gray-600">
{{ __('Edit') }}
</a>
@endif
@if(Auth::user()->is_admin)
<form method="POST" action="{{ route('admin.issues.destroy', $issue) }}" onsubmit="return confirm('{{ __('Are you sure?') }}')">
@csrf
@method('DELETE')
<button type="submit" class="inline-flex items-center rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 dark:bg-red-500 dark:hover:bg-red-400">
{{ __('Delete') }}
</button>
</form>
@endif
</div>
</div>
<!-- Labels -->
@if($issue->labels->count() > 0)
<div class="mb-6 flex gap-2 flex-wrap">
@foreach($issue->labels as $label)
<span class="inline-flex items-center rounded-full px-3 py-1 text-sm font-medium"
style="background-color: {{ $label->color }}; color: {{ $label->text_color }}">
{{ $label->name }}
</span>
@endforeach
</div>
@endif
<!-- Description -->
<div class="prose dark:prose-invert max-w-none mb-6">
<h4 class="text-sm font-medium text-gray-900 dark:text-gray-100 mb-2">{{ __('Description') }}</h4>
<div class="text-gray-700 dark:text-gray-300 whitespace-pre-wrap">{{ $issue->description ?: __('No description provided') }}</div>
</div>
<!-- Details Grid -->
<dl class="grid grid-cols-1 gap-x-4 gap-y-6 sm:grid-cols-2 border-t border-gray-200 dark:border-gray-700 pt-6">
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Assigned To') }}</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">{{ $issue->assignee?->name ?? __('Unassigned') }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Reviewer') }}</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">{{ $issue->reviewer?->name ?? __('None') }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Due Date') }}</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
@if($issue->due_date)
<span class="{{ $issue->is_overdue ? 'text-red-600 dark:text-red-400 font-semibold' : '' }}">
{{ $issue->due_date->format('Y-m-d') }}
@if($issue->is_overdue)
({{ __('Overdue by :days days', ['days' => abs($issue->days_until_due)]) }})
@elseif($issue->days_until_due !== null && $issue->days_until_due >= 0)
({{ __(':days days left', ['days' => $issue->days_until_due]) }})
@endif
</span>
@else
<span class="text-gray-400">{{ __('No due date') }}</span>
@endif
</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Time Tracking') }}</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
{{ number_format($issue->actual_hours, 1) }}h
@if($issue->estimated_hours)
/ {{ number_format($issue->estimated_hours, 1) }}h {{ __('estimated') }}
@endif
</dd>
</div>
@if($issue->member)
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Related Member') }}</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">{{ $issue->member->full_name }}</dd>
</div>
@endif
@if($issue->parentIssue)
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Parent Issue') }}</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
<a href="{{ route('admin.issues.show', $issue->parentIssue) }}" class="text-indigo-600 hover:underline dark:text-indigo-400">
{{ $issue->parentIssue->issue_number }} - {{ $issue->parentIssue->title }}
</a>
</dd>
</div>
@endif
</dl>
<!-- Sub-tasks -->
@if($issue->subTasks->count() > 0)
<div class="mt-6 border-t border-gray-200 dark:border-gray-700 pt-6">
<h4 class="text-sm font-medium text-gray-900 dark:text-gray-100 mb-3">{{ __('Sub-tasks') }} ({{ $issue->subTasks->count() }})</h4>
<ul class="space-y-2">
@foreach($issue->subTasks as $subTask)
<li class="flex items-center gap-2">
<x-issue.status-badge :status="$subTask->status" />
<a href="{{ route('admin.issues.show', $subTask) }}" class="text-indigo-600 hover:underline dark:text-indigo-400">
{{ $subTask->issue_number }} - {{ $subTask->title }}
</a>
</li>
@endforeach
</ul>
</div>
@endif
</div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- Left Column: Comments, Attachments, Time Logs -->
<div class="lg:col-span-2 space-y-6">
<!-- Workflow Actions -->
@if(!$issue->isClosed())
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">{{ __('Actions') }}</h3>
<div class="flex flex-wrap gap-2">
<!-- Update Status -->
<form method="POST" action="{{ route('admin.issues.update-status', $issue) }}" class="inline-flex gap-2">
@csrf
@method('PATCH')
<select name="status" class="rounded-md border-gray-300 text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
<option value="new" @selected($issue->status === 'new')>{{ __('New') }}</option>
<option value="assigned" @selected($issue->status === 'assigned')>{{ __('Assigned') }}</option>
<option value="in_progress" @selected($issue->status === 'in_progress')>{{ __('In Progress') }}</option>
<option value="review" @selected($issue->status === 'review')>{{ __('Review') }}</option>
<option value="closed" @selected($issue->status === 'closed')>{{ __('Closed') }}</option>
</select>
<button type="submit" class="inline-flex items-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white hover:bg-indigo-500 dark:bg-indigo-500 dark:hover:bg-indigo-400">
{{ __('Update Status') }}
</button>
</form>
<!-- Assign -->
<form method="POST" action="{{ route('admin.issues.assign', $issue) }}" class="inline-flex gap-2">
@csrf
<select name="assigned_to_user_id" class="rounded-md border-gray-300 text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
<option value="">{{ __('Unassigned') }}</option>
@foreach($users as $user)
<option value="{{ $user->id }}" @selected($issue->assigned_to_user_id == $user->id)>{{ $user->name }}</option>
@endforeach
</select>
<button type="submit" class="inline-flex items-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 dark:bg-gray-700 dark:text-gray-100 dark:ring-gray-600">
{{ __('Assign') }}
</button>
</form>
</div>
</div>
@endif
<!-- Comments -->
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">
{{ __('Comments') }} ({{ $issue->comments->count() }})
</h3>
<!-- Comments List -->
<div class="space-y-4 mb-6">
@forelse($issue->comments as $comment)
<div class="border-l-2 {{ $comment->is_internal ? 'border-orange-400 bg-orange-50 dark:bg-orange-900/20' : 'border-gray-300 dark:border-gray-600' }} pl-4 py-2">
<div class="flex items-center gap-2 mb-1">
<span class="font-medium text-sm text-gray-900 dark:text-gray-100">{{ $comment->user->name }}</span>
<span class="text-xs text-gray-500 dark:text-gray-400">{{ $comment->created_at->diffForHumans() }}</span>
@if($comment->is_internal)
<span class="text-xs bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200 px-2 py-0.5 rounded">{{ __('Internal') }}</span>
@endif
</div>
<p class="text-sm text-gray-700 dark:text-gray-300 whitespace-pre-wrap">{{ $comment->comment_text }}</p>
</div>
@empty
<p class="text-sm text-gray-500 dark:text-gray-400 text-center py-4">{{ __('No comments yet') }}</p>
@endforelse
</div>
<!-- Add Comment Form -->
<form method="POST" action="{{ route('admin.issues.comments.store', $issue) }}" class="border-t border-gray-200 dark:border-gray-700 pt-4">
@csrf
<textarea name="comment_text" rows="3" required
class="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100 mb-2"
placeholder="{{ __('Add a comment...') }}"></textarea>
<div class="flex items-center justify-between">
<label class="inline-flex items-center">
<input type="checkbox" name="is_internal" value="1" class="rounded border-gray-300 text-indigo-600 shadow-sm focus:ring-indigo-500 dark:bg-gray-700 dark:border-gray-600">
<span class="ml-2 text-sm text-gray-700 dark:text-gray-300">{{ __('Internal comment') }}</span>
</label>
<button type="submit" class="inline-flex items-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white hover:bg-indigo-500 dark:bg-indigo-500">
{{ __('Add Comment') }}
</button>
</div>
</form>
</div>
<!-- Attachments -->
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">
{{ __('Attachments') }} ({{ $issue->attachments->count() }})
</h3>
<div class="space-y-2 mb-6">
@forelse($issue->attachments as $attachment)
<div class="flex items-center justify-between p-2 bg-gray-50 dark:bg-gray-900 rounded">
<div class="flex items-center gap-2">
<svg class="h-5 w-5 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M8 4a3 3 0 00-3 3v4a5 5 0 0010 0V7a1 1 0 112 0v4a7 7 0 11-14 0V7a5 5 0 0110 0v4a3 3 0 11-6 0V7a1 1 0 012 0v4a1 1 0 102 0V7a3 3 0 00-3-3z" clip-rule="evenodd"/>
</svg>
<div>
<p class="text-sm font-medium text-gray-900 dark:text-gray-100">{{ $attachment->file_name }}</p>
<p class="text-xs text-gray-500 dark:text-gray-400">{{ $attachment->file_size_human }} " {{ $attachment->user->name }} " {{ $attachment->created_at->diffForHumans() }}</p>
</div>
</div>
<div class="flex gap-2">
<a href="{{ route('admin.issues.attachments.download', $attachment) }}" class="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 text-sm">{{ __('Download') }}</a>
@if(Auth::user()->is_admin)
<form method="POST" action="{{ route('admin.issues.attachments.destroy', $attachment) }}" onsubmit="return confirm('{{ __('Delete this attachment?') }}')">
@csrf
@method('DELETE')
<button type="submit" class="text-red-600 hover:text-red-900 dark:text-red-400 text-sm">{{ __('Delete') }}</button>
</form>
@endif
</div>
</div>
@empty
<p class="text-sm text-gray-500 dark:text-gray-400 text-center py-4">{{ __('No attachments') }}</p>
@endforelse
</div>
<!-- Upload Form -->
<form method="POST" action="{{ route('admin.issues.attachments.store', $issue) }}" enctype="multipart/form-data" class="border-t border-gray-200 dark:border-gray-700 pt-4">
@csrf
<div class="flex items-center gap-2">
<input type="file" name="file" required class="block w-full text-sm text-gray-900 dark:text-gray-100 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-indigo-50 file:text-indigo-700 hover:file:bg-indigo-100 dark:file:bg-indigo-900 dark:file:text-indigo-200">
<button type="submit" class="inline-flex items-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white hover:bg-indigo-500 dark:bg-indigo-500">
{{ __('Upload') }}
</button>
</div>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">{{ __('Max size: 10MB') }}</p>
</form>
</div>
<!-- Time Logs -->
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">
{{ __('Time Tracking') }} ({{ number_format($issue->total_time_logged, 1) }}h total)
</h3>
<div class="space-y-2 mb-6">
@forelse($issue->timeLogs->sortByDesc('logged_at') as $timeLog)
<div class="flex items-center justify-between p-2 bg-gray-50 dark:bg-gray-900 rounded">
<div>
<p class="text-sm font-medium text-gray-900 dark:text-gray-100">{{ number_format($timeLog->hours, 2) }}h - {{ $timeLog->user->name }}</p>
<p class="text-xs text-gray-500 dark:text-gray-400">{{ $timeLog->logged_at->format('Y-m-d') }} - {{ $timeLog->description }}</p>
</div>
</div>
@empty
<p class="text-sm text-gray-500 dark:text-gray-400 text-center py-4">{{ __('No time logged yet') }}</p>
@endforelse
</div>
<!-- Log Time Form -->
<form method="POST" action="{{ route('admin.issues.time-logs.store', $issue) }}" class="border-t border-gray-200 dark:border-gray-700 pt-4 grid grid-cols-2 gap-2">
@csrf
<input type="number" name="hours" step="0.25" min="0.25" placeholder="{{ __('Hours') }}" required
class="rounded-md border-gray-300 text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
<input type="date" name="logged_at" value="{{ now()->format('Y-m-d') }}" required
class="rounded-md border-gray-300 text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
<input type="text" name="description" placeholder="{{ __('What did you do?') }}"
class="col-span-2 rounded-md border-gray-300 text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
<button type="submit" class="col-span-2 inline-flex justify-center items-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white hover:bg-indigo-500 dark:bg-indigo-500">
{{ __('Log Time') }}
</button>
</form>
</div>
</div>
<!-- Right Column: Timeline & Watchers -->
<div class="space-y-6">
<!-- Timeline -->
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">{{ __('Progress') }}</h3>
<x-issue.timeline :issue="$issue" />
<div class="mt-4">
<div class="flex justify-between text-sm mb-1">
<span class="text-gray-700 dark:text-gray-300">{{ __('Completion') }}</span>
<span class="font-medium text-gray-900 dark:text-gray-100">{{ $issue->progress_percentage }}%</span>
</div>
<div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2">
<div class="bg-indigo-600 dark:bg-indigo-500 h-2 rounded-full transition-all" style="width: {{ $issue->progress_percentage }}%"></div>
</div>
</div>
</div>
<!-- Watchers -->
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">
{{ __('Watchers') }} ({{ $issue->watchers->count() }})
</h3>
<ul class="space-y-2 mb-4">
@foreach($issue->watchers as $watcher)
<li class="flex items-center justify-between">
<span class="text-sm text-gray-900 dark:text-gray-100">{{ $watcher->name }}</span>
@if($watcher->id !== $issue->created_by_user_id)
<form method="POST" action="{{ route('admin.issues.watchers.destroy', $issue) }}">
@csrf
@method('DELETE')
<input type="hidden" name="user_id" value="{{ $watcher->id }}">
<button type="submit" class="text-xs text-red-600 hover:text-red-900 dark:text-red-400">{{ __('Remove') }}</button>
</form>
@endif
</li>
@endforeach
</ul>
<!-- Add Watcher -->
<form method="POST" action="{{ route('admin.issues.watchers.store', $issue) }}" class="border-t border-gray-200 dark:border-gray-700 pt-4">
@csrf
<div class="flex gap-2">
<select name="user_id" required class="flex-1 rounded-md border-gray-300 text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
<option value="">{{ __('Add watcher...') }}</option>
@foreach($users->whereNotIn('id', $issue->watchers->pluck('id')) as $user)
<option value="{{ $user->id }}">{{ $user->name }}</option>
@endforeach
</select>
<button type="submit" class="inline-flex items-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white hover:bg-indigo-500 dark:bg-indigo-500">
{{ __('Add') }}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,171 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
{{ __('Activate Membership') }} - {{ $member->full_name }}
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-2xl sm:px-6 lg:px-8">
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
@if($approvedPayment)
{{-- Approved Payment Info --}}
<div class="mb-6 bg-green-50 dark:bg-green-900/30 border-l-4 border-green-400 p-4">
<div class="flex">
<div class="flex-shrink-0">
<svg class="h-5 w-5 text-green-400" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
</svg>
</div>
<div class="ml-3">
<h3 class="text-sm font-medium text-green-800 dark:text-green-200">{{ __('Payment Approved') }}</h3>
<div class="mt-2 text-sm text-green-700 dark:text-green-300">
<p>{{ __('Amount') }}: TWD {{ number_format($approvedPayment->amount, 0) }}</p>
<p>{{ __('Payment Date') }}: {{ $approvedPayment->paid_at->format('Y-m-d') }}</p>
<p>{{ __('Payment Method') }}: {{ $approvedPayment->payment_method_label }}</p>
<p>{{ __('Approved on') }}: {{ $approvedPayment->chair_verified_at->format('Y-m-d H:i') }}</p>
</div>
</div>
</div>
</div>
@endif
{{-- Member Info --}}
<div class="mb-6">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-3">{{ __('Member Information') }}</h3>
<dl class="grid grid-cols-1 gap-x-4 gap-y-4 sm:grid-cols-2">
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Full Name') }}</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">{{ $member->full_name }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Email') }}</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">{{ $member->email }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Current Status') }}</dt>
<dd class="mt-1">
<span class="inline-flex items-center rounded-full px-2 py-1 text-xs font-medium {{ $member->membership_status_badge }}">
{{ $member->membership_status_label }}
</span>
</dd>
</div>
</dl>
</div>
{{-- Activation Form --}}
<form method="POST" action="{{ route('admin.members.activate', $member) }}" class="space-y-6">
@csrf
<div class="border-t border-gray-200 dark:border-gray-700 pt-6">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">{{ __('Membership Activation Details') }}</h3>
{{-- Membership Type --}}
<div>
<label for="membership_type" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Membership Type') }} <span class="text-red-500">*</span>
</label>
<select name="membership_type" id="membership_type" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100 @error('membership_type') border-red-300 @enderror">
<option value="regular" {{ old('membership_type', 'regular') == 'regular' ? 'selected' : '' }}>{{ __('Regular Member (一般會員)') }}</option>
<option value="student" {{ old('membership_type') == 'student' ? 'selected' : '' }}>{{ __('Student Member (學生會員)') }}</option>
<option value="honorary" {{ old('membership_type') == 'honorary' ? 'selected' : '' }}>{{ __('Honorary Member (榮譽會員)') }}</option>
<option value="lifetime" {{ old('membership_type') == 'lifetime' ? 'selected' : '' }}>{{ __('Lifetime Member (終身會員)') }}</option>
</select>
@error('membership_type')<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>@enderror
</div>
{{-- Start Date --}}
<div class="mt-4">
<label for="membership_started_at" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Membership Start Date') }} <span class="text-red-500">*</span>
</label>
<input type="date" name="membership_started_at" id="membership_started_at"
value="{{ old('membership_started_at', today()->format('Y-m-d')) }}" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100 @error('membership_started_at') border-red-300 @enderror">
@error('membership_started_at')<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>@enderror
</div>
{{-- End Date --}}
<div class="mt-4">
<label for="membership_expires_at" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Membership Expiry Date') }} <span class="text-red-500">*</span>
</label>
<input type="date" name="membership_expires_at" id="membership_expires_at"
value="{{ old('membership_expires_at', today()->addYear()->format('Y-m-d')) }}" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100 @error('membership_expires_at') border-red-300 @enderror">
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">{{ __('Default: One year from start date') }}</p>
@error('membership_expires_at')<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>@enderror
</div>
</div>
{{-- Confirmation --}}
<div class="bg-blue-50 dark:bg-blue-900/30 border-l-4 border-blue-400 p-4">
<div class="flex">
<div class="flex-shrink-0">
<svg class="h-5 w-5 text-blue-400" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd" />
</svg>
</div>
<div class="ml-3">
<p class="text-sm text-blue-700 dark:text-blue-300">
{{ __('After activation, the member will receive a confirmation email and gain access to member-only resources.') }}
</p>
</div>
</div>
</div>
{{-- Submit Buttons --}}
<div class="flex items-center justify-end gap-x-4 border-t border-gray-200 pt-6 dark:border-gray-700">
<a href="{{ route('admin.members.show', $member) }}"
class="rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 dark:bg-gray-700 dark:text-gray-100 dark:ring-gray-600">
{{ __('Cancel') }}
</a>
<button type="submit"
class="inline-flex justify-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 dark:bg-indigo-500 dark:hover:bg-indigo-400">
{{ __('Activate Membership') }}
</button>
</div>
</form>
</div>
</div>
</div>
<script>
// Auto-calculate expiry date based on membership type
document.getElementById('membership_type').addEventListener('change', function() {
const startDate = new Date(document.getElementById('membership_started_at').value);
const expiryInput = document.getElementById('membership_expires_at');
if (this.value === 'lifetime') {
// Set to 100 years for lifetime
const lifetimeDate = new Date(startDate);
lifetimeDate.setFullYear(lifetimeDate.getFullYear() + 100);
expiryInput.value = lifetimeDate.toISOString().split('T')[0];
} else {
// Set to 1 year for other types
const oneYearDate = new Date(startDate);
oneYearDate.setFullYear(oneYearDate.getFullYear() + 1);
expiryInput.value = oneYearDate.toISOString().split('T')[0];
}
});
// Update expiry when start date changes
document.getElementById('membership_started_at').addEventListener('change', function() {
const membershipType = document.getElementById('membership_type').value;
const startDate = new Date(this.value);
const expiryInput = document.getElementById('membership_expires_at');
if (membershipType === 'lifetime') {
const lifetimeDate = new Date(startDate);
lifetimeDate.setFullYear(lifetimeDate.getFullYear() + 100);
expiryInput.value = lifetimeDate.toISOString().split('T')[0];
} else {
const oneYearDate = new Date(startDate);
oneYearDate.setFullYear(oneYearDate.getFullYear() + 1);
expiryInput.value = oneYearDate.toISOString().split('T')[0];
}
});
</script>
</x-app-layout>

View File

@@ -0,0 +1,241 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
{{ __('Create new member') }}
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-3xl sm:px-6 lg:px-8">
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
@if (session('status'))
<div class="mb-4 rounded-md bg-green-50 p-4" role="status" aria-live="polite">
<p class="text-sm font-medium text-green-800">
{{ session('status') }}
</p>
</div>
@endif
<form method="POST" action="{{ route('admin.members.store') }}" class="space-y-6" aria-label="{{ __('Create member form') }}">
@csrf
<div>
<label for="full_name" class="block text-sm font-medium text-gray-700">
{{ __('Full name') }}
</label>
<input
type="text"
name="full_name"
id="full_name"
value="{{ old('full_name') }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
required
>
@error('full_name')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="email" class="block text-sm font-medium text-gray-700">
{{ __('Email') }}
</label>
<input
type="email"
name="email"
id="email"
value="{{ old('email') }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
required
>
@error('email')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
<p class="mt-1 text-sm text-gray-500">
{{ __('An activation email will be sent to this address.') }}
</p>
</div>
<div>
<label for="national_id" class="block text-sm font-medium text-gray-700">
{{ __('National ID') }}
</label>
<input
type="text"
name="national_id"
id="national_id"
value="{{ old('national_id') }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
autocomplete="off"
>
@error('national_id')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
<p class="mt-1 text-sm text-gray-500">
{{ __('Will be stored encrypted for security.') }}
</p>
</div>
<div>
<label for="phone" class="block text-sm font-medium text-gray-700">
{{ __('Phone') }}
</label>
<input
type="text"
name="phone"
id="phone"
value="{{ old('phone') }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
>
@error('phone')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div class="grid gap-6 sm:grid-cols-2">
<div>
<label for="membership_started_at" class="block text-sm font-medium text-gray-700">
{{ __('Membership start date') }}
</label>
<input
type="date"
name="membership_started_at"
id="membership_started_at"
value="{{ old('membership_started_at') }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
>
@error('membership_started_at')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="membership_expires_at" class="block text-sm font-medium text-gray-700">
{{ __('Membership expiry date') }}
</label>
<input
type="date"
name="membership_expires_at"
id="membership_expires_at"
value="{{ old('membership_expires_at') }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
>
@error('membership_expires_at')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
</div>
<div>
<label for="address_line_1" class="block text-sm font-medium text-gray-700">
{{ __('Address Line 1') }}
</label>
<input
type="text"
name="address_line_1"
id="address_line_1"
value="{{ old('address_line_1') }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
>
@error('address_line_1')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="address_line_2" class="block text-sm font-medium text-gray-700">
{{ __('Address Line 2') }}
</label>
<input
type="text"
name="address_line_2"
id="address_line_2"
value="{{ old('address_line_2') }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
>
@error('address_line_2')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div class="grid gap-6 sm:grid-cols-2">
<div>
<label for="city" class="block text-sm font-medium text-gray-700">
{{ __('City') }}
</label>
<input
type="text"
name="city"
id="city"
value="{{ old('city') }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
>
@error('city')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="postal_code" class="block text-sm font-medium text-gray-700">
{{ __('Postal Code') }}
</label>
<input
type="text"
name="postal_code"
id="postal_code"
value="{{ old('postal_code') }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
>
@error('postal_code')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
</div>
<div>
<label for="emergency_contact_name" class="block text-sm font-medium text-gray-700">
{{ __('Emergency Contact Name') }}
</label>
<input
type="text"
name="emergency_contact_name"
id="emergency_contact_name"
value="{{ old('emergency_contact_name') }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
>
@error('emergency_contact_name')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="emergency_contact_phone" class="block text-sm font-medium text-gray-700">
{{ __('Emergency Contact Phone') }}
</label>
<input
type="text"
name="emergency_contact_phone"
id="emergency_contact_phone"
value="{{ old('emergency_contact_phone') }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
>
@error('emergency_contact_phone')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div class="flex justify-end gap-3">
<a href="{{ route('admin.members.index') }}" class="inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
{{ __('Cancel') }}
</a>
<button type="submit" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
{{ __('Create member') }}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,236 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
{{ __('Edit member') }}
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-3xl sm:px-6 lg:px-8">
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
@if (session('status'))
<div class="mb-4 rounded-md bg-green-50 p-4" role="status" aria-live="polite">
<p class="text-sm font-medium text-green-800">
{{ session('status') }}
</p>
</div>
@endif
<form method="POST" action="{{ route('admin.members.update', $member) }}" class="space-y-6">
@csrf
@method('PATCH')
<div>
<label for="full_name" class="block text-sm font-medium text-gray-700">
{{ __('Full name') }}
</label>
<input
type="text"
name="full_name"
id="full_name"
value="{{ old('full_name', $member->full_name) }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
required
>
@error('full_name')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="email" class="block text-sm font-medium text-gray-700">
{{ __('Email') }}
</label>
<input
type="email"
name="email"
id="email"
value="{{ old('email', $member->email) }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
required
>
@error('email')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="national_id" class="block text-sm font-medium text-gray-700">
{{ __('National ID') }}
</label>
<input
type="text"
name="national_id"
id="national_id"
value="{{ old('national_id', $member->national_id) }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
autocomplete="off"
>
@error('national_id')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
<p class="mt-1 text-sm text-gray-500">
{{ __('Will be stored encrypted for security.') }}
</p>
</div>
<div>
<label for="phone" class="block text-sm font-medium text-gray-700">
{{ __('Phone') }}
</label>
<input
type="text"
name="phone"
id="phone"
value="{{ old('phone', $member->phone) }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
>
@error('phone')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div class="grid gap-6 sm:grid-cols-2">
<div>
<label for="membership_started_at" class="block text-sm font-medium text-gray-700">
{{ __('Membership start date') }}
</label>
<input
type="date"
name="membership_started_at"
id="membership_started_at"
value="{{ old('membership_started_at', optional($member->membership_started_at)->toDateString()) }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
>
@error('membership_started_at')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="membership_expires_at" class="block text-sm font-medium text-gray-700">
{{ __('Membership expiry date') }}
</label>
<input
type="date"
name="membership_expires_at"
id="membership_expires_at"
value="{{ old('membership_expires_at', optional($member->membership_expires_at)->toDateString()) }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
>
@error('membership_expires_at')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
</div>
<div>
<label for="address_line_1" class="block text-sm font-medium text-gray-700">
{{ __('Address Line 1') }}
</label>
<input
type="text"
name="address_line_1"
id="address_line_1"
value="{{ old('address_line_1', $member->address_line_1) }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
>
@error('address_line_1')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="address_line_2" class="block text-sm font-medium text-gray-700">
{{ __('Address Line 2') }}
</label>
<input
type="text"
name="address_line_2"
id="address_line_2"
value="{{ old('address_line_2', $member->address_line_2) }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
>
@error('address_line_2')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div class="grid gap-6 sm:grid-cols-2">
<div>
<label for="city" class="block text-sm font-medium text-gray-700">
{{ __('City') }}
</label>
<input
type="text"
name="city"
id="city"
value="{{ old('city', $member->city) }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
>
@error('city')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="postal_code" class="block text-sm font-medium text-gray-700">
{{ __('Postal Code') }}
</label>
<input
type="text"
name="postal_code"
id="postal_code"
value="{{ old('postal_code', $member->postal_code) }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
>
@error('postal_code')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
</div>
<div>
<label for="emergency_contact_name" class="block text-sm font-medium text-gray-700">
{{ __('Emergency Contact Name') }}
</label>
<input
type="text"
name="emergency_contact_name"
id="emergency_contact_name"
value="{{ old('emergency_contact_name', $member->emergency_contact_name) }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
>
@error('emergency_contact_name')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="emergency_contact_phone" class="block text-sm font-medium text-gray-700">
{{ __('Emergency Contact Phone') }}
</label>
<input
type="text"
name="emergency_contact_phone"
id="emergency_contact_phone"
value="{{ old('emergency_contact_phone', $member->emergency_contact_phone) }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
>
@error('emergency_contact_phone')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div class="flex justify-end">
<button type="submit" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
{{ __('Save changes') }}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,59 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
{{ __('Import members from CSV') }}
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-3xl sm:px-6 lg:px-8">
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<p class="text-sm text-gray-700 mb-4">
{{ __('Upload a CSV file with the following header columns (existing members matched by email are updated):') }}
</p>
<ul class="list-disc list-inside text-sm text-gray-700 mb-4 space-y-1">
<li><code>full_name</code></li>
<li><code>email</code></li>
<li><code>phone</code></li>
<li><code>address_line_1</code> (optional)</li>
<li><code>address_line_2</code> (optional)</li>
<li><code>city</code> (optional)</li>
<li><code>postal_code</code> (optional)</li>
<li><code>emergency_contact_name</code> (optional)</li>
<li><code>emergency_contact_phone</code> (optional)</li>
<li><code>membership_started_at</code> (YYYY-MM-DD)</li>
<li><code>membership_expires_at</code> (YYYY-MM-DD)</li>
</ul>
<form method="POST" action="{{ route('admin.members.import') }}" enctype="multipart/form-data" class="space-y-6">
@csrf
<div>
<label for="file" class="block text-sm font-medium text-gray-700">
{{ __('CSV file') }}
</label>
<input
type="file"
name="file"
id="file"
accept=".csv,text/csv"
class="mt-1 block w-full text-sm text-gray-900 file:mr-4 file:rounded-md file:border-0 file:bg-indigo-50 file:px-4 file:py-2 file:text-sm file:font-semibold file:text-indigo-700 hover:file:bg-indigo-100"
required
>
@error('file')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div class="flex justify-end">
<button type="submit" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
{{ __('Start import') }}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,182 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
{{ __('Members') }}
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8">
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<form method="GET" action="{{ route('admin.members.index') }}" class="mb-4 space-y-4" role="search" aria-label="{{ __('Search and filter members') }}">
<div>
<label for="search" class="block text-sm font-medium text-gray-700">
{{ __('Search by name, email, phone, or national ID') }}
</label>
<input
type="text"
name="search"
id="search"
value="{{ $filters['search'] ?? '' }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
placeholder="{{ __('Enter search term...') }}"
>
<p class="mt-1 text-xs text-gray-500">
{{ __('Searches in name, email, phone number, and national ID') }}
</p>
</div>
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
<div>
<label for="status" class="block text-sm font-medium text-gray-700">
{{ __('Membership status') }}
</label>
<select
id="status"
name="status"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
>
<option value="">{{ __('All') }}</option>
<option value="active" @selected(($filters['status'] ?? '') === 'active')>{{ __('Active') }}</option>
<option value="expired" @selected(($filters['status'] ?? '') === 'expired')>{{ __('Expired') }}</option>
<option value="expiring_soon" @selected(($filters['status'] ?? '') === 'expiring_soon')>{{ __('Expiring Soon (30 days)') }}</option>
</select>
</div>
<div>
<label for="payment_status" class="block text-sm font-medium text-gray-700">
{{ __('Payment status') }}
</label>
<select
id="payment_status"
name="payment_status"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
>
<option value="">{{ __('All') }}</option>
<option value="has_payments" @selected(($filters['payment_status'] ?? '') === 'has_payments')>{{ __('Has Payments') }}</option>
<option value="no_payments" @selected(($filters['payment_status'] ?? '') === 'no_payments')>{{ __('No Payments') }}</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700">
{{ __('Date range') }}
</label>
<button
type="button"
onclick="document.getElementById('dateFilters').classList.toggle('hidden')"
class="mt-1 inline-flex w-full items-center justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
>
{{ __('Toggle Date Filters') }}
</button>
</div>
</div>
<div id="dateFilters" class="{{ (($filters['started_from'] ?? '') || ($filters['started_to'] ?? '')) ? '' : 'hidden' }} grid grid-cols-1 gap-4 sm:grid-cols-2">
<div>
<label for="started_from" class="block text-sm font-medium text-gray-700">
{{ __('Joined from') }}
</label>
<input
type="date"
name="started_from"
id="started_from"
value="{{ $filters['started_from'] ?? '' }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
>
</div>
<div>
<label for="started_to" class="block text-sm font-medium text-gray-700">
{{ __('Joined to') }}
</label>
<input
type="date"
name="started_to"
id="started_to"
value="{{ $filters['started_to'] ?? '' }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
>
</div>
</div>
<div class="flex flex-wrap items-center gap-2 justify-between">
<div class="flex gap-2">
<button type="submit" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
{{ __('Apply filters') }}
</button>
<a href="{{ route('admin.members.export', request()->only('search','status')) }}" class="inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
{{ __('Export CSV') }}
</a>
</div>
<div class="flex gap-2">
<a href="{{ route('admin.members.create') }}" class="inline-flex items-center rounded-md border border-transparent bg-green-600 px-4 py-2 text-sm font-medium text-white hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2">
{{ __('Create Member') }}
</a>
<a href="{{ route('admin.members.import-form') }}" class="inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
{{ __('Import CSV') }}
</a>
</div>
</div>
</form>
<div class="mt-4 overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200" role="table">
<thead class="bg-gray-50">
<tr>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
{{ __('Name') }}
</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
{{ __('Email') }}
</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
{{ __('Membership Expires') }}
</th>
<th scope="col" class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500">
<span class="sr-only">{{ __('Actions') }}</span>
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
@forelse ($members as $member)
<tr>
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900">
{{ $member->full_name }}
</td>
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900">
{{ $member->email }}
</td>
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900">
@if ($member->membership_expires_at)
{{ $member->membership_expires_at->toDateString() }}
@else
<span class="text-gray-500">{{ __('Not set') }}</span>
@endif
</td>
<td class="whitespace-nowrap px-4 py-3 text-right text-sm font-medium">
<a href="{{ route('admin.members.show', $member) }}" class="text-indigo-600 hover:text-indigo-900">
{{ __('View') }}
</a>
</td>
</tr>
@empty
<tr>
<td colspan="4" class="px-4 py-4 text-sm text-gray-500">
{{ __('No members found.') }}
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div class="mt-4">
{{ $members->links() }}
</div>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,316 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
{{ __('Member details') }}
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8 space-y-6">
{{-- Pending Payment Alert for Admin --}}
@php
$approvedPayment = $member->payments()
->where('status', \App\Models\MembershipPayment::STATUS_APPROVED_CHAIR)
->latest()
->first();
@endphp
@if($approvedPayment && $member->isPending() && (Auth::user()->can('activate_memberships') || Auth::user()->is_admin))
<div class="rounded-md bg-green-50 dark:bg-green-900/30 p-4">
<div class="flex">
<div class="flex-shrink-0">
<svg class="h-5 w-5 text-green-400" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
</svg>
</div>
<div class="ml-3 flex-1">
<h3 class="text-sm font-medium text-green-800 dark:text-green-200">
{{ __('Ready for Activation') }}
</h3>
<div class="mt-2 text-sm text-green-700 dark:text-green-300">
<p>{{ __('This member has a fully approved payment and is ready for membership activation.') }}</p>
</div>
<div class="mt-4">
<a href="{{ route('admin.members.activate', $member) }}" class="inline-flex items-center rounded-md bg-green-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600">
{{ __('Activate Membership') }}
</a>
</div>
</div>
</div>
</div>
@endif
<section aria-labelledby="member-info-heading" class="bg-white dark:bg-gray-800 shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-3">
@if ($member->user?->profilePhotoUrl())
<img src="{{ $member->user->profilePhotoUrl() }}" alt="{{ __('Profile photo') }}" class="h-16 w-16 rounded-full object-cover ring-2 ring-indigo-500">
@endif
<div>
<h3 id="member-info-heading" class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100">
{{ $member->full_name }}
</h3>
<div class="mt-1 flex items-center gap-2">
{!! $member->membership_status_badge !!}
<span class="inline-flex items-center rounded-md bg-gray-100 dark:bg-gray-700 px-2.5 py-0.5 text-xs font-medium text-gray-800 dark:text-gray-200">
{{ $member->membership_type_label }}
</span>
</div>
</div>
</div>
<a href="{{ route('admin.members.edit', $member) }}" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
{{ __('Edit') }}
</a>
</div>
<dl class="mt-5 grid grid-cols-1 gap-5 sm:grid-cols-2">
<div class="overflow-hidden rounded-lg bg-gray-50 dark:bg-gray-700 px-4 py-5 sm:p-6">
<dt class="truncate text-sm font-medium text-gray-500 dark:text-gray-400">
{{ __('Email') }}
</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
{{ $member->email }}
</dd>
</div>
<div class="overflow-hidden rounded-lg bg-gray-50 dark:bg-gray-700 px-4 py-5 sm:p-6">
<dt class="truncate text-sm font-medium text-gray-500 dark:text-gray-400">
{{ __('Phone') }}
</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
{{ $member->phone ?? __('Not set') }}
</dd>
</div>
<div class="overflow-hidden rounded-lg bg-gray-50 dark:bg-gray-700 px-4 py-5 sm:p-6">
<dt class="truncate text-sm font-medium text-gray-500 dark:text-gray-400">
{{ __('Membership Status') }}
</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
{{ $member->membership_status_label }}
</dd>
</div>
<div class="overflow-hidden rounded-lg bg-gray-50 dark:bg-gray-700 px-4 py-5 sm:p-6">
<dt class="truncate text-sm font-medium text-gray-500 dark:text-gray-400">
{{ __('Membership Type') }}
</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
{{ $member->membership_type_label }}
</dd>
</div>
<div class="overflow-hidden rounded-lg bg-gray-50 dark:bg-gray-700 px-4 py-5 sm:p-6">
<dt class="truncate text-sm font-medium text-gray-500 dark:text-gray-400">
{{ __('Membership start') }}
</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
@if ($member->membership_started_at)
{{ $member->membership_started_at->toDateString() }}
@else
{{ __('Not set') }}
@endif
</dd>
</div>
<div class="overflow-hidden rounded-lg bg-gray-50 dark:bg-gray-700 px-4 py-5 sm:p-6">
<dt class="truncate text-sm font-medium text-gray-500 dark:text-gray-400">
{{ __('Membership expires') }}
</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
@if ($member->membership_expires_at)
{{ $member->membership_expires_at->toDateString() }}
@else
{{ __('Not set') }}
@endif
</dd>
</div>
<div class="overflow-hidden rounded-lg bg-gray-50 px-4 py-5 sm:p-6 sm:col-span-2">
<dt class="truncate text-sm font-medium text-gray-500">
{{ __('Address') }}
</dt>
<dd class="mt-1 text-sm text-gray-900 space-y-1">
<div>{{ $member->address_line_1 ?? __('Not set') }}</div>
@if ($member->address_line_2)
<div>{{ $member->address_line_2 }}</div>
@endif
<div>
{{ $member->city }}
@if ($member->postal_code)
, {{ $member->postal_code }}
@endif
</div>
</dd>
</div>
<div class="overflow-hidden rounded-lg bg-gray-50 px-4 py-5 sm:p-6 sm:col-span-2">
<dt class="truncate text-sm font-medium text-gray-500">
{{ __('Emergency Contact') }}
</dt>
<dd class="mt-1 text-sm text-gray-900 space-y-1">
<div>{{ $member->emergency_contact_name ?? __('Not set') }}</div>
<div>{{ $member->emergency_contact_phone ?? '' }}</div>
</dd>
</div>
</dl>
</div>
</section>
@if ($member->user)
<section aria-labelledby="roles-heading" class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6 space-y-4">
<div class="flex items-center justify-between">
<h3 id="roles-heading" class="text-lg font-medium leading-6 text-gray-900">
{{ __('Roles') }}
</h3>
</div>
<div class="flex flex-wrap gap-2">
@forelse ($member->user->roles as $role)
<span class="inline-flex items-center rounded-full bg-indigo-100 px-3 py-1 text-sm font-medium text-indigo-800">
{{ $role->name }}
</span>
@empty
<p class="text-sm text-gray-500">{{ __('No roles assigned.') }}</p>
@endforelse
</div>
<form method="POST" action="{{ route('admin.members.roles.update', $member) }}" class="space-y-3">
@csrf
@method('PATCH')
<p class="text-sm text-gray-600">
{{ __('Select roles for this member\'s user account.') }}
</p>
<div class="grid gap-3 sm:grid-cols-2">
@foreach ($roles as $role)
<label class="flex items-center space-x-2">
<input
type="checkbox"
name="roles[]"
value="{{ $role->name }}"
@checked($member->user->hasRole($role->name))
class="rounded border-gray-300 text-indigo-600 shadow-sm focus:ring-indigo-500"
>
<span class="text-sm text-gray-800">
{{ $role->name }}
<span class="block text-xs text-gray-500">{{ $role->description }}</span>
</span>
</label>
@endforeach
</div>
<div class="flex justify-end">
<button type="submit" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
{{ __('Update Roles') }}
</button>
</div>
</form>
</div>
</section>
@endif
<section aria-labelledby="admin-payment-history-heading" class="bg-white dark:bg-gray-800 shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<div class="flex items-center justify-between">
<h3 id="admin-payment-history-heading" class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100">
{{ __('Payment history') }}
</h3>
<a href="{{ route('admin.members.payments.create', $member) }}" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
{{ __('Record payment') }}
</a>
</div>
<div class="mt-5 overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700" role="table">
<thead class="bg-gray-50 dark:bg-gray-700">
<tr>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">
{{ __('Paid at') }}
</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">
{{ __('Amount') }}
</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">
{{ __('Method') }}
</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">
{{ __('Status') }}
</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">
{{ __('Submitted By') }}
</th>
<th scope="col" class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">
{{ __('Actions') }}
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 dark:divide-gray-700 bg-white dark:bg-gray-800">
@forelse ($member->payments as $payment)
<tr>
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900 dark:text-gray-100">
{{ optional($payment->paid_at)->toDateString() }}
</td>
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900 dark:text-gray-100">
TWD {{ number_format($payment->amount, 0) }}
</td>
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900 dark:text-gray-100">
{{ $payment->payment_method_label ?? ($payment->method ?? __('N/A')) }}
</td>
<td class="whitespace-nowrap px-4 py-3 text-sm">
{!! $payment->status_label ?? '<span class="inline-flex items-center rounded-md bg-gray-100 dark:bg-gray-700 px-2 py-1 text-xs font-medium text-gray-600 dark:text-gray-300">' . __('Legacy') . '</span>' !!}
</td>
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900 dark:text-gray-100">
@if($payment->submittedBy)
<div class="flex items-center">
<svg class="mr-1.5 h-4 w-4 text-blue-500" viewBox="0 0 20 20" fill="currentColor">
<path d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" />
</svg>
<span class="text-xs">{{ $payment->submittedBy->name }}</span>
</div>
@else
<span class="text-xs text-gray-500 dark:text-gray-400">{{ __('Admin') }}</span>
@endif
</td>
<td class="whitespace-nowrap px-4 py-3 text-right text-sm space-x-3">
@if($payment->status)
{{-- New payment verification system --}}
@if(Auth::user()->can('view_payment_verifications') || Auth::user()->is_admin)
<a href="{{ route('admin.payment-verifications.show', $payment) }}" class="text-blue-600 hover:text-blue-900 dark:text-blue-400 dark:hover:text-blue-300">
{{ __('Verify') }}
</a>
@endif
@if($payment->receipt_path)
<a href="{{ route('admin.payment-verifications.download-receipt', $payment) }}" class="text-green-600 hover:text-green-900 dark:text-green-400 dark:hover:text-green-300" title="{{ __('Download Receipt') }}">
{{ __('Receipt') }}
</a>
@endif
@else
{{-- Legacy admin-created payments --}}
<a href="{{ route('admin.members.payments.receipt', [$member, $payment]) }}" class="text-green-600 hover:text-green-900 dark:text-green-400 dark:hover:text-green-300" title="{{ __('Download Receipt') }}">
{{ __('Receipt') }}
</a>
<a href="{{ route('admin.members.payments.edit', [$member, $payment]) }}" class="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300">
{{ __('Edit') }}
</a>
@endif
</td>
</tr>
@empty
<tr>
<td colspan="6" class="px-4 py-8 text-center text-sm text-gray-500 dark:text-gray-400">
<svg class="mx-auto h-12 w-12 text-gray-400 dark:text-gray-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z" />
</svg>
<p class="mt-2">{{ __('No payment records found.') }}</p>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</section>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,184 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
製作付款單
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-4xl sm:px-6 lg:px-8 space-y-4">
@if (session('error'))
<div class="rounded-md bg-red-50 p-4">
<p class="text-sm font-medium text-red-800">{{ session('error') }}</p>
</div>
@endif
<!-- Finance Document Info -->
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium leading-6 text-gray-900 mb-4">財務申請單資訊</h3>
<dl class="grid grid-cols-1 gap-x-4 gap-y-4 sm:grid-cols-2">
<div>
<dt class="text-sm font-medium text-gray-500">申請標題</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $financeDocument->title }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">申請金額</dt>
<dd class="mt-1 text-sm text-gray-900">NT$ {{ number_format($financeDocument->amount, 2) }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">申請類型</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $financeDocument->getRequestTypeText() }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">金額級別</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $financeDocument->getAmountTierText() }}</dd>
</div>
@if($financeDocument->member)
<div>
<dt class="text-sm font-medium text-gray-500">關聯會員</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $financeDocument->member->full_name }}</dd>
</div>
@endif
<div>
<dt class="text-sm font-medium text-gray-500">申請人</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $financeDocument->submittedBy->name }}</dd>
</div>
</dl>
</div>
</div>
<!-- Payment Order Form -->
<form method="POST" action="{{ route('admin.payment-orders.store', $financeDocument) }}" class="space-y-4">
@csrf
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium leading-6 text-gray-900 mb-4">付款單資訊</h3>
<div class="grid grid-cols-1 gap-6">
<!-- Payee Name -->
<div>
<label for="payee_name" class="block text-sm font-medium text-gray-700">
收款人姓名 <span class="text-red-500">*</span>
</label>
<input type="text" name="payee_name" id="payee_name" required
value="{{ old('payee_name', $financeDocument->member->full_name ?? '') }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm @error('payee_name') border-red-300 @enderror">
@error('payee_name')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Payment Method -->
<div>
<label for="payment_method" class="block text-sm font-medium text-gray-700">
付款方式 <span class="text-red-500">*</span>
</label>
<select name="payment_method" id="payment_method" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm @error('payment_method') border-red-300 @enderror">
<option value="">請選擇付款方式</option>
<option value="bank_transfer" {{ old('payment_method') == 'bank_transfer' ? 'selected' : '' }}>銀行轉帳</option>
<option value="check" {{ old('payment_method') == 'check' ? 'selected' : '' }}>支票</option>
<option value="cash" {{ old('payment_method') == 'cash' ? 'selected' : '' }}>現金</option>
</select>
@error('payment_method')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Bank Information (shown when bank_transfer is selected) -->
<div id="bank_info" class="space-y-4" style="display: none;">
<div>
<label for="payee_bank_name" class="block text-sm font-medium text-gray-700">
銀行名稱
</label>
<input type="text" name="payee_bank_name" id="payee_bank_name"
value="{{ old('payee_bank_name') }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
</div>
<div>
<label for="payee_bank_code" class="block text-sm font-medium text-gray-700">
銀行代碼
</label>
<input type="text" name="payee_bank_code" id="payee_bank_code" maxlength="10"
value="{{ old('payee_bank_code') }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
</div>
<div>
<label for="payee_account_number" class="block text-sm font-medium text-gray-700">
銀行帳號
</label>
<input type="text" name="payee_account_number" id="payee_account_number" maxlength="30"
value="{{ old('payee_account_number') }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
</div>
</div>
<!-- Payment Amount -->
<div>
<label for="payment_amount" class="block text-sm font-medium text-gray-700">
付款金額 <span class="text-red-500">*</span>
</label>
<div class="relative mt-1 rounded-md shadow-sm">
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
<span class="text-gray-500 sm:text-sm">NT$</span>
</div>
<input type="number" name="payment_amount" id="payment_amount" step="0.01" min="0" required
value="{{ old('payment_amount', $financeDocument->amount) }}"
class="block w-full rounded-md border-gray-300 pl-12 focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm @error('payment_amount') border-red-300 @enderror">
</div>
@error('payment_amount')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Notes -->
<div>
<label for="notes" class="block text-sm font-medium text-gray-700">
備註
</label>
<textarea name="notes" id="notes" rows="3"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">{{ old('notes') }}</textarea>
@error('notes')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
</div>
</div>
</div>
<!-- Form Actions -->
<div class="flex justify-end space-x-3">
<a href="{{ route('admin.finance.show', $financeDocument) }}" class="inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
取消
</a>
<button type="submit" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
製作付款單
</button>
</div>
</form>
</div>
</div>
@push('scripts')
<script>
// Show/hide bank information based on payment method
document.getElementById('payment_method').addEventListener('change', function() {
const bankInfo = document.getElementById('bank_info');
if (this.value === 'bank_transfer') {
bankInfo.style.display = 'block';
} else {
bankInfo.style.display = 'none';
}
});
// Trigger on page load to handle old input
if (document.getElementById('payment_method').value === 'bank_transfer') {
document.getElementById('bank_info').style.display = 'block';
}
</script>
@endpush
</x-app-layout>

View File

@@ -0,0 +1,153 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
付款單管理
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8 space-y-4">
@if (session('status'))
<div class="rounded-md bg-green-50 p-4">
<p class="text-sm font-medium text-green-800">{{ session('status') }}</p>
</div>
@endif
@if (session('error'))
<div class="rounded-md bg-red-50 p-4">
<p class="text-sm font-medium text-red-800">{{ session('error') }}</p>
</div>
@endif
<!-- Filters -->
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<form method="GET" action="{{ route('admin.payment-orders.index') }}" class="grid grid-cols-1 gap-4 sm:grid-cols-4">
<div>
<label for="status" class="block text-sm font-medium text-gray-700">狀態</label>
<select name="status" id="status" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
<option value="">全部</option>
<option value="draft" {{ request('status') == 'draft' ? 'selected' : '' }}>草稿</option>
<option value="pending_verification" {{ request('status') == 'pending_verification' ? 'selected' : '' }}>待出納覆核</option>
<option value="verified" {{ request('status') == 'verified' ? 'selected' : '' }}>已覆核</option>
<option value="executed" {{ request('status') == 'executed' ? 'selected' : '' }}>已執行付款</option>
<option value="cancelled" {{ request('status') == 'cancelled' ? 'selected' : '' }}>已取消</option>
</select>
</div>
<div>
<label for="verification_status" class="block text-sm font-medium text-gray-700">覆核狀態</label>
<select name="verification_status" id="verification_status" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
<option value="">全部</option>
<option value="pending" {{ request('verification_status') == 'pending' ? 'selected' : '' }}>待覆核</option>
<option value="approved" {{ request('verification_status') == 'approved' ? 'selected' : '' }}>已通過</option>
<option value="rejected" {{ request('verification_status') == 'rejected' ? 'selected' : '' }}>已駁回</option>
</select>
</div>
<div>
<label for="execution_status" class="block text-sm font-medium text-gray-700">執行狀態</label>
<select name="execution_status" id="execution_status" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
<option value="">全部</option>
<option value="pending" {{ request('execution_status') == 'pending' ? 'selected' : '' }}>待執行</option>
<option value="completed" {{ request('execution_status') == 'completed' ? 'selected' : '' }}>已完成</option>
<option value="failed" {{ request('execution_status') == 'failed' ? 'selected' : '' }}>失敗</option>
</select>
</div>
<div class="flex items-end">
<button type="submit" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
篩選
</button>
<a href="{{ route('admin.payment-orders.index') }}" class="ml-2 inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
清除
</a>
</div>
</form>
</div>
</div>
<!-- Payment Orders Table -->
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
付款單號
</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
收款人
</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
金額
</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
付款方式
</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
狀態
</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
製單人
</th>
<th scope="col" class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500">
<span class="sr-only">操作</span>
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
@forelse ($paymentOrders as $order)
<tr>
<td class="whitespace-nowrap px-4 py-4 text-sm font-medium text-gray-900">
{{ $order->payment_order_number }}
</td>
<td class="whitespace-nowrap px-4 py-4 text-sm text-gray-500">
{{ $order->payee_name }}
</td>
<td class="whitespace-nowrap px-4 py-4 text-sm text-gray-500">
NT$ {{ number_format($order->payment_amount, 2) }}
</td>
<td class="whitespace-nowrap px-4 py-4 text-sm text-gray-500">
{{ $order->getPaymentMethodText() }}
</td>
<td class="whitespace-nowrap px-4 py-4 text-sm">
<span class="inline-flex rounded-full px-2 text-xs font-semibold leading-5
@if($order->status === 'executed') bg-green-100 text-green-800
@elseif($order->status === 'verified') bg-blue-100 text-blue-800
@elseif($order->status === 'pending_verification') bg-yellow-100 text-yellow-800
@elseif($order->status === 'cancelled') bg-red-100 text-red-800
@else bg-gray-100 text-gray-800
@endif">
{{ $order->getStatusText() }}
</span>
</td>
<td class="whitespace-nowrap px-4 py-4 text-sm text-gray-500">
{{ $order->createdByAccountant->name ?? 'N/A' }}
</td>
<td class="whitespace-nowrap px-4 py-4 text-right text-sm font-medium">
<a href="{{ route('admin.payment-orders.show', $order) }}" class="text-indigo-600 hover:text-indigo-900">
查看
</a>
</td>
</tr>
@empty
<tr>
<td colspan="7" class="px-4 py-8 text-center text-sm text-gray-500">
沒有付款單記錄
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div class="mt-4">
{{ $paymentOrders->links() }}
</div>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,298 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
付款單詳情: {{ $paymentOrder->payment_order_number }}
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-5xl sm:px-6 lg:px-8 space-y-4">
@if (session('status'))
<div class="rounded-md bg-green-50 p-4">
<p class="text-sm font-medium text-green-800">{{ session('status') }}</p>
</div>
@endif
@if (session('error'))
<div class="rounded-md bg-red-50 p-4">
<p class="text-sm font-medium text-red-800">{{ session('error') }}</p>
</div>
@endif
<!-- Payment Order Info -->
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-medium leading-6 text-gray-900">付款單資訊</h3>
<span class="inline-flex rounded-full px-3 py-1 text-sm font-semibold
@if($paymentOrder->status === 'executed') bg-green-100 text-green-800
@elseif($paymentOrder->status === 'verified') bg-blue-100 text-blue-800
@elseif($paymentOrder->status === 'pending_verification') bg-yellow-100 text-yellow-800
@elseif($paymentOrder->status === 'cancelled') bg-red-100 text-red-800
@else bg-gray-100 text-gray-800
@endif">
{{ $paymentOrder->getStatusText() }}
</span>
</div>
<dl class="grid grid-cols-1 gap-x-4 gap-y-4 sm:grid-cols-2">
<div>
<dt class="text-sm font-medium text-gray-500">付款單號</dt>
<dd class="mt-1 text-sm text-gray-900 font-mono">{{ $paymentOrder->payment_order_number }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">收款人</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $paymentOrder->payee_name }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">付款金額</dt>
<dd class="mt-1 text-sm text-gray-900 font-semibold">NT$ {{ number_format($paymentOrder->payment_amount, 2) }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">付款方式</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $paymentOrder->getPaymentMethodText() }}</dd>
</div>
@if($paymentOrder->payment_method === 'bank_transfer')
<div>
<dt class="text-sm font-medium text-gray-500">銀行名稱</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $paymentOrder->payee_bank_name ?? 'N/A' }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">銀行代碼</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $paymentOrder->payee_bank_code ?? 'N/A' }}</dd>
</div>
<div class="sm:col-span-2">
<dt class="text-sm font-medium text-gray-500">銀行帳號</dt>
<dd class="mt-1 text-sm text-gray-900 font-mono">{{ $paymentOrder->payee_account_number ?? 'N/A' }}</dd>
</div>
@endif
<div class="sm:col-span-2">
<dt class="text-sm font-medium text-gray-500">製單人(會計)</dt>
<dd class="mt-1 text-sm text-gray-900">
{{ $paymentOrder->createdByAccountant->name }} - {{ $paymentOrder->created_at->format('Y-m-d H:i') }}
</dd>
</div>
@if($paymentOrder->notes)
<div class="sm:col-span-2">
<dt class="text-sm font-medium text-gray-500">備註</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $paymentOrder->notes }}</dd>
</div>
@endif
</dl>
</div>
</div>
<!-- Verification Section -->
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium leading-6 text-gray-900 mb-4">出納覆核</h3>
@if($paymentOrder->verified_at)
<dl class="grid grid-cols-1 gap-x-4 gap-y-4 sm:grid-cols-2">
<div>
<dt class="text-sm font-medium text-gray-500">覆核人</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $paymentOrder->verifiedByCashier->name }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">覆核時間</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $paymentOrder->verified_at->format('Y-m-d H:i') }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">覆核狀態</dt>
<dd class="mt-1 text-sm">
<span class="inline-flex rounded-full px-2 text-xs font-semibold
@if($paymentOrder->verification_status === 'approved') bg-green-100 text-green-800
@elseif($paymentOrder->verification_status === 'rejected') bg-red-100 text-red-800
@else bg-yellow-100 text-yellow-800
@endif">
@if($paymentOrder->verification_status === 'approved') 通過
@elseif($paymentOrder->verification_status === 'rejected') 駁回
@else 待覆核
@endif
</span>
</dd>
</div>
@if($paymentOrder->verification_notes)
<div class="sm:col-span-2">
<dt class="text-sm font-medium text-gray-500">覆核備註</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $paymentOrder->verification_notes }}</dd>
</div>
@endif
</dl>
@else
<p class="text-sm text-gray-500 mb-4">此付款單待出納覆核</p>
@can('verify_payment_order')
@if($paymentOrder->canBeVerifiedByCashier())
<form method="POST" action="{{ route('admin.payment-orders.verify', $paymentOrder) }}" class="space-y-4">
@csrf
<div>
<label for="verification_notes" class="block text-sm font-medium text-gray-700">覆核備註</label>
<textarea name="verification_notes" id="verification_notes" rows="3"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"></textarea>
</div>
<div class="flex space-x-3">
<button type="submit" name="action" value="approve"
class="inline-flex items-center rounded-md border border-transparent bg-green-600 px-4 py-2 text-sm font-medium text-white hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2">
通過覆核
</button>
<button type="submit" name="action" value="reject"
class="inline-flex items-center rounded-md border border-transparent bg-red-600 px-4 py-2 text-sm font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2">
駁回
</button>
</div>
</form>
@endif
@endcan
@endif
</div>
</div>
<!-- Execution Section -->
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium leading-6 text-gray-900 mb-4">付款執行</h3>
@if($paymentOrder->executed_at)
<dl class="grid grid-cols-1 gap-x-4 gap-y-4 sm:grid-cols-2">
<div>
<dt class="text-sm font-medium text-gray-500">執行人</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $paymentOrder->executedByCashier->name }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">執行時間</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $paymentOrder->executed_at->format('Y-m-d H:i') }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">執行狀態</dt>
<dd class="mt-1 text-sm">
<span class="inline-flex rounded-full px-2 text-xs font-semibold
@if($paymentOrder->execution_status === 'completed') bg-green-100 text-green-800
@elseif($paymentOrder->execution_status === 'failed') bg-red-100 text-red-800
@else bg-yellow-100 text-yellow-800
@endif">
@if($paymentOrder->execution_status === 'completed') 已完成
@elseif($paymentOrder->execution_status === 'failed') 失敗
@else 待執行
@endif
</span>
</dd>
</div>
@if($paymentOrder->transaction_reference)
<div>
<dt class="text-sm font-medium text-gray-500">交易參考號</dt>
<dd class="mt-1 text-sm text-gray-900 font-mono">{{ $paymentOrder->transaction_reference }}</dd>
</div>
@endif
@if($paymentOrder->payment_receipt_path)
<div class="sm:col-span-2">
<dt class="text-sm font-medium text-gray-500">付款憑證</dt>
<dd class="mt-1">
<a href="{{ route('admin.payment-orders.download-receipt', $paymentOrder) }}" class="text-indigo-600 hover:text-indigo-900">
下載憑證
</a>
</dd>
</div>
@endif
</dl>
@else
<p class="text-sm text-gray-500 mb-4">此付款單待執行付款</p>
@can('execute_payment')
@if($paymentOrder->canBeExecuted())
<form method="POST" action="{{ route('admin.payment-orders.execute', $paymentOrder) }}" enctype="multipart/form-data" class="space-y-4">
@csrf
<div>
<label for="transaction_reference" class="block text-sm font-medium text-gray-700">
交易參考號 <span class="text-red-500">*</span>
</label>
<input type="text" name="transaction_reference" id="transaction_reference" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
<p class="mt-1 text-xs text-gray-500">銀行交易編號或支票號碼</p>
</div>
<div>
<label for="payment_receipt" class="block text-sm font-medium text-gray-700">
付款憑證
</label>
<input type="file" name="payment_receipt" id="payment_receipt"
class="mt-1 block w-full text-sm text-gray-500
file:mr-4 file:py-2 file:px-4
file:rounded-md file:border-0
file:text-sm file:font-semibold
file:bg-indigo-50 file:text-indigo-700
hover:file:bg-indigo-100">
<p class="mt-1 text-xs text-gray-500">上傳轉帳收據或付款證明(最大 10MB</p>
</div>
<div>
<label for="execution_notes" class="block text-sm font-medium text-gray-700">執行備註</label>
<textarea name="execution_notes" id="execution_notes" rows="3"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"></textarea>
</div>
<div>
<button type="submit"
class="inline-flex items-center rounded-md border border-transparent bg-green-600 px-4 py-2 text-sm font-medium text-white hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2">
確認執行付款
</button>
</div>
</form>
@endif
@endcan
@endif
</div>
</div>
<!-- Related Finance Document -->
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium leading-6 text-gray-900 mb-4">關聯財務申請單</h3>
<dl class="grid grid-cols-1 gap-x-4 gap-y-4 sm:grid-cols-2">
<div>
<dt class="text-sm font-medium text-gray-500">申請標題</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $paymentOrder->financeDocument->title }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">申請類型</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $paymentOrder->financeDocument->getRequestTypeText() }}</dd>
</div>
@if($paymentOrder->financeDocument->member)
<div>
<dt class="text-sm font-medium text-gray-500">關聯會員</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $paymentOrder->financeDocument->member->full_name }}</dd>
</div>
@endif
<div>
<dt class="text-sm font-medium text-gray-500">申請人</dt>
<dd class="mt-1 text-sm text-gray-900">{{ $paymentOrder->financeDocument->submittedBy->name }}</dd>
</div>
</dl>
<div class="mt-4">
<a href="{{ route('admin.finance.show', $paymentOrder->financeDocument) }}" class="text-indigo-600 hover:text-indigo-900">
查看完整申請單
</a>
</div>
</div>
</div>
<!-- Actions -->
<div class="flex justify-between">
<a href="{{ route('admin.payment-orders.index') }}" class="inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
返回列表
</a>
@can('create_payment_order')
@if(!$paymentOrder->isExecuted() && $paymentOrder->status !== 'cancelled')
<form method="POST" action="{{ route('admin.payment-orders.cancel', $paymentOrder) }}" onsubmit="return confirm('確定要取消此付款單嗎?');">
@csrf
<button type="submit" class="inline-flex items-center rounded-md border border-transparent bg-red-600 px-4 py-2 text-sm font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2">
取消付款單
</button>
</form>
@endif
@endcan
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,157 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
{{ __('Payment Verification Dashboard') }}
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8">
@if (session('status'))
<div class="mb-4 rounded-md bg-green-50 p-4 dark:bg-green-900/30 border-l-4 border-green-400">
<p class="text-sm text-green-800 dark:text-green-200">{{ session('status') }}</p>
</div>
@endif
@if (session('error'))
<div class="mb-4 rounded-md bg-red-50 p-4 dark:bg-red-900/30 border-l-4 border-red-400">
<p class="text-sm text-red-800 dark:text-red-200">{{ session('error') }}</p>
</div>
@endif
{{-- Tabs --}}
<div class="mb-4 border-b border-gray-200 dark:border-gray-700">
<nav class="-mb-px flex space-x-8">
<a href="{{ route('admin.payment-verifications.index', ['tab' => 'all']) }}"
class="border-b-2 py-4 px-1 text-sm font-medium {{ $tab === 'all' ? 'border-indigo-500 text-indigo-600 dark:text-indigo-400' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300' }}">
{{ __('All Payments') }}
</a>
@can('verify_payments_cashier')
<a href="{{ route('admin.payment-verifications.index', ['tab' => 'cashier']) }}"
class="border-b-2 py-4 px-1 text-sm font-medium {{ $tab === 'cashier' ? 'border-indigo-500 text-indigo-600 dark:text-indigo-400' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300' }}">
{{ __('Cashier Queue') }}
@if($counts['pending'] > 0)
<span class="ml-2 rounded-full bg-red-100 px-2 py-0.5 text-xs font-medium text-red-800 dark:bg-red-900 dark:text-red-200">
{{ $counts['pending'] }}
</span>
@endif
</a>
@endcan
@can('verify_payments_accountant')
<a href="{{ route('admin.payment-verifications.index', ['tab' => 'accountant']) }}"
class="border-b-2 py-4 px-1 text-sm font-medium {{ $tab === 'accountant' ? 'border-indigo-500 text-indigo-600 dark:text-indigo-400' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300' }}">
{{ __('Accountant Queue') }}
@if($counts['cashier_approved'] > 0)
<span class="ml-2 rounded-full bg-red-100 px-2 py-0.5 text-xs font-medium text-red-800 dark:bg-red-900 dark:text-red-200">
{{ $counts['cashier_approved'] }}
</span>
@endif
</a>
@endcan
@can('verify_payments_chair')
<a href="{{ route('admin.payment-verifications.index', ['tab' => 'chair']) }}"
class="border-b-2 py-4 px-1 text-sm font-medium {{ $tab === 'chair' ? 'border-indigo-500 text-indigo-600 dark:text-indigo-400' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300' }}">
{{ __('Chair Queue') }}
@if($counts['accountant_approved'] > 0)
<span class="ml-2 rounded-full bg-red-100 px-2 py-0.5 text-xs font-medium text-red-800 dark:bg-red-900 dark:text-red-200">
{{ $counts['accountant_approved'] }}
</span>
@endif
</a>
@endcan
<a href="{{ route('admin.payment-verifications.index', ['tab' => 'approved']) }}"
class="border-b-2 py-4 px-1 text-sm font-medium {{ $tab === 'approved' ? 'border-indigo-500 text-indigo-600 dark:text-indigo-400' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300' }}">
{{ __('Approved') }} ({{ $counts['approved'] }})
</a>
<a href="{{ route('admin.payment-verifications.index', ['tab' => 'rejected']) }}"
class="border-b-2 py-4 px-1 text-sm font-medium {{ $tab === 'rejected' ? 'border-indigo-500 text-indigo-600 dark:text-indigo-400' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300' }}">
{{ __('Rejected') }} ({{ $counts['rejected'] }})
</a>
</nav>
</div>
{{-- Search --}}
<div class="mb-4">
<form method="GET" action="{{ route('admin.payment-verifications.index') }}" class="flex gap-2">
<input type="hidden" name="tab" value="{{ $tab }}">
<input type="text" name="search" value="{{ request('search') }}" placeholder="{{ __('Search by member name, email, or reference...') }}"
class="flex-1 rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
<button type="submit" class="inline-flex items-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500">
{{ __('Search') }}
</button>
</form>
</div>
{{-- Payment List --}}
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800">
<div class="overflow-hidden shadow ring-1 ring-black ring-opacity-5 sm:rounded-lg dark:ring-gray-700">
<table class="min-w-full divide-y divide-gray-300 dark:divide-gray-600">
<thead class="bg-gray-50 dark:bg-gray-900">
<tr>
<th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Member') }}</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Amount') }}</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Payment Date') }}</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Method') }}</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Status') }}</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Submitted') }}</th>
<th scope="col" class="relative py-3.5 pl-3 pr-4"><span class="sr-only">{{ __('Actions') }}</span></th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white dark:divide-gray-700 dark:bg-gray-800">
@forelse($payments as $payment)
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
<td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm">
<div class="font-medium text-gray-900 dark:text-gray-100">{{ $payment->member->full_name }}</div>
<div class="text-gray-500 dark:text-gray-400">{{ $payment->member->email }}</div>
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900 dark:text-gray-100">
TWD {{ number_format($payment->amount, 0) }}
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900 dark:text-gray-100">
{{ $payment->paid_at->format('Y-m-d') }}
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900 dark:text-gray-100">
{{ $payment->payment_method_label }}
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm">
<span class="inline-flex items-center rounded-full px-2 py-1 text-xs font-medium
@if($payment->status === 'pending') bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200
@elseif($payment->status === 'approved_chair') bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200
@elseif($payment->status === 'rejected') bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200
@else bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200
@endif">
{{ $payment->status_label }}
</span>
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400">
{{ $payment->created_at->format('Y-m-d H:i') }}
</td>
<td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium">
<a href="{{ route('admin.payment-verifications.show', $payment) }}" class="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300">
{{ __('View & Verify') }}
</a>
</td>
</tr>
@empty
<tr>
<td colspan="7" class="px-3 py-8 text-center text-sm text-gray-500 dark:text-gray-400">
{{ __('No payments found') }}
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div class="px-4 py-3">
{{ $payments->links() }}
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,247 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
{{ __('Payment Verification') }} - {{ $payment->member->full_name }}
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-4xl sm:px-6 lg:px-8 space-y-6">
@if (session('status'))
<div class="rounded-md bg-green-50 p-4 dark:bg-green-900/30 border-l-4 border-green-400">
<p class="text-sm text-green-800 dark:text-green-200">{{ session('status') }}</p>
</div>
@endif
@if (session('error'))
<div class="rounded-md bg-red-50 p-4 dark:bg-red-900/30 border-l-4 border-red-400">
<p class="text-sm text-red-800 dark:text-red-200">{{ session('error') }}</p>
</div>
@endif
{{-- Payment Details --}}
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">{{ __('Payment Details') }}</h3>
<dl class="grid grid-cols-1 gap-x-4 gap-y-6 sm:grid-cols-2">
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Member') }}</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
{{ $payment->member->full_name }}<br>
<span class="text-gray-500">{{ $payment->member->email }}</span>
</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Status') }}</dt>
<dd class="mt-1">
<span class="inline-flex items-center rounded-full px-2 py-1 text-xs font-medium
@if($payment->status === 'pending') bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200
@elseif($payment->status === 'approved_chair') bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200
@elseif($payment->status === 'rejected') bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200
@else bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200
@endif">
{{ $payment->status_label }}
</span>
</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Amount') }}</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">TWD {{ number_format($payment->amount, 0) }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Payment Date') }}</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">{{ $payment->paid_at->format('Y-m-d') }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Payment Method') }}</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">{{ $payment->payment_method_label }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Reference Number') }}</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">{{ $payment->reference ?? '—' }}</dd>
</div>
<div class="sm:col-span-2">
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Submitted By') }}</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
{{ $payment->submittedBy->name }} on {{ $payment->created_at->format('Y-m-d H:i') }}
</dd>
</div>
@if($payment->notes)
<div class="sm:col-span-2">
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Notes') }}</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100 whitespace-pre-line">{{ $payment->notes }}</dd>
</div>
@endif
@if($payment->receipt_path)
<div class="sm:col-span-2">
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Payment Receipt') }}</dt>
<dd class="mt-1">
<a href="{{ route('admin.payment-verifications.download-receipt', $payment) }}" target="_blank"
class="inline-flex items-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 dark:bg-gray-700 dark:text-gray-100 dark:ring-gray-600">
<svg class="-ml-0.5 mr-1.5 h-5 w-5 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
{{ __('Download Receipt') }}
</a>
</dd>
</div>
@endif
</dl>
</div>
{{-- Verification History --}}
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">{{ __('Verification History') }}</h3>
<div class="flow-root">
<ul role="list" class="-mb-8">
@if($payment->verifiedByCashier)
<li>
<div class="relative pb-8">
<div class="relative flex space-x-3">
<div><span class="h-8 w-8 rounded-full bg-green-500 flex items-center justify-center ring-8 ring-white dark:ring-gray-800">
<svg class="h-5 w-5 text-white" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" /></svg>
</span></div>
<div class="flex min-w-0 flex-1 justify-between space-x-4 pt-1.5">
<div><p class="text-sm text-gray-500 dark:text-gray-400">{{ __('Verified by Cashier') }}: <span class="font-medium text-gray-900 dark:text-gray-100">{{ $payment->verifiedByCashier->name }}</span></p></div>
<div class="whitespace-nowrap text-right text-sm text-gray-500 dark:text-gray-400">{{ $payment->cashier_verified_at->format('Y-m-d H:i') }}</div>
</div>
</div>
</div>
</li>
@endif
@if($payment->verifiedByAccountant)
<li>
<div class="relative pb-8">
<div class="relative flex space-x-3">
<div><span class="h-8 w-8 rounded-full bg-green-500 flex items-center justify-center ring-8 ring-white dark:ring-gray-800">
<svg class="h-5 w-5 text-white" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" /></svg>
</span></div>
<div class="flex min-w-0 flex-1 justify-between space-x-4 pt-1.5">
<div><p class="text-sm text-gray-500 dark:text-gray-400">{{ __('Verified by Accountant') }}: <span class="font-medium text-gray-900 dark:text-gray-100">{{ $payment->verifiedByAccountant->name }}</span></p></div>
<div class="whitespace-nowrap text-right text-sm text-gray-500 dark:text-gray-400">{{ $payment->accountant_verified_at->format('Y-m-d H:i') }}</div>
</div>
</div>
</div>
</li>
@endif
@if($payment->verifiedByChair)
<li>
<div class="relative pb-8">
<div class="relative flex space-x-3">
<div><span class="h-8 w-8 rounded-full bg-green-500 flex items-center justify-center ring-8 ring-white dark:ring-gray-800">
<svg class="h-5 w-5 text-white" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" /></svg>
</span></div>
<div class="flex min-w-0 flex-1 justify-between space-x-4 pt-1.5">
<div><p class="text-sm text-gray-500 dark:text-gray-400">{{ __('Final Approval by Chair') }}: <span class="font-medium text-gray-900 dark:text-gray-100">{{ $payment->verifiedByChair->name }}</span></p></div>
<div class="whitespace-nowrap text-right text-sm text-gray-500 dark:text-gray-400">{{ $payment->chair_verified_at->format('Y-m-d H:i') }}</div>
</div>
</div>
</div>
</li>
@endif
@if($payment->rejectedBy)
<li>
<div class="relative pb-8">
<div class="relative flex space-x-3">
<div><span class="h-8 w-8 rounded-full bg-red-500 flex items-center justify-center ring-8 ring-white dark:ring-gray-800">
<svg class="h-5 w-5 text-white" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" /></svg>
</span></div>
<div class="flex min-w-0 flex-1 justify-between space-x-4 pt-1.5">
<div>
<p class="text-sm text-gray-500 dark:text-gray-400">{{ __('Rejected by') }}: <span class="font-medium text-gray-900 dark:text-gray-100">{{ $payment->rejectedBy->name }}</span></p>
@if($payment->rejection_reason)
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $payment->rejection_reason }}</p>
@endif
</div>
<div class="whitespace-nowrap text-right text-sm text-gray-500 dark:text-gray-400">{{ $payment->rejected_at->format('Y-m-d H:i') }}</div>
</div>
</div>
</div>
</li>
@endif
</ul>
</div>
</div>
{{-- Verification Actions --}}
@if(!$payment->isRejected() && !$payment->isFullyApproved())
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">{{ __('Verification Actions') }}</h3>
<div class="space-y-4">
@if($payment->canBeApprovedByCashier() && Auth::user()->can('verify_payments_cashier'))
<form method="POST" action="{{ route('admin.payment-verifications.approve-cashier', $payment) }}" class="border-l-4 border-green-400 pl-4">
@csrf
<div class="mb-3">
<label for="cashier_notes" class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ __('Notes (Optional)') }}</label>
<textarea name="notes" id="cashier_notes" rows="2" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100"></textarea>
</div>
<button type="submit" class="inline-flex justify-center rounded-md bg-green-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-green-500">
{{ __('Approve as Cashier') }}
</button>
</form>
@endif
@if($payment->canBeApprovedByAccountant() && Auth::user()->can('verify_payments_accountant'))
<form method="POST" action="{{ route('admin.payment-verifications.approve-accountant', $payment) }}" class="border-l-4 border-green-400 pl-4">
@csrf
<div class="mb-3">
<label for="accountant_notes" class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ __('Notes (Optional)') }}</label>
<textarea name="notes" id="accountant_notes" rows="2" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100"></textarea>
</div>
<button type="submit" class="inline-flex justify-center rounded-md bg-green-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-green-500">
{{ __('Approve as Accountant') }}
</button>
</form>
@endif
@if($payment->canBeApprovedByChair() && Auth::user()->can('verify_payments_chair'))
<form method="POST" action="{{ route('admin.payment-verifications.approve-chair', $payment) }}" class="border-l-4 border-green-400 pl-4">
@csrf
<div class="mb-3">
<label for="chair_notes" class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ __('Notes (Optional)') }}</label>
<textarea name="notes" id="chair_notes" rows="2" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100"></textarea>
</div>
<button type="submit" class="inline-flex justify-center rounded-md bg-green-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-green-500">
{{ __('Final Approval as Chair') }}
</button>
</form>
@endif
{{-- Reject Form --}}
<form method="POST" action="{{ route('admin.payment-verifications.reject', $payment) }}" class="border-l-4 border-red-400 pl-4" onsubmit="return confirm('{{ __('Are you sure you want to reject this payment?') }}')">
@csrf
<div class="mb-3">
<label for="rejection_reason" class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ __('Rejection Reason') }} <span class="text-red-500">*</span></label>
<textarea name="rejection_reason" id="rejection_reason" rows="3" required class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100"></textarea>
</div>
<button type="submit" class="inline-flex justify-center rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500">
{{ __('Reject Payment') }}
</button>
</form>
</div>
</div>
@endif
{{-- Back Button --}}
<div>
<a href="{{ route('admin.payment-verifications.index') }}" class="inline-flex items-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 dark:bg-gray-700 dark:text-gray-100 dark:ring-gray-600">
{{ __('Back to List') }}
</a>
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,93 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
{{ __('Record payment for :name', ['name' => $member->full_name]) }}
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-3xl sm:px-6 lg:px-8">
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<form method="POST" action="{{ route('admin.members.payments.store', $member) }}" class="space-y-6">
@csrf
<div>
<label for="paid_at" class="block text-sm font-medium text-gray-700">
{{ __('Paid at') }}
</label>
<input
type="date"
name="paid_at"
id="paid_at"
value="{{ old('paid_at', now()->toDateString()) }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
required
>
@error('paid_at')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="amount" class="block text-sm font-medium text-gray-700">
{{ __('Amount') }}
</label>
<input
type="number"
step="0.01"
name="amount"
id="amount"
value="{{ old('amount') }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
required
>
@error('amount')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="method" class="block text-sm font-medium text-gray-700">
{{ __('Method') }}
</label>
<input
type="text"
name="method"
id="method"
value="{{ old('method') }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 sm:text-sm"
>
@error('method')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="reference" class="block text-sm font-medium text-gray-700">
{{ __('Reference') }}
</label>
<input
type="text"
name="reference"
id="reference"
value="{{ old('reference') }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 sm:text-sm"
>
@error('reference')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div class="flex justify-end">
<button type="submit" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
{{ __('Save payment') }}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,102 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
{{ __('Edit payment for :name', ['name' => $member->full_name]) }}
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-3xl sm:px-6 lg:px-8">
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<form method="POST" action="{{ route('admin.members.payments.update', [$member, $payment]) }}" class="space-y-6">
@csrf
@method('PATCH')
<div>
<label for="paid_at" class="block text-sm font-medium text-gray-700">
{{ __('Paid at') }}
</label>
<input
type="date"
name="paid_at"
id="paid_at"
value="{{ old('paid_at', optional($payment->paid_at)->toDateString()) }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
required
>
@error('paid_at')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="amount" class="block text-sm font-medium text-gray-700">
{{ __('Amount') }}
</label>
<input
type="number"
step="0.01"
name="amount"
id="amount"
value="{{ old('amount', $payment->amount) }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
required
>
@error('amount')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="method" class="block text-sm font-medium text-gray-700">
{{ __('Method') }}
</label>
<input
type="text"
name="method"
id="method"
value="{{ old('method', $payment->method) }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 sm:text-sm"
>
@error('method')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="reference" class="block text-sm font-medium text-gray-700">
{{ __('Reference') }}
</label>
<input
type="text"
name="reference"
id="reference"
value="{{ old('reference', $payment->reference) }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 sm:text-sm"
>
@error('reference')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div class="flex justify-between">
<form method="POST" action="{{ route('admin.members.payments.destroy', [$member, $payment]) }}">
@csrf
@method('DELETE')
<button type="submit" class="inline-flex items-center rounded-md border border-red-300 bg-white px-4 py-2 text-sm font-medium text-red-700 hover:bg-red-50 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2">
{{ __('Delete') }}
</button>
</form>
<button type="submit" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
{{ __('Save changes') }}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,161 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Payment Receipt #{{ $payment->id }}</title>
<style>
body {
font-family: Arial, sans-serif;
color: #333;
line-height: 1.6;
margin: 0;
padding: 20px;
}
.header {
text-align: center;
margin-bottom: 30px;
border-bottom: 2px solid #4F46E5;
padding-bottom: 20px;
}
.header h1 {
margin: 0;
color: #4F46E5;
font-size: 28px;
}
.header p {
margin: 5px 0;
color: #666;
}
.receipt-details {
margin: 30px 0;
}
.receipt-details table {
width: 100%;
border-collapse: collapse;
}
.receipt-details td {
padding: 10px;
border-bottom: 1px solid #eee;
}
.receipt-details td:first-child {
font-weight: bold;
width: 40%;
color: #555;
}
.amount-box {
background-color: #F0FDF4;
border: 2px solid #10B981;
border-radius: 8px;
padding: 20px;
margin: 30px 0;
text-align: center;
}
.amount-box .label {
font-size: 14px;
color: #666;
margin-bottom: 5px;
}
.amount-box .amount {
font-size: 36px;
font-weight: bold;
color: #10B981;
}
.footer {
margin-top: 50px;
padding-top: 20px;
border-top: 1px solid #eee;
text-align: center;
font-size: 12px;
color: #999;
}
.stamp {
margin-top: 40px;
text-align: right;
}
.stamp p {
margin: 5px 0;
font-size: 12px;
color: #666;
}
</style>
</head>
<body>
<div class="header">
<h1>PAYMENT RECEIPT</h1>
<p>Membership Payment Confirmation</p>
<p>Receipt #{{ $payment->id }}</p>
</div>
<div class="amount-box">
<div class="label">Amount Paid</div>
<div class="amount">${{ number_format($payment->amount, 2) }}</div>
</div>
<div class="receipt-details">
<table>
<tr>
<td>Payment Date:</td>
<td>{{ $payment->paid_at ? $payment->paid_at->format('F d, Y') : 'N/A' }}</td>
</tr>
<tr>
<td>Receipt Date:</td>
<td>{{ now()->format('F d, Y') }}</td>
</tr>
<tr>
<td>Payment Method:</td>
<td>{{ $payment->method ?? 'N/A' }}</td>
</tr>
@if($payment->reference)
<tr>
<td>Reference Number:</td>
<td>{{ $payment->reference }}</td>
</tr>
@endif
</table>
</div>
<div class="receipt-details">
<h3 style="color: #4F46E5; margin-bottom: 15px;">Member Information</h3>
<table>
<tr>
<td>Name:</td>
<td>{{ $member->full_name }}</td>
</tr>
<tr>
<td>Email:</td>
<td>{{ $member->email }}</td>
</tr>
@if($member->phone)
<tr>
<td>Phone:</td>
<td>{{ $member->phone }}</td>
</tr>
@endif
@if($member->membership_started_at)
<tr>
<td>Membership Start:</td>
<td>{{ $member->membership_started_at->format('F d, Y') }}</td>
</tr>
@endif
@if($member->membership_expires_at)
<tr>
<td>Membership Expires:</td>
<td>{{ $member->membership_expires_at->format('F d, Y') }}</td>
</tr>
@endif
</table>
</div>
<div class="stamp">
<p><strong>Issued by:</strong> {{ config('app.name') }}</p>
<p><strong>Date:</strong> {{ now()->format('F d, Y \a\t H:i') }}</p>
</div>
<div class="footer">
<p>This is an official receipt for membership payment.</p>
<p>Please keep this receipt for your records.</p>
<p>For questions or concerns, please contact the membership office.</p>
</div>
</body>
</html>

View File

@@ -0,0 +1,59 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
{{ __('Create Role') }}
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-3xl sm:px-6 lg:px-8">
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<form method="POST" action="{{ route('admin.roles.store') }}" class="space-y-6">
@csrf
<div>
<label for="name" class="block text-sm font-medium text-gray-700">
{{ __('Name') }}
</label>
<input
type="text"
name="name"
id="name"
value="{{ old('name') }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
required
>
@error('name')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="description" class="block text-sm font-medium text-gray-700">
{{ __('Description') }}
</label>
<input
type="text"
name="description"
id="description"
value="{{ old('description') }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
>
@error('description')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div class="flex justify-end">
<button type="submit" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
{{ __('Create Role') }}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,60 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
{{ __('Edit Role') }}
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-3xl sm:px-6 lg:px-8">
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<form method="POST" action="{{ route('admin.roles.update', $role) }}" class="space-y-6">
@csrf
@method('PATCH')
<div>
<label for="name" class="block text-sm font-medium text-gray-700">
{{ __('Name') }}
</label>
<input
type="text"
name="name"
id="name"
value="{{ old('name', $role->name) }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
required
>
@error('name')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="description" class="block text-sm font-medium text-gray-700">
{{ __('Description') }}
</label>
<input
type="text"
name="description"
id="description"
value="{{ old('description', $role->description) }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
>
@error('description')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div class="flex justify-end">
<button type="submit" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
{{ __('Save changes') }}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,71 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
{{ __('Roles') }}
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-6xl sm:px-6 lg:px-8 space-y-6">
@if (session('status'))
<div class="rounded-md bg-green-50 p-4">
<p class="text-sm font-medium text-green-800">{{ session('status') }}</p>
</div>
@endif
<div class="flex justify-end">
<a href="{{ route('admin.roles.create') }}" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
{{ __('Create Role') }}
</a>
</div>
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200" role="table">
<thead class="bg-gray-50">
<tr>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">{{ __('Name') }}</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">{{ __('Description') }}</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">{{ __('Users') }}</th>
<th scope="col" class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500"><span class="sr-only">{{ __('Actions') }}</span></th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
@forelse ($roles as $role)
<tr>
<td class="px-4 py-3 text-sm font-medium text-gray-900">
{{ $role->name }}
</td>
<td class="px-4 py-3 text-sm text-gray-900">
{{ $role->description ?? __('—') }}
</td>
<td class="px-4 py-3 text-sm text-gray-900">
{{ $role->users_count }}
</td>
<td class="px-4 py-3 text-right text-sm">
<a href="{{ route('admin.roles.show', $role) }}" class="text-indigo-600 hover:text-indigo-900">
{{ __('View') }}
</a>
</td>
</tr>
@empty
<tr>
<td colspan="4" class="px-4 py-4 text-sm text-gray-500">
{{ __('No roles found.') }}
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div class="mt-4">
{{ $roles->links() }}
</div>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,118 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
{{ __('Role Details') }}
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-6xl sm:px-6 lg:px-8 space-y-6">
@if (session('status'))
<div class="rounded-md bg-green-50 p-4">
<p class="text-sm font-medium text-green-800">{{ session('status') }}</p>
</div>
@endif
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6 flex items-center justify-between">
<div>
<h3 class="text-lg font-medium text-gray-900">{{ $role->name }}</h3>
<p class="mt-1 text-sm text-gray-600">{{ $role->description ?: __('No description') }}</p>
</div>
<a href="{{ route('admin.roles.edit', $role) }}" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
{{ __('Edit Role') }}
</a>
</div>
</div>
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6 space-y-4">
<h3 class="text-lg font-medium text-gray-900">
{{ __('Assign Users') }}
</h3>
<form method="POST" action="{{ route('admin.roles.assign-users', $role) }}" class="space-y-3">
@csrf
<label for="user_ids" class="block text-sm font-medium text-gray-700">
{{ __('Select users to assign') }}
</label>
<select name="user_ids[]" id="user_ids" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" multiple size="5">
@foreach ($availableUsers as $user)
<option value="{{ $user->id }}">{{ $user->name }} ({{ $user->email }})</option>
@endforeach
</select>
@error('user_ids')
<p class="text-sm text-red-600">{{ $message }}</p>
@enderror
<div class="flex justify-end">
<button type="submit" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
{{ __('Assign Selected') }}
</button>
</div>
</form>
</div>
</div>
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6 space-y-4">
<div class="flex items-center justify-between">
<h3 class="text-lg font-medium text-gray-900">
{{ __('Users with this role') }}
</h3>
<form method="GET" action="{{ route('admin.roles.show', $role) }}" class="flex gap-2" role="search">
<input
type="text"
name="search"
value="{{ $search }}"
placeholder="{{ __('Search users') }}"
class="block rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
>
<button type="submit" class="inline-flex items-center rounded-md border border-transparent bg-gray-200 px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-300">
{{ __('Search') }}
</button>
</form>
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200" role="table">
<thead class="bg-gray-50">
<tr>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">{{ __('Name') }}</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">{{ __('Email') }}</th>
<th scope="col" class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500"><span class="sr-only">{{ __('Remove') }}</span></th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
@forelse ($users as $user)
<tr>
<td class="px-4 py-3 text-sm text-gray-900">{{ $user->name }}</td>
<td class="px-4 py-3 text-sm text-gray-900">{{ $user->email }}</td>
<td class="px-4 py-3 text-right text-sm">
<form method="POST" action="{{ route('admin.roles.remove-user', [$role, $user]) }}" onsubmit="return confirm('{{ __('Remove this role from the user?') }}');">
@csrf
@method('DELETE')
<button type="submit" class="text-red-600 hover:text-red-800">
{{ __('Remove') }}
</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="3" class="px-4 py-4 text-sm text-gray-500">
{{ __('No users assigned to this role.') }}
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div>
{{ $users->links() }}
</div>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,44 @@
<div class="bg-white shadow sm:rounded-lg">
<nav class="space-y-1 p-4">
<a href="{{ route('admin.settings.general') }}"
class="flex items-center px-3 py-2 text-sm font-medium rounded-md {{ request()->routeIs('admin.settings.general') ? 'bg-indigo-50 text-indigo-700' : 'text-gray-700 hover:bg-gray-50' }}">
<svg class="mr-3 h-5 w-5 {{ request()->routeIs('admin.settings.general') ? 'text-indigo-500' : 'text-gray-400' }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
一般設定
</a>
<a href="{{ route('admin.settings.features') }}"
class="flex items-center px-3 py-2 text-sm font-medium rounded-md {{ request()->routeIs('admin.settings.features') ? 'bg-indigo-50 text-indigo-700' : 'text-gray-700 hover:bg-gray-50' }}">
<svg class="mr-3 h-5 w-5 {{ request()->routeIs('admin.settings.features') ? 'text-indigo-500' : 'text-gray-400' }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
</svg>
文件功能
</a>
<a href="{{ route('admin.settings.security') }}"
class="flex items-center px-3 py-2 text-sm font-medium rounded-md {{ request()->routeIs('admin.settings.security') ? 'bg-indigo-50 text-indigo-700' : 'text-gray-700 hover:bg-gray-50' }}">
<svg class="mr-3 h-5 w-5 {{ request()->routeIs('admin.settings.security') ? 'text-indigo-500' : 'text-gray-400' }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
</svg>
安全性與限制
</a>
<a href="{{ route('admin.settings.notifications') }}"
class="flex items-center px-3 py-2 text-sm font-medium rounded-md {{ request()->routeIs('admin.settings.notifications') ? 'bg-indigo-50 text-indigo-700' : 'text-gray-700 hover:bg-gray-50' }}">
<svg class="mr-3 h-5 w-5 {{ request()->routeIs('admin.settings.notifications') ? 'text-indigo-500' : 'text-gray-400' }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
</svg>
通知設定
</a>
<a href="{{ route('admin.settings.advanced') }}"
class="flex items-center px-3 py-2 text-sm font-medium rounded-md {{ request()->routeIs('admin.settings.advanced') ? 'bg-indigo-50 text-indigo-700' : 'text-gray-700 hover:bg-gray-50' }}">
<svg class="mr-3 h-5 w-5 {{ request()->routeIs('admin.settings.advanced') ? 'text-indigo-500' : 'text-gray-400' }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4" />
</svg>
進階設定
</a>
</nav>
</div>

View File

@@ -0,0 +1,239 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
系統設定 - 進階設定
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8">
<div class="grid grid-cols-1 gap-6 lg:grid-cols-4">
<!-- Sidebar -->
<div class="lg:col-span-1">
@include('admin.settings._sidebar')
</div>
<!-- Main Content -->
<div class="lg:col-span-3">
@if (session('status'))
<div class="mb-6 rounded-md bg-green-50 p-4">
<p class="text-sm font-medium text-green-800">{{ session('status') }}</p>
</div>
@endif
<div class="bg-white shadow sm:rounded-lg">
<div class="px-6 py-5 border-b border-gray-200">
<h3 class="text-lg font-medium text-gray-900">進階設定</h3>
<p class="mt-1 text-sm text-gray-600">配置 QR Code、統計、版本控制和其他進階功能</p>
</div>
<form action="{{ route('admin.settings.advanced.update') }}" method="POST" class="px-6 py-6 space-y-8">
@csrf
<!-- QR Code Settings Section -->
<div class="border-b border-gray-200 pb-6">
<h4 class="text-base font-medium text-gray-900 mb-4">QR Code 設定</h4>
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
<div>
<label for="qr_code_size" class="block text-sm font-medium text-gray-700">
QR Code 尺寸(像素)
</label>
<input type="number" name="qr_code_size" id="qr_code_size"
value="{{ old('qr_code_size', $settings['qr_code_size']) }}"
min="100" max="1000"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
required>
@error('qr_code_size')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="qr_code_format" class="block text-sm font-medium text-gray-700">
QR Code 格式
</label>
<select name="qr_code_format" id="qr_code_format"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
required>
<option value="png" {{ $settings['qr_code_format'] === 'png' ? 'selected' : '' }}>PNG</option>
<option value="svg" {{ $settings['qr_code_format'] === 'svg' ? 'selected' : '' }}>SVG</option>
</select>
@error('qr_code_format')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
</div>
</div>
<!-- Statistics Settings Section -->
<div class="border-b border-gray-200 pb-6">
<h4 class="text-base font-medium text-gray-900 mb-4">統計報表設定</h4>
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
<div>
<label for="statistics_time_range" class="block text-sm font-medium text-gray-700">
統計時間範圍(天)
</label>
<input type="number" name="statistics_time_range" id="statistics_time_range"
value="{{ old('statistics_time_range', $settings['statistics_time_range']) }}"
min="7" max="365"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
required>
<p class="mt-1 text-sm text-gray-500">預設顯示的統計天數</p>
@error('statistics_time_range')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="statistics_top_n" class="block text-sm font-medium text-gray-700">
熱門項目顯示數量
</label>
<input type="number" name="statistics_top_n" id="statistics_top_n"
value="{{ old('statistics_top_n', $settings['statistics_top_n']) }}"
min="5" max="100"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
required>
<p class="mt-1 text-sm text-gray-500">顯示前 N 個熱門文件</p>
@error('statistics_top_n')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
</div>
</div>
<!-- Document Settings Section -->
<div class="border-b border-gray-200 pb-6">
<h4 class="text-base font-medium text-gray-900 mb-4">文件設定</h4>
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
<div>
<label for="default_expiration_days" class="block text-sm font-medium text-gray-700">
預設到期天數
</label>
<input type="number" name="default_expiration_days" id="default_expiration_days"
value="{{ old('default_expiration_days', $settings['default_expiration_days']) }}"
min="0" max="3650"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
required>
<p class="mt-1 text-sm text-gray-500">新文件的預設到期天數0 表示永不過期)</p>
@error('default_expiration_days')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="expiration_warning_days" class="block text-sm font-medium text-gray-700">
到期前提醒天數
</label>
<input type="number" name="expiration_warning_days" id="expiration_warning_days"
value="{{ old('expiration_warning_days', $settings['expiration_warning_days']) }}"
min="1" max="365"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
required>
<p class="mt-1 text-sm text-gray-500">到期前幾天發送提醒通知</p>
@error('expiration_warning_days')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="max_tags_per_document" class="block text-sm font-medium text-gray-700">
每個文件的標籤上限
</label>
<input type="number" name="max_tags_per_document" id="max_tags_per_document"
value="{{ old('max_tags_per_document', $settings['max_tags_per_document']) }}"
min="1" max="50"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
required>
@error('max_tags_per_document')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="default_access_level" class="block text-sm font-medium text-gray-700">
預設存取權限
</label>
<select name="default_access_level" id="default_access_level"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
required>
<option value="public" {{ $settings['default_access_level'] === 'public' ? 'selected' : '' }}>公開</option>
<option value="members" {{ $settings['default_access_level'] === 'members' ? 'selected' : '' }}>會員</option>
<option value="admin" {{ $settings['default_access_level'] === 'admin' ? 'selected' : '' }}>管理員</option>
<option value="board" {{ $settings['default_access_level'] === 'board' ? 'selected' : '' }}>理事會</option>
</select>
<p class="mt-1 text-sm text-gray-500">新文件的預設存取權限</p>
@error('default_access_level')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
</div>
<div class="mt-4">
<div class="flex items-start">
<div class="flex items-center h-5">
<input type="checkbox" name="auto_archive_enabled" id="auto_archive_enabled"
{{ $settings['auto_archive_enabled'] ? 'checked' : '' }}
class="w-4 h-4 text-indigo-600 border-gray-300 rounded focus:ring-indigo-500">
</div>
<div class="ml-3 text-sm">
<label for="auto_archive_enabled" class="font-medium text-gray-700">自動封存過期文件</label>
<p class="text-gray-500">當文件到期時自動將其封存</p>
</div>
</div>
</div>
</div>
<!-- System Settings Section -->
<div>
<h4 class="text-base font-medium text-gray-900 mb-4">系統設定</h4>
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
<div>
<label for="audit_log_retention_days" class="block text-sm font-medium text-gray-700">
稽核記錄保留天數
</label>
<input type="number" name="audit_log_retention_days" id="audit_log_retention_days"
value="{{ old('audit_log_retention_days', $settings['audit_log_retention_days']) }}"
min="30" max="3650"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
required>
<p class="mt-1 text-sm text-gray-500">稽核記錄保留的天數</p>
@error('audit_log_retention_days')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="max_versions_retain" class="block text-sm font-medium text-gray-700">
版本保留數量
</label>
<input type="number" name="max_versions_retain" id="max_versions_retain"
value="{{ old('max_versions_retain', $settings['max_versions_retain']) }}"
min="0" max="100"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
required>
<p class="mt-1 text-sm text-gray-500">每個文件保留的最大版本數0 表示無限制)</p>
@error('max_versions_retain')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
</div>
</div>
<!-- Submit Button -->
<div class="flex items-center justify-end pt-4 border-t">
<button type="submit"
class="inline-flex justify-center rounded-md border border-transparent bg-indigo-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
儲存變更
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,124 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
系統設定 - 文件功能
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8">
<div class="grid grid-cols-1 gap-6 lg:grid-cols-4">
<!-- Sidebar -->
<div class="lg:col-span-1">
@include('admin.settings._sidebar')
</div>
<!-- Main Content -->
<div class="lg:col-span-3">
@if (session('status'))
<div class="mb-6 rounded-md bg-green-50 p-4">
<p class="text-sm font-medium text-green-800">{{ session('status') }}</p>
</div>
@endif
<div class="bg-white shadow sm:rounded-lg">
<div class="px-6 py-5 border-b border-gray-200">
<h3 class="text-lg font-medium text-gray-900">文件功能開關</h3>
<p class="mt-1 text-sm text-gray-600">控制文件管理系統的進階功能</p>
</div>
<form action="{{ route('admin.settings.features.update') }}" method="POST" class="px-6 py-6 space-y-6">
@csrf
<!-- QR Codes -->
<div class="flex items-start">
<div class="flex items-center h-5">
<input type="checkbox" name="qr_codes_enabled" id="qr_codes_enabled"
{{ $settings['qr_codes_enabled'] ? 'checked' : '' }}
class="w-4 h-4 text-indigo-600 border-gray-300 rounded focus:ring-indigo-500">
</div>
<div class="ml-3 text-sm">
<label for="qr_codes_enabled" class="font-medium text-gray-700">啟用 QR Code 功能</label>
<p class="text-gray-500">允許為文件產生 QR Code 以便快速存取</p>
</div>
</div>
<!-- Tagging -->
<div class="flex items-start">
<div class="flex items-center h-5">
<input type="checkbox" name="tagging_enabled" id="tagging_enabled"
{{ $settings['tagging_enabled'] ? 'checked' : '' }}
class="w-4 h-4 text-indigo-600 border-gray-300 rounded focus:ring-indigo-500">
</div>
<div class="ml-3 text-sm">
<label for="tagging_enabled" class="font-medium text-gray-700">啟用標籤功能</label>
<p class="text-gray-500">允許為文件添加標籤以便分類和搜尋</p>
</div>
</div>
<!-- Expiration -->
<div class="flex items-start">
<div class="flex items-center h-5">
<input type="checkbox" name="expiration_enabled" id="expiration_enabled"
{{ $settings['expiration_enabled'] ? 'checked' : '' }}
class="w-4 h-4 text-indigo-600 border-gray-300 rounded focus:ring-indigo-500">
</div>
<div class="ml-3 text-sm">
<label for="expiration_enabled" class="font-medium text-gray-700">啟用到期日功能</label>
<p class="text-gray-500">允許設定文件到期日並接收提醒通知</p>
</div>
</div>
<!-- Bulk Import -->
<div class="flex items-start">
<div class="flex items-center h-5">
<input type="checkbox" name="bulk_import_enabled" id="bulk_import_enabled"
{{ $settings['bulk_import_enabled'] ? 'checked' : '' }}
class="w-4 h-4 text-indigo-600 border-gray-300 rounded focus:ring-indigo-500">
</div>
<div class="ml-3 text-sm">
<label for="bulk_import_enabled" class="font-medium text-gray-700">啟用批次匯入功能</label>
<p class="text-gray-500">允許管理員批次匯入多個文件</p>
</div>
</div>
<!-- Statistics -->
<div class="flex items-start">
<div class="flex items-center h-5">
<input type="checkbox" name="statistics_enabled" id="statistics_enabled"
{{ $settings['statistics_enabled'] ? 'checked' : '' }}
class="w-4 h-4 text-indigo-600 border-gray-300 rounded focus:ring-indigo-500">
</div>
<div class="ml-3 text-sm">
<label for="statistics_enabled" class="font-medium text-gray-700">啟用統計報表功能</label>
<p class="text-gray-500">顯示文件下載統計和使用分析</p>
</div>
</div>
<!-- Version History -->
<div class="flex items-start">
<div class="flex items-center h-5">
<input type="checkbox" name="version_history_enabled" id="version_history_enabled"
{{ $settings['version_history_enabled'] ? 'checked' : '' }}
class="w-4 h-4 text-indigo-600 border-gray-300 rounded focus:ring-indigo-500">
</div>
<div class="ml-3 text-sm">
<label for="version_history_enabled" class="font-medium text-gray-700">啟用版本控制功能</label>
<p class="text-gray-500">保留文件歷史版本並允許回溯</p>
</div>
</div>
<!-- Submit Button -->
<div class="flex items-center justify-end pt-4 border-t">
<button type="submit"
class="inline-flex justify-center rounded-md border border-transparent bg-indigo-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
儲存變更
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,80 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
系統設定 - 一般設定
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8">
<div class="grid grid-cols-1 gap-6 lg:grid-cols-4">
<!-- Sidebar -->
<div class="lg:col-span-1">
@include('admin.settings._sidebar')
</div>
<!-- Main Content -->
<div class="lg:col-span-3">
@if (session('status'))
<div class="mb-6 rounded-md bg-green-50 p-4">
<p class="text-sm font-medium text-green-800">{{ session('status') }}</p>
</div>
@endif
<div class="bg-white shadow sm:rounded-lg">
<div class="px-6 py-5 border-b border-gray-200">
<h3 class="text-lg font-medium text-gray-900">一般設定</h3>
<p class="mt-1 text-sm text-gray-600">配置系統的基本資訊</p>
</div>
<form action="{{ route('admin.settings.general.update') }}" method="POST" class="px-6 py-6 space-y-6">
@csrf
<!-- System Name -->
<div>
<label for="system_name" class="block text-sm font-medium text-gray-700">
系統名稱
</label>
<input type="text" name="system_name" id="system_name"
value="{{ old('system_name', $settings['system_name']) }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
required>
<p class="mt-1 text-sm text-gray-500">顯示在系統各處的名稱</p>
@error('system_name')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Timezone -->
<div>
<label for="timezone" class="block text-sm font-medium text-gray-700">
時區
</label>
<select name="timezone" id="timezone"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
required>
<option value="Asia/Taipei" {{ $settings['timezone'] === 'Asia/Taipei' ? 'selected' : '' }}>台北 (Asia/Taipei)</option>
<option value="Asia/Hong_Kong" {{ $settings['timezone'] === 'Asia/Hong_Kong' ? 'selected' : '' }}>香港 (Asia/Hong_Kong)</option>
<option value="Asia/Shanghai" {{ $settings['timezone'] === 'Asia/Shanghai' ? 'selected' : '' }}>上海 (Asia/Shanghai)</option>
<option value="UTC" {{ $settings['timezone'] === 'UTC' ? 'selected' : '' }}>UTC</option>
</select>
<p class="mt-1 text-sm text-gray-500">系統使用的時區設定</p>
@error('timezone')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Submit Button -->
<div class="flex items-center justify-end pt-4 border-t">
<button type="submit"
class="inline-flex justify-center rounded-md border border-transparent bg-indigo-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
儲存變更
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,113 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
系統設定 - 通知設定
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8">
<div class="grid grid-cols-1 gap-6 lg:grid-cols-4">
<!-- Sidebar -->
<div class="lg:col-span-1">
@include('admin.settings._sidebar')
</div>
<!-- Main Content -->
<div class="lg:col-span-3">
@if (session('status'))
<div class="mb-6 rounded-md bg-green-50 p-4">
<p class="text-sm font-medium text-green-800">{{ session('status') }}</p>
</div>
@endif
<div class="bg-white shadow sm:rounded-lg">
<div class="px-6 py-5 border-b border-gray-200">
<h3 class="text-lg font-medium text-gray-900">通知設定</h3>
<p class="mt-1 text-sm text-gray-600">配置系統通知和電子郵件提醒</p>
</div>
<form action="{{ route('admin.settings.notifications.update') }}" method="POST" class="px-6 py-6 space-y-6">
@csrf
<!-- Enable Notifications -->
<div class="flex items-start">
<div class="flex items-center h-5">
<input type="checkbox" name="enabled" id="enabled"
{{ $settings['enabled'] ? 'checked' : '' }}
class="w-4 h-4 text-indigo-600 border-gray-300 rounded focus:ring-indigo-500">
</div>
<div class="ml-3 text-sm">
<label for="enabled" class="font-medium text-gray-700">啟用系統通知</label>
<p class="text-gray-500">啟用所有系統通知功能</p>
</div>
</div>
<!-- Expiration Alerts -->
<div class="flex items-start">
<div class="flex items-center h-5">
<input type="checkbox" name="expiration_alerts_enabled" id="expiration_alerts_enabled"
{{ $settings['expiration_alerts_enabled'] ? 'checked' : '' }}
class="w-4 h-4 text-indigo-600 border-gray-300 rounded focus:ring-indigo-500">
</div>
<div class="ml-3 text-sm">
<label for="expiration_alerts_enabled" class="font-medium text-gray-700">文件到期提醒</label>
<p class="text-gray-500">當文件即將到期時發送電子郵件提醒</p>
</div>
</div>
<!-- Expiration Recipients -->
<div>
<label for="expiration_recipients" class="block text-sm font-medium text-gray-700">
到期通知收件人
</label>
<input type="text" name="expiration_recipients" id="expiration_recipients"
value="{{ old('expiration_recipients', implode(', ', $settings['expiration_recipients'])) }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
placeholder="admin@example.com, manager@example.com">
<p class="mt-1 text-sm text-gray-500">接收到期通知的電子郵件地址,以逗號分隔</p>
@error('expiration_recipients')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Archive Notifications -->
<div class="flex items-start">
<div class="flex items-center h-5">
<input type="checkbox" name="archive_notifications_enabled" id="archive_notifications_enabled"
{{ $settings['archive_notifications_enabled'] ? 'checked' : '' }}
class="w-4 h-4 text-indigo-600 border-gray-300 rounded focus:ring-indigo-500">
</div>
<div class="ml-3 text-sm">
<label for="archive_notifications_enabled" class="font-medium text-gray-700">文件封存通知</label>
<p class="text-gray-500">當文件被封存時發送通知</p>
</div>
</div>
<!-- New Document Alerts -->
<div class="flex items-start">
<div class="flex items-center h-5">
<input type="checkbox" name="new_document_alerts_enabled" id="new_document_alerts_enabled"
{{ $settings['new_document_alerts_enabled'] ? 'checked' : '' }}
class="w-4 h-4 text-indigo-600 border-gray-300 rounded focus:ring-indigo-500">
</div>
<div class="ml-3 text-sm">
<label for="new_document_alerts_enabled" class="font-medium text-gray-700">新文件通知</label>
<p class="text-gray-500">當有新文件上傳時發送通知給相關人員</p>
</div>
</div>
<!-- Submit Button -->
<div class="flex items-center justify-end pt-4 border-t">
<button type="submit"
class="inline-flex justify-center rounded-md border border-transparent bg-indigo-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
儲存變更
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,109 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
系統設定 - 安全性與限制
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8">
<div class="grid grid-cols-1 gap-6 lg:grid-cols-4">
<!-- Sidebar -->
<div class="lg:col-span-1">
@include('admin.settings._sidebar')
</div>
<!-- Main Content -->
<div class="lg:col-span-3">
@if (session('status'))
<div class="mb-6 rounded-md bg-green-50 p-4">
<p class="text-sm font-medium text-green-800">{{ session('status') }}</p>
</div>
@endif
<div class="bg-white shadow sm:rounded-lg">
<div class="px-6 py-5 border-b border-gray-200">
<h3 class="text-lg font-medium text-gray-900">安全性與限制設定</h3>
<p class="mt-1 text-sm text-gray-600">配置下載速率限制和文件上傳限制</p>
</div>
<form action="{{ route('admin.settings.security.update') }}" method="POST" class="px-6 py-6 space-y-6">
@csrf
<!-- Rate Limit - Authenticated Users -->
<div>
<label for="rate_limit_authenticated" class="block text-sm font-medium text-gray-700">
已登入用戶下載限制(每小時)
</label>
<input type="number" name="rate_limit_authenticated" id="rate_limit_authenticated"
value="{{ old('rate_limit_authenticated', $settings['rate_limit_authenticated']) }}"
min="1" max="1000"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
required>
<p class="mt-1 text-sm text-gray-500">已登入用戶每小時可下載的文件次數</p>
@error('rate_limit_authenticated')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Rate Limit - Guest Users -->
<div>
<label for="rate_limit_guest" class="block text-sm font-medium text-gray-700">
訪客下載限制(每小時)
</label>
<input type="number" name="rate_limit_guest" id="rate_limit_guest"
value="{{ old('rate_limit_guest', $settings['rate_limit_guest']) }}"
min="1" max="1000"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
required>
<p class="mt-1 text-sm text-gray-500">未登入訪客每小時可下載的文件次數</p>
@error('rate_limit_guest')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Max File Size -->
<div>
<label for="max_file_size_mb" class="block text-sm font-medium text-gray-700">
檔案大小上限MB
</label>
<input type="number" name="max_file_size_mb" id="max_file_size_mb"
value="{{ old('max_file_size_mb', $settings['max_file_size_mb']) }}"
min="1" max="100"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
required>
<p class="mt-1 text-sm text-gray-500">單一文件上傳的最大檔案大小</p>
@error('max_file_size_mb')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Allowed File Types -->
<div>
<label for="allowed_file_types" class="block text-sm font-medium text-gray-700">
允許的檔案類型
</label>
<input type="text" name="allowed_file_types" id="allowed_file_types"
value="{{ old('allowed_file_types', implode(', ', $settings['allowed_file_types'])) }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
placeholder="pdf, doc, docx, xls, xlsx">
<p class="mt-1 text-sm text-gray-500">允許上傳的檔案副檔名,以逗號分隔</p>
@error('allowed_file_types')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Submit Button -->
<div class="flex items-center justify-end pt-4 border-t">
<button type="submit"
class="inline-flex justify-center rounded-md border border-transparent bg-indigo-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
儲存變更
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,165 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
{{ __('Record Transaction') }} (¤)
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-3xl sm:px-6 lg:px-8">
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<form method="POST" action="{{ route('admin.transactions.store') }}" class="space-y-6">
@csrf
<!-- Transaction Type -->
<div>
<label for="transaction_type" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Transaction Type') }} <span class="text-red-500">*</span>
</label>
<select name="transaction_type" id="transaction_type" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100 @error('transaction_type') border-red-300 @enderror">
<option value="">{{ __('Select type...') }}</option>
<option value="income" @selected(old('transaction_type') === 'income')>{{ __('Income') }} (6e)</option>
<option value="expense" @selected(old('transaction_type') === 'expense')>{{ __('Expense') }} (/ú)</option>
</select>
@error('transaction_type')<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>@enderror
</div>
<!-- Account -->
<div>
<label for="chart_of_account_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Account') }} <span class="text-red-500">*</span>
</label>
<select name="chart_of_account_id" id="chart_of_account_id" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100 @error('chart_of_account_id') border-red-300 @enderror">
<option value="">{{ __('Select account...') }}</option>
<optgroup label="{{ __('Income Accounts') }}">
@foreach($incomeAccounts as $account)
<option value="{{ $account->id }}" @selected(old('chart_of_account_id') == $account->id)>
{{ $account->account_code }} - {{ $account->account_name_zh }}
</option>
@endforeach
</optgroup>
<optgroup label="{{ __('Expense Accounts') }}">
@foreach($expenseAccounts as $account)
<option value="{{ $account->id }}" @selected(old('chart_of_account_id') == $account->id)>
{{ $account->account_code }} - {{ $account->account_name_zh }}
</option>
@endforeach
</optgroup>
</select>
@error('chart_of_account_id')<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>@enderror
</div>
<!-- Transaction Date -->
<div>
<label for="transaction_date" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Transaction Date') }} <span class="text-red-500">*</span>
</label>
<input type="date" name="transaction_date" id="transaction_date" value="{{ old('transaction_date', now()->format('Y-m-d')) }}" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100 @error('transaction_date') border-red-300 @enderror">
@error('transaction_date')<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>@enderror
</div>
<!-- Amount -->
<div>
<label for="amount" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Amount') }} (NT$) <span class="text-red-500">*</span>
</label>
<input type="number" name="amount" id="amount" value="{{ old('amount') }}" step="0.01" min="0.01" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100 @error('amount') border-red-300 @enderror"
placeholder="0.00">
@error('amount')<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>@enderror
</div>
<!-- Description -->
<div>
<label for="description" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Description') }} <span class="text-red-500">*</span>
</label>
<input type="text" name="description" id="description" value="{{ old('description') }}" required maxlength="255"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100 @error('description') border-red-300 @enderror"
placeholder="{{ __('Brief description of the transaction') }}">
@error('description')<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>@enderror
</div>
<!-- Reference Number -->
<div>
<label for="reference_number" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Reference Number') }}
</label>
<input type="text" name="reference_number" id="reference_number" value="{{ old('reference_number') }}" maxlength="255"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100"
placeholder="{{ __('Receipt or invoice number (optional)') }}">
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">{{ __('Optional reference or receipt number') }}</p>
</div>
<!-- Budget Item (Optional) -->
<div>
<label for="budget_item_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Link to Budget') }}
</label>
<select name="budget_item_id" id="budget_item_id"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
<option value="">{{ __('No budget link (standalone transaction)') }}</option>
@foreach($budgets as $budget)
<optgroup label="{{ $budget->name }} ({{ $budget->fiscal_year }})">
@foreach($budget->budgetItems as $item)
<option value="{{ $item->id }}" @selected(old('budget_item_id') == $item->id)>
{{ $item->chartOfAccount->account_code }} - {{ $item->chartOfAccount->account_name_zh }}
</option>
@endforeach
</optgroup>
@endforeach
</select>
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">{{ __('Link this transaction to a budget item to track actual vs budgeted amounts') }}</p>
</div>
<!-- Notes -->
<div>
<label for="notes" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ __('Notes') }}
</label>
<textarea name="notes" id="notes" rows="3"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100"
placeholder="{{ __('Additional notes (optional)') }}">{{ old('notes') }}</textarea>
</div>
<!-- Actions -->
<div class="flex items-center justify-end gap-x-4 border-t border-gray-200 pt-6 dark:border-gray-700">
<a href="{{ route('admin.transactions.index') }}"
class="rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 dark:bg-gray-700 dark:text-gray-100 dark:ring-gray-600 dark:hover:bg-gray-600">
{{ __('Cancel') }}
</a>
<button type="submit"
class="inline-flex justify-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:bg-indigo-500 dark:hover:bg-indigo-400 dark:focus:ring-offset-gray-800">
{{ __('Record Transaction') }}
</button>
</div>
</form>
</div>
<!-- Help Section -->
<div class="mt-6 rounded-lg bg-blue-50 p-4 dark:bg-blue-900/30 border-l-4 border-blue-400">
<div class="flex">
<div class="flex-shrink-0">
<svg class="h-5 w-5 text-blue-400" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z" clip-rule="evenodd"/>
</svg>
</div>
<div class="ml-3">
<h3 class="text-sm font-medium text-blue-800 dark:text-blue-200">{{ __('Recording Transactions') }}</h3>
<div class="mt-2 text-sm text-blue-700 dark:text-blue-300">
<ul class="list-disc pl-5 space-y-1">
<li>{{ __('Choose income or expense type based on money flow') }}</li>
<li>{{ __('Select the appropriate chart of account for categorization') }}</li>
<li>{{ __('Link to a budget item to automatically update budget vs actual tracking') }}</li>
<li>{{ __('Add reference numbers for audit trails and reconciliation') }}</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@@ -0,0 +1,145 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
{{ __('Transactions') }} (¤)
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8">
@if (session('status'))
<div class="mb-4 rounded-md bg-green-50 p-4 dark:bg-green-900/30 border-l-4 border-green-400" role="status" aria-live="polite">
<p class="text-sm text-green-800 dark:text-green-200">{{ session('status') }}</p>
</div>
@endif
<div class="bg-white shadow sm:rounded-lg dark:bg-gray-800 px-4 py-5 sm:p-6">
<!-- Header -->
<div class="sm:flex sm:items-center sm:justify-between mb-6">
<div>
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">{{ __('Transaction List') }}</h3>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">{{ __('Track all income and expense transactions') }}</p>
</div>
<div class="mt-4 sm:mt-0">
<a href="{{ route('admin.transactions.create') }}" class="inline-flex items-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:bg-indigo-500 dark:hover:bg-indigo-400 dark:focus:ring-offset-gray-800">
<svg class="-ml-0.5 mr-1.5 h-5 w-5" fill="currentColor" viewBox="0 0 20 20"><path d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z"/></svg>
{{ __('Record Transaction') }}
</a>
</div>
</div>
<!-- Summary Cards -->
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 mb-6">
<div class="bg-green-50 dark:bg-green-900/30 rounded-lg p-4 border-l-4 border-green-400">
<dt class="text-sm font-medium text-green-800 dark:text-green-200">{{ __('Total Income') }}</dt>
<dd class="mt-1 text-2xl font-semibold text-green-900 dark:text-green-100">NT$ {{ number_format($totalIncome, 2) }}</dd>
</div>
<div class="bg-red-50 dark:bg-red-900/30 rounded-lg p-4 border-l-4 border-red-400">
<dt class="text-sm font-medium text-red-800 dark:text-red-200">{{ __('Total Expense') }}</dt>
<dd class="mt-1 text-2xl font-semibold text-red-900 dark:text-red-100">NT$ {{ number_format($totalExpense, 2) }}</dd>
</div>
</div>
<!-- Filters -->
<form method="GET" class="mb-6 space-y-4" role="search">
<div class="grid grid-cols-1 gap-4 sm:grid-cols-4">
<div>
<label for="transaction_type" class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ __('Type') }}</label>
<select name="transaction_type" id="transaction_type" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
<option value="">{{ __('All') }}</option>
<option value="income" @selected(request('transaction_type') === 'income')>{{ __('Income') }}</option>
<option value="expense" @selected(request('transaction_type') === 'expense')>{{ __('Expense') }}</option>
</select>
</div>
<div>
<label for="chart_of_account_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ __('Account') }}</label>
<select name="chart_of_account_id" id="chart_of_account_id" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
<option value="">{{ __('All Accounts') }}</option>
@foreach($accounts as $account)
<option value="{{ $account->id }}" @selected(request('chart_of_account_id') == $account->id)>{{ $account->account_code }} - {{ $account->account_name_zh }}</option>
@endforeach
</select>
</div>
<div>
<label for="start_date" class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ __('Start Date') }}</label>
<input type="date" name="start_date" id="start_date" value="{{ request('start_date') }}" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
</div>
<div>
<label for="end_date" class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ __('End Date') }}</label>
<input type="date" name="end_date" id="end_date" value="{{ request('end_date') }}" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
</div>
</div>
<div class="flex gap-4">
<div class="flex-1">
<label for="search" class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ __('Search') }}</label>
<input type="text" name="search" id="search" value="{{ request('search') }}" placeholder="{{ __('Description or reference number...') }}" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100">
</div>
<div class="flex items-end">
<button type="submit" class="inline-flex justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 dark:bg-gray-700 dark:text-gray-100 dark:ring-gray-600 dark:hover:bg-gray-600">{{ __('Filter') }}</button>
</div>
</div>
</form>
<!-- Transactions Table -->
<div class="overflow-hidden shadow ring-1 ring-black ring-opacity-5 sm:rounded-lg dark:ring-gray-700">
<table class="min-w-full divide-y divide-gray-300 dark:divide-gray-600">
<caption class="sr-only">{{ __('List of transactions') }}</caption>
<thead class="bg-gray-50 dark:bg-gray-900">
<tr>
<th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Date') }}</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Type') }}</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Account') }}</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Description') }}</th>
<th scope="col" class="px-3 py-3.5 text-right text-sm font-semibold text-gray-900 dark:text-gray-100">{{ __('Amount') }}</th>
<th scope="col" class="relative py-3.5 pl-3 pr-4"><span class="sr-only">{{ __('Actions') }}</span></th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white dark:divide-gray-700 dark:bg-gray-800">
@forelse($transactions as $transaction)
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
<td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm text-gray-900 dark:text-gray-100">
{{ $transaction->transaction_date->format('Y-m-d') }}
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm">
@if($transaction->transaction_type === 'income')
<span class="inline-flex items-center rounded-full bg-green-100 px-2 py-1 text-xs font-medium text-green-800 dark:bg-green-900 dark:text-green-200">{{ __('Income') }}</span>
@else
<span class="inline-flex items-center rounded-full bg-red-100 px-2 py-1 text-xs font-medium text-red-800 dark:bg-red-900 dark:text-red-200">{{ __('Expense') }}</span>
@endif
</td>
<td class="px-3 py-4 text-sm text-gray-900 dark:text-gray-100">
<div class="font-medium">{{ $transaction->chartOfAccount->account_name_zh }}</div>
<div class="text-gray-500 dark:text-gray-400">{{ $transaction->chartOfAccount->account_code }}</div>
</td>
<td class="px-3 py-4 text-sm text-gray-900 dark:text-gray-100">{{ $transaction->description }}</td>
<td class="whitespace-nowrap px-3 py-4 text-sm text-right tabular-nums font-medium {{ $transaction->transaction_type === 'income' ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400' }}">
{{ $transaction->transaction_type === 'income' ? '+' : '-' }}NT$ {{ number_format($transaction->amount, 2) }}
</td>
<td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium">
<a href="{{ route('admin.transactions.show', $transaction) }}" class="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300">{{ __('View') }}</a>
</td>
</tr>
@empty
<tr>
<td colspan="6" class="px-3 py-8 text-center text-sm text-gray-500 dark:text-gray-400">
<p>{{ __('No transactions found') }}</p>
<div class="mt-4">
<a href="{{ route('admin.transactions.create') }}" class="inline-flex items-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 dark:bg-indigo-500 dark:hover:bg-indigo-400">
+ {{ __('Record First Transaction') }}
</a>
</div>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<!-- Pagination -->
@if($transactions->hasPages())
<div class="mt-6">{{ $transactions->links() }}</div>
@endif
</div>
</div>
</div>
</x-app-layout>