Initial commit
This commit is contained in:
3
resources/css/app.css
Normal file
3
resources/css/app.css
Normal file
@@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
7
resources/js/app.js
Normal file
7
resources/js/app.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import './bootstrap';
|
||||
|
||||
import Alpine from 'alpinejs';
|
||||
|
||||
window.Alpine = Alpine;
|
||||
|
||||
Alpine.start();
|
||||
32
resources/js/bootstrap.js
vendored
Normal file
32
resources/js/bootstrap.js
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* We'll load the axios HTTP library which allows us to easily issue requests
|
||||
* to our Laravel back-end. This library automatically handles sending the
|
||||
* CSRF token as a header based on the value of the "XSRF" token cookie.
|
||||
*/
|
||||
|
||||
import axios from 'axios';
|
||||
window.axios = axios;
|
||||
|
||||
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||
|
||||
/**
|
||||
* Echo exposes an expressive API for subscribing to channels and listening
|
||||
* for events that are broadcast by Laravel. Echo and event broadcasting
|
||||
* allows your team to easily build robust real-time web applications.
|
||||
*/
|
||||
|
||||
// import Echo from 'laravel-echo';
|
||||
|
||||
// import Pusher from 'pusher-js';
|
||||
// window.Pusher = Pusher;
|
||||
|
||||
// window.Echo = new Echo({
|
||||
// broadcaster: 'pusher',
|
||||
// key: import.meta.env.VITE_PUSHER_APP_KEY,
|
||||
// cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER ?? 'mt1',
|
||||
// wsHost: import.meta.env.VITE_PUSHER_HOST ? import.meta.env.VITE_PUSHER_HOST : `ws-${import.meta.env.VITE_PUSHER_APP_CLUSTER}.pusher.com`,
|
||||
// wsPort: import.meta.env.VITE_PUSHER_PORT ?? 80,
|
||||
// wssPort: import.meta.env.VITE_PUSHER_PORT ?? 443,
|
||||
// forceTLS: (import.meta.env.VITE_PUSHER_SCHEME ?? 'https') === 'https',
|
||||
// enabledTransports: ['ws', 'wss'],
|
||||
// });
|
||||
149
resources/views/admin/audit/index.blade.php
Normal file
149
resources/views/admin/audit/index.blade.php
Normal 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>
|
||||
287
resources/views/admin/bank-reconciliations/create.blade.php
Normal file
287
resources/views/admin/bank-reconciliations/create.blade.php
Normal 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>
|
||||
165
resources/views/admin/bank-reconciliations/index.blade.php
Normal file
165
resources/views/admin/bank-reconciliations/index.blade.php
Normal 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>
|
||||
482
resources/views/admin/bank-reconciliations/pdf.blade.php
Normal file
482
resources/views/admin/bank-reconciliations/pdf.blade.php
Normal 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>
|
||||
348
resources/views/admin/bank-reconciliations/show.blade.php
Normal file
348
resources/views/admin/bank-reconciliations/show.blade.php
Normal 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>
|
||||
175
resources/views/admin/budgets/create.blade.php
Normal file
175
resources/views/admin/budgets/create.blade.php
Normal 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') }} (t¦)</option>
|
||||
<option value="quarterly" @selected(old('period_type') === 'quarterly')>{{ __('Quarterly') }} (c¦)</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>
|
||||
154
resources/views/admin/budgets/edit.blade.php
Normal file
154
resources/views/admin/budgets/edit.blade.php
Normal 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>
|
||||
210
resources/views/admin/budgets/index.blade.php
Normal file
210
resources/views/admin/budgets/index.blade.php
Normal 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') }} (t¦)
|
||||
</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>
|
||||
127
resources/views/admin/budgets/show.blade.php
Normal file
127
resources/views/admin/budgets/show.blade.php
Normal 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>
|
||||
194
resources/views/admin/cashier-ledger/balance-report.blade.php
Normal file
194
resources/views/admin/cashier-ledger/balance-report.blade.php
Normal 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>
|
||||
212
resources/views/admin/cashier-ledger/create.blade.php
Normal file
212
resources/views/admin/cashier-ledger/create.blade.php
Normal 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>
|
||||
204
resources/views/admin/cashier-ledger/index.blade.php
Normal file
204
resources/views/admin/cashier-ledger/index.blade.php
Normal 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>
|
||||
171
resources/views/admin/cashier-ledger/show.blade.php
Normal file
171
resources/views/admin/cashier-ledger/show.blade.php
Normal 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>
|
||||
283
resources/views/admin/dashboard/index.blade.php
Normal file
283
resources/views/admin/dashboard/index.blade.php
Normal 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>
|
||||
100
resources/views/admin/document-categories/create.blade.php
Normal file
100
resources/views/admin/document-categories/create.blade.php
Normal 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>
|
||||
100
resources/views/admin/document-categories/edit.blade.php
Normal file
100
resources/views/admin/document-categories/edit.blade.php
Normal 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>
|
||||
108
resources/views/admin/document-categories/index.blade.php
Normal file
108
resources/views/admin/document-categories/index.blade.php
Normal 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>
|
||||
127
resources/views/admin/documents/create.blade.php
Normal file
127
resources/views/admin/documents/create.blade.php
Normal 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>
|
||||
109
resources/views/admin/documents/edit.blade.php
Normal file
109
resources/views/admin/documents/edit.blade.php
Normal 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>
|
||||
186
resources/views/admin/documents/index.blade.php
Normal file
186
resources/views/admin/documents/index.blade.php
Normal 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>
|
||||
280
resources/views/admin/documents/show.blade.php
Normal file
280
resources/views/admin/documents/show.blade.php
Normal 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>
|
||||
258
resources/views/admin/documents/statistics.blade.php
Normal file
258
resources/views/admin/documents/statistics.blade.php
Normal 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>
|
||||
117
resources/views/admin/finance/create.blade.php
Normal file
117
resources/views/admin/finance/create.blade.php
Normal 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>
|
||||
|
||||
95
resources/views/admin/finance/index.blade.php
Normal file
95
resources/views/admin/finance/index.blade.php
Normal 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>
|
||||
|
||||
377
resources/views/admin/finance/show.blade.php
Normal file
377
resources/views/admin/finance/show.blade.php
Normal 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">
|
||||
← {{ __('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>
|
||||
80
resources/views/admin/issue-labels/create.blade.php
Normal file
80
resources/views/admin/issue-labels/create.blade.php
Normal 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>
|
||||
77
resources/views/admin/issue-labels/edit.blade.php
Normal file
77
resources/views/admin/issue-labels/edit.blade.php
Normal 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>
|
||||
79
resources/views/admin/issue-labels/index.blade.php
Normal file
79
resources/views/admin/issue-labels/index.blade.php
Normal 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>
|
||||
280
resources/views/admin/issue-reports/index.blade.php
Normal file
280
resources/views/admin/issue-reports/index.blade.php
Normal 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>
|
||||
197
resources/views/admin/issues/create.blade.php
Normal file
197
resources/views/admin/issues/create.blade.php
Normal 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>
|
||||
184
resources/views/admin/issues/edit.blade.php
Normal file
184
resources/views/admin/issues/edit.blade.php
Normal 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>
|
||||
200
resources/views/admin/issues/index.blade.php
Normal file
200
resources/views/admin/issues/index.blade.php
Normal 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>
|
||||
371
resources/views/admin/issues/show.blade.php
Normal file
371
resources/views/admin/issues/show.blade.php
Normal 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>
|
||||
171
resources/views/admin/members/activate.blade.php
Normal file
171
resources/views/admin/members/activate.blade.php
Normal 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>
|
||||
241
resources/views/admin/members/create.blade.php
Normal file
241
resources/views/admin/members/create.blade.php
Normal 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>
|
||||
236
resources/views/admin/members/edit.blade.php
Normal file
236
resources/views/admin/members/edit.blade.php
Normal 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>
|
||||
59
resources/views/admin/members/import.blade.php
Normal file
59
resources/views/admin/members/import.blade.php
Normal 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>
|
||||
182
resources/views/admin/members/index.blade.php
Normal file
182
resources/views/admin/members/index.blade.php
Normal 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>
|
||||
316
resources/views/admin/members/show.blade.php
Normal file
316
resources/views/admin/members/show.blade.php
Normal 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>
|
||||
184
resources/views/admin/payment-orders/create.blade.php
Normal file
184
resources/views/admin/payment-orders/create.blade.php
Normal 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>
|
||||
153
resources/views/admin/payment-orders/index.blade.php
Normal file
153
resources/views/admin/payment-orders/index.blade.php
Normal 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>
|
||||
298
resources/views/admin/payment-orders/show.blade.php
Normal file
298
resources/views/admin/payment-orders/show.blade.php
Normal 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>
|
||||
157
resources/views/admin/payment-verifications/index.blade.php
Normal file
157
resources/views/admin/payment-verifications/index.blade.php
Normal 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>
|
||||
247
resources/views/admin/payment-verifications/show.blade.php
Normal file
247
resources/views/admin/payment-verifications/show.blade.php
Normal 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>
|
||||
93
resources/views/admin/payments/create.blade.php
Normal file
93
resources/views/admin/payments/create.blade.php
Normal 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>
|
||||
|
||||
102
resources/views/admin/payments/edit.blade.php
Normal file
102
resources/views/admin/payments/edit.blade.php
Normal 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>
|
||||
|
||||
161
resources/views/admin/payments/receipt.blade.php
Normal file
161
resources/views/admin/payments/receipt.blade.php
Normal 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>
|
||||
59
resources/views/admin/roles/create.blade.php
Normal file
59
resources/views/admin/roles/create.blade.php
Normal 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>
|
||||
|
||||
60
resources/views/admin/roles/edit.blade.php
Normal file
60
resources/views/admin/roles/edit.blade.php
Normal 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>
|
||||
|
||||
71
resources/views/admin/roles/index.blade.php
Normal file
71
resources/views/admin/roles/index.blade.php
Normal 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>
|
||||
|
||||
118
resources/views/admin/roles/show.blade.php
Normal file
118
resources/views/admin/roles/show.blade.php
Normal 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>
|
||||
|
||||
44
resources/views/admin/settings/_sidebar.blade.php
Normal file
44
resources/views/admin/settings/_sidebar.blade.php
Normal 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>
|
||||
239
resources/views/admin/settings/advanced.blade.php
Normal file
239
resources/views/admin/settings/advanced.blade.php
Normal 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>
|
||||
124
resources/views/admin/settings/features.blade.php
Normal file
124
resources/views/admin/settings/features.blade.php
Normal 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>
|
||||
80
resources/views/admin/settings/general.blade.php
Normal file
80
resources/views/admin/settings/general.blade.php
Normal 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>
|
||||
113
resources/views/admin/settings/notifications.blade.php
Normal file
113
resources/views/admin/settings/notifications.blade.php
Normal 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>
|
||||
109
resources/views/admin/settings/security.blade.php
Normal file
109
resources/views/admin/settings/security.blade.php
Normal 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>
|
||||
165
resources/views/admin/transactions/create.blade.php
Normal file
165
resources/views/admin/transactions/create.blade.php
Normal 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>
|
||||
145
resources/views/admin/transactions/index.blade.php
Normal file
145
resources/views/admin/transactions/index.blade.php
Normal 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>
|
||||
27
resources/views/auth/confirm-password.blade.php
Normal file
27
resources/views/auth/confirm-password.blade.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<x-guest-layout>
|
||||
<div class="mb-4 text-sm text-gray-600">
|
||||
{{ __('This is a secure area of the application. Please confirm your password before continuing.') }}
|
||||
</div>
|
||||
|
||||
<form method="POST" action="{{ route('password.confirm') }}">
|
||||
@csrf
|
||||
|
||||
<!-- Password -->
|
||||
<div>
|
||||
<x-input-label for="password" :value="__('Password')" />
|
||||
|
||||
<x-text-input id="password" class="block mt-1 w-full"
|
||||
type="password"
|
||||
name="password"
|
||||
required autocomplete="current-password" />
|
||||
|
||||
<x-input-error :messages="$errors->get('password')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end mt-4">
|
||||
<x-primary-button>
|
||||
{{ __('Confirm') }}
|
||||
</x-primary-button>
|
||||
</div>
|
||||
</form>
|
||||
</x-guest-layout>
|
||||
25
resources/views/auth/forgot-password.blade.php
Normal file
25
resources/views/auth/forgot-password.blade.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<x-guest-layout>
|
||||
<div class="mb-4 text-sm text-gray-600">
|
||||
{{ __('Forgot your password? No problem. Just let us know your email address and we will email you a password reset link that will allow you to choose a new one.') }}
|
||||
</div>
|
||||
|
||||
<!-- Session Status -->
|
||||
<x-auth-session-status class="mb-4" :status="session('status')" />
|
||||
|
||||
<form method="POST" action="{{ route('password.email') }}">
|
||||
@csrf
|
||||
|
||||
<!-- Email Address -->
|
||||
<div>
|
||||
<x-input-label for="email" :value="__('Email')" />
|
||||
<x-text-input id="email" class="block mt-1 w-full" type="email" name="email" :value="old('email')" required autofocus />
|
||||
<x-input-error :messages="$errors->get('email')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end mt-4">
|
||||
<x-primary-button>
|
||||
{{ __('Email Password Reset Link') }}
|
||||
</x-primary-button>
|
||||
</div>
|
||||
</form>
|
||||
</x-guest-layout>
|
||||
47
resources/views/auth/login.blade.php
Normal file
47
resources/views/auth/login.blade.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<x-guest-layout>
|
||||
<!-- Session Status -->
|
||||
<x-auth-session-status class="mb-4" :status="session('status')" />
|
||||
|
||||
<form method="POST" action="{{ route('login') }}">
|
||||
@csrf
|
||||
|
||||
<!-- Email Address -->
|
||||
<div>
|
||||
<x-input-label for="email" :value="__('Email')" />
|
||||
<x-text-input id="email" class="block mt-1 w-full" type="email" name="email" :value="old('email')" required autofocus autocomplete="username" />
|
||||
<x-input-error :messages="$errors->get('email')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<!-- Password -->
|
||||
<div class="mt-4">
|
||||
<x-input-label for="password" :value="__('Password')" />
|
||||
|
||||
<x-text-input id="password" class="block mt-1 w-full"
|
||||
type="password"
|
||||
name="password"
|
||||
required autocomplete="current-password" />
|
||||
|
||||
<x-input-error :messages="$errors->get('password')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<!-- Remember Me -->
|
||||
<div class="block mt-4">
|
||||
<label for="remember_me" class="inline-flex items-center">
|
||||
<input id="remember_me" type="checkbox" class="rounded border-gray-300 text-indigo-600 shadow-sm focus:ring-indigo-500" name="remember">
|
||||
<span class="ms-2 text-sm text-gray-600">{{ __('Remember me') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end mt-4">
|
||||
@if (Route::has('password.request'))
|
||||
<a class="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" href="{{ route('password.request') }}">
|
||||
{{ __('Forgot your password?') }}
|
||||
</a>
|
||||
@endif
|
||||
|
||||
<x-primary-button class="ms-3">
|
||||
{{ __('Log in') }}
|
||||
</x-primary-button>
|
||||
</div>
|
||||
</form>
|
||||
</x-guest-layout>
|
||||
52
resources/views/auth/register.blade.php
Normal file
52
resources/views/auth/register.blade.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<x-guest-layout>
|
||||
<form method="POST" action="{{ route('register') }}">
|
||||
@csrf
|
||||
|
||||
<!-- Name -->
|
||||
<div>
|
||||
<x-input-label for="name" :value="__('Name')" />
|
||||
<x-text-input id="name" class="block mt-1 w-full" type="text" name="name" :value="old('name')" required autofocus autocomplete="name" />
|
||||
<x-input-error :messages="$errors->get('name')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<!-- Email Address -->
|
||||
<div class="mt-4">
|
||||
<x-input-label for="email" :value="__('Email')" />
|
||||
<x-text-input id="email" class="block mt-1 w-full" type="email" name="email" :value="old('email')" required autocomplete="username" />
|
||||
<x-input-error :messages="$errors->get('email')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<!-- Password -->
|
||||
<div class="mt-4">
|
||||
<x-input-label for="password" :value="__('Password')" />
|
||||
|
||||
<x-text-input id="password" class="block mt-1 w-full"
|
||||
type="password"
|
||||
name="password"
|
||||
required autocomplete="new-password" />
|
||||
|
||||
<x-input-error :messages="$errors->get('password')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<!-- Confirm Password -->
|
||||
<div class="mt-4">
|
||||
<x-input-label for="password_confirmation" :value="__('Confirm Password')" />
|
||||
|
||||
<x-text-input id="password_confirmation" class="block mt-1 w-full"
|
||||
type="password"
|
||||
name="password_confirmation" required autocomplete="new-password" />
|
||||
|
||||
<x-input-error :messages="$errors->get('password_confirmation')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end mt-4">
|
||||
<a class="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" href="{{ route('login') }}">
|
||||
{{ __('Already registered?') }}
|
||||
</a>
|
||||
|
||||
<x-primary-button class="ms-4">
|
||||
{{ __('Register') }}
|
||||
</x-primary-button>
|
||||
</div>
|
||||
</form>
|
||||
</x-guest-layout>
|
||||
39
resources/views/auth/reset-password.blade.php
Normal file
39
resources/views/auth/reset-password.blade.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<x-guest-layout>
|
||||
<form method="POST" action="{{ route('password.store') }}">
|
||||
@csrf
|
||||
|
||||
<!-- Password Reset Token -->
|
||||
<input type="hidden" name="token" value="{{ $request->route('token') }}">
|
||||
|
||||
<!-- Email Address -->
|
||||
<div>
|
||||
<x-input-label for="email" :value="__('Email')" />
|
||||
<x-text-input id="email" class="block mt-1 w-full" type="email" name="email" :value="old('email', $request->email)" required autofocus autocomplete="username" />
|
||||
<x-input-error :messages="$errors->get('email')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<!-- Password -->
|
||||
<div class="mt-4">
|
||||
<x-input-label for="password" :value="__('Password')" />
|
||||
<x-text-input id="password" class="block mt-1 w-full" type="password" name="password" required autocomplete="new-password" />
|
||||
<x-input-error :messages="$errors->get('password')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<!-- Confirm Password -->
|
||||
<div class="mt-4">
|
||||
<x-input-label for="password_confirmation" :value="__('Confirm Password')" />
|
||||
|
||||
<x-text-input id="password_confirmation" class="block mt-1 w-full"
|
||||
type="password"
|
||||
name="password_confirmation" required autocomplete="new-password" />
|
||||
|
||||
<x-input-error :messages="$errors->get('password_confirmation')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end mt-4">
|
||||
<x-primary-button>
|
||||
{{ __('Reset Password') }}
|
||||
</x-primary-button>
|
||||
</div>
|
||||
</form>
|
||||
</x-guest-layout>
|
||||
31
resources/views/auth/verify-email.blade.php
Normal file
31
resources/views/auth/verify-email.blade.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<x-guest-layout>
|
||||
<div class="mb-4 text-sm text-gray-600">
|
||||
{{ __('Thanks for signing up! Before getting started, could you verify your email address by clicking on the link we just emailed to you? If you didn\'t receive the email, we will gladly send you another.') }}
|
||||
</div>
|
||||
|
||||
@if (session('status') == 'verification-link-sent')
|
||||
<div class="mb-4 font-medium text-sm text-green-600">
|
||||
{{ __('A new verification link has been sent to the email address you provided during registration.') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="mt-4 flex items-center justify-between">
|
||||
<form method="POST" action="{{ route('verification.send') }}">
|
||||
@csrf
|
||||
|
||||
<div>
|
||||
<x-primary-button>
|
||||
{{ __('Resend Verification Email') }}
|
||||
</x-primary-button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form method="POST" action="{{ route('logout') }}">
|
||||
@csrf
|
||||
|
||||
<button type="submit" class="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
|
||||
{{ __('Log Out') }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</x-guest-layout>
|
||||
3
resources/views/components/application-logo.blade.php
Normal file
3
resources/views/components/application-logo.blade.php
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 316 316" xmlns="http://www.w3.org/2000/svg" {{ $attributes }}>
|
||||
<path d="M305.8 81.125C305.77 80.995 305.69 80.885 305.65 80.755C305.56 80.525 305.49 80.285 305.37 80.075C305.29 79.935 305.17 79.815 305.07 79.685C304.94 79.515 304.83 79.325 304.68 79.175C304.55 79.045 304.39 78.955 304.25 78.845C304.09 78.715 303.95 78.575 303.77 78.475L251.32 48.275C249.97 47.495 248.31 47.495 246.96 48.275L194.51 78.475C194.33 78.575 194.19 78.725 194.03 78.845C193.89 78.955 193.73 79.045 193.6 79.175C193.45 79.325 193.34 79.515 193.21 79.685C193.11 79.815 192.99 79.935 192.91 80.075C192.79 80.285 192.71 80.525 192.63 80.755C192.58 80.875 192.51 80.995 192.48 81.125C192.38 81.495 192.33 81.875 192.33 82.265V139.625L148.62 164.795V52.575C148.62 52.185 148.57 51.805 148.47 51.435C148.44 51.305 148.36 51.195 148.32 51.065C148.23 50.835 148.16 50.595 148.04 50.385C147.96 50.245 147.84 50.125 147.74 49.995C147.61 49.825 147.5 49.635 147.35 49.485C147.22 49.355 147.06 49.265 146.92 49.155C146.76 49.025 146.62 48.885 146.44 48.785L93.99 18.585C92.64 17.805 90.98 17.805 89.63 18.585L37.18 48.785C37 48.885 36.86 49.035 36.7 49.155C36.56 49.265 36.4 49.355 36.27 49.485C36.12 49.635 36.01 49.825 35.88 49.995C35.78 50.125 35.66 50.245 35.58 50.385C35.46 50.595 35.38 50.835 35.3 51.065C35.25 51.185 35.18 51.305 35.15 51.435C35.05 51.805 35 52.185 35 52.575V232.235C35 233.795 35.84 235.245 37.19 236.025L142.1 296.425C142.33 296.555 142.58 296.635 142.82 296.725C142.93 296.765 143.04 296.835 143.16 296.865C143.53 296.965 143.9 297.015 144.28 297.015C144.66 297.015 145.03 296.965 145.4 296.865C145.5 296.835 145.59 296.775 145.69 296.745C145.95 296.655 146.21 296.565 146.45 296.435L251.36 236.035C252.72 235.255 253.55 233.815 253.55 232.245V174.885L303.81 145.945C305.17 145.165 306 143.725 306 142.155V82.265C305.95 81.875 305.89 81.495 305.8 81.125ZM144.2 227.205L100.57 202.515L146.39 176.135L196.66 147.195L240.33 172.335L208.29 190.625L144.2 227.205ZM244.75 114.995V164.795L226.39 154.225L201.03 139.625V89.825L219.39 100.395L244.75 114.995ZM249.12 57.105L292.81 82.265L249.12 107.425L205.43 82.265L249.12 57.105ZM114.49 184.425L96.13 194.995V85.305L121.49 70.705L139.85 60.135V169.815L114.49 184.425ZM91.76 27.425L135.45 52.585L91.76 77.745L48.07 52.585L91.76 27.425ZM43.67 60.135L62.03 70.705L87.39 85.305V202.545V202.555V202.565C87.39 202.735 87.44 202.895 87.46 203.055C87.49 203.265 87.49 203.485 87.55 203.695V203.705C87.6 203.875 87.69 204.035 87.76 204.195C87.84 204.375 87.89 204.575 87.99 204.745C87.99 204.745 87.99 204.755 88 204.755C88.09 204.905 88.22 205.035 88.33 205.175C88.45 205.335 88.55 205.495 88.69 205.635L88.7 205.645C88.82 205.765 88.98 205.855 89.12 205.965C89.28 206.085 89.42 206.225 89.59 206.325C89.6 206.325 89.6 206.325 89.61 206.335C89.62 206.335 89.62 206.345 89.63 206.345L139.87 234.775V285.065L43.67 229.705V60.135ZM244.75 229.705L148.58 285.075V234.775L219.8 194.115L244.75 179.875V229.705ZM297.2 139.625L253.49 164.795V114.995L278.85 100.395L297.21 89.825V139.625H297.2Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
7
resources/views/components/auth-session-status.blade.php
Normal file
7
resources/views/components/auth-session-status.blade.php
Normal file
@@ -0,0 +1,7 @@
|
||||
@props(['status'])
|
||||
|
||||
@if ($status)
|
||||
<div {{ $attributes->merge(['class' => 'font-medium text-sm text-green-600']) }}>
|
||||
{{ $status }}
|
||||
</div>
|
||||
@endif
|
||||
3
resources/views/components/danger-button.blade.php
Normal file
3
resources/views/components/danger-button.blade.php
Normal file
@@ -0,0 +1,3 @@
|
||||
<button {{ $attributes->merge(['type' => 'submit', 'class' => 'inline-flex items-center px-4 py-2 bg-red-600 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-red-500 active:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 transition ease-in-out duration-150']) }}>
|
||||
{{ $slot }}
|
||||
</button>
|
||||
1
resources/views/components/dropdown-link.blade.php
Normal file
1
resources/views/components/dropdown-link.blade.php
Normal file
@@ -0,0 +1 @@
|
||||
<a {{ $attributes->merge(['class' => 'block w-full px-4 py-2 text-start text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition duration-150 ease-in-out']) }}>{{ $slot }}</a>
|
||||
43
resources/views/components/dropdown.blade.php
Normal file
43
resources/views/components/dropdown.blade.php
Normal file
@@ -0,0 +1,43 @@
|
||||
@props(['align' => 'right', 'width' => '48', 'contentClasses' => 'py-1 bg-white'])
|
||||
|
||||
@php
|
||||
switch ($align) {
|
||||
case 'left':
|
||||
$alignmentClasses = 'ltr:origin-top-left rtl:origin-top-right start-0';
|
||||
break;
|
||||
case 'top':
|
||||
$alignmentClasses = 'origin-top';
|
||||
break;
|
||||
case 'right':
|
||||
default:
|
||||
$alignmentClasses = 'ltr:origin-top-right rtl:origin-top-left end-0';
|
||||
break;
|
||||
}
|
||||
|
||||
switch ($width) {
|
||||
case '48':
|
||||
$width = 'w-48';
|
||||
break;
|
||||
}
|
||||
@endphp
|
||||
|
||||
<div class="relative" x-data="{ open: false }" @click.outside="open = false" @close.stop="open = false">
|
||||
<div @click="open = ! open">
|
||||
{{ $trigger }}
|
||||
</div>
|
||||
|
||||
<div x-show="open"
|
||||
x-transition:enter="transition ease-out duration-200"
|
||||
x-transition:enter-start="opacity-0 scale-95"
|
||||
x-transition:enter-end="opacity-100 scale-100"
|
||||
x-transition:leave="transition ease-in duration-75"
|
||||
x-transition:leave-start="opacity-100 scale-100"
|
||||
x-transition:leave-end="opacity-0 scale-95"
|
||||
class="absolute z-50 mt-2 {{ $width }} rounded-md shadow-lg {{ $alignmentClasses }}"
|
||||
style="display: none;"
|
||||
@click="open = false">
|
||||
<div class="rounded-md ring-1 ring-black ring-opacity-5 {{ $contentClasses }}">
|
||||
{{ $content }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
9
resources/views/components/input-error.blade.php
Normal file
9
resources/views/components/input-error.blade.php
Normal file
@@ -0,0 +1,9 @@
|
||||
@props(['messages'])
|
||||
|
||||
@if ($messages)
|
||||
<ul {{ $attributes->merge(['class' => 'text-sm text-red-600 space-y-1']) }}>
|
||||
@foreach ((array) $messages as $message)
|
||||
<li>{{ $message }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
@endif
|
||||
5
resources/views/components/input-label.blade.php
Normal file
5
resources/views/components/input-label.blade.php
Normal file
@@ -0,0 +1,5 @@
|
||||
@props(['value'])
|
||||
|
||||
<label {{ $attributes->merge(['class' => 'block font-medium text-sm text-gray-700']) }}>
|
||||
{{ $value ?? $slot }}
|
||||
</label>
|
||||
25
resources/views/components/issue/priority-badge.blade.php
Normal file
25
resources/views/components/issue/priority-badge.blade.php
Normal file
@@ -0,0 +1,25 @@
|
||||
@props(['priority', 'label' => null])
|
||||
|
||||
@php
|
||||
$colors = [
|
||||
'low' => 'bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300',
|
||||
'medium' => 'bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300',
|
||||
'high' => 'bg-orange-100 text-orange-700 dark:bg-orange-900 dark:text-orange-300',
|
||||
'urgent' => 'bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300',
|
||||
];
|
||||
|
||||
$icons = [
|
||||
'low' => '↓',
|
||||
'medium' => '→',
|
||||
'high' => '↑',
|
||||
'urgent' => '⇈',
|
||||
];
|
||||
|
||||
$colorClass = $colors[$priority] ?? 'bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300';
|
||||
$icon = $icons[$priority] ?? '→';
|
||||
@endphp
|
||||
|
||||
<span {{ $attributes->merge(['class' => "inline-flex items-center gap-1 rounded-full px-2 py-1 text-xs font-medium {$colorClass}"]) }}>
|
||||
<span aria-hidden="true">{{ $icon }}</span>
|
||||
<span>{{ $label ?? __(ucfirst($priority)) }}</span>
|
||||
</span>
|
||||
27
resources/views/components/issue/status-badge.blade.php
Normal file
27
resources/views/components/issue/status-badge.blade.php
Normal file
@@ -0,0 +1,27 @@
|
||||
@props(['status', 'label' => null])
|
||||
|
||||
@php
|
||||
$colors = [
|
||||
'new' => 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200',
|
||||
'assigned' => 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200',
|
||||
'in_progress' => 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200',
|
||||
'review' => 'bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200',
|
||||
'closed' => 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200',
|
||||
];
|
||||
|
||||
$icons = [
|
||||
'new' => '●',
|
||||
'assigned' => '◐',
|
||||
'in_progress' => '◔',
|
||||
'review' => '◕',
|
||||
'closed' => '✓',
|
||||
];
|
||||
|
||||
$colorClass = $colors[$status] ?? 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-200';
|
||||
$icon = $icons[$status] ?? '●';
|
||||
@endphp
|
||||
|
||||
<span {{ $attributes->merge(['class' => "inline-flex items-center gap-1 rounded-full px-2 py-1 text-xs font-medium {$colorClass}"]) }}>
|
||||
<span aria-hidden="true">{{ $icon }}</span>
|
||||
<span>{{ $label ?? __(ucfirst(str_replace('_', ' ', $status))) }}</span>
|
||||
</span>
|
||||
47
resources/views/components/issue/timeline.blade.php
Normal file
47
resources/views/components/issue/timeline.blade.php
Normal file
@@ -0,0 +1,47 @@
|
||||
@props(['issue'])
|
||||
|
||||
<div class="flow-root">
|
||||
<div class="-mb-8">
|
||||
<!-- Timeline items -->
|
||||
@php
|
||||
$statuses = [
|
||||
'new' => ['label' => __('New'), 'reached' => true],
|
||||
'assigned' => ['label' => __('Assigned'), 'reached' => in_array($issue->status, ['assigned', 'in_progress', 'review', 'closed'])],
|
||||
'in_progress' => ['label' => __('In Progress'), 'reached' => in_array($issue->status, ['in_progress', 'review', 'closed'])],
|
||||
'review' => ['label' => __('Review'), 'reached' => in_array($issue->status, ['review', 'closed'])],
|
||||
'closed' => ['label' => __('Closed'), 'reached' => $issue->status === 'closed'],
|
||||
];
|
||||
@endphp
|
||||
|
||||
@foreach($statuses as $status => $data)
|
||||
<div class="relative pb-8 {{ $loop->last ? 'pb-0' : '' }}">
|
||||
@if(!$loop->last)
|
||||
<span class="absolute left-4 top-4 -ml-px h-full w-0.5 {{ $data['reached'] ? 'bg-indigo-600 dark:bg-indigo-400' : 'bg-gray-300 dark:bg-gray-600' }}" aria-hidden="true"></span>
|
||||
@endif
|
||||
|
||||
<div class="relative flex space-x-3">
|
||||
<div>
|
||||
@if($data['reached'])
|
||||
<span class="h-8 w-8 rounded-full bg-indigo-600 dark:bg-indigo-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>
|
||||
@else
|
||||
<span class="h-8 w-8 rounded-full bg-gray-300 dark:bg-gray-600 flex items-center justify-center ring-8 ring-white dark:ring-gray-800">
|
||||
<span class="h-2.5 w-2.5 rounded-full bg-gray-100 dark:bg-gray-500"></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 {{ $data['reached'] ? 'text-gray-900 dark:text-gray-100 font-medium' : 'text-gray-500 dark:text-gray-400' }}">
|
||||
{{ $data['label'] }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
78
resources/views/components/modal.blade.php
Normal file
78
resources/views/components/modal.blade.php
Normal file
@@ -0,0 +1,78 @@
|
||||
@props([
|
||||
'name',
|
||||
'show' => false,
|
||||
'maxWidth' => '2xl'
|
||||
])
|
||||
|
||||
@php
|
||||
$maxWidth = [
|
||||
'sm' => 'sm:max-w-sm',
|
||||
'md' => 'sm:max-w-md',
|
||||
'lg' => 'sm:max-w-lg',
|
||||
'xl' => 'sm:max-w-xl',
|
||||
'2xl' => 'sm:max-w-2xl',
|
||||
][$maxWidth];
|
||||
@endphp
|
||||
|
||||
<div
|
||||
x-data="{
|
||||
show: @js($show),
|
||||
focusables() {
|
||||
// All focusable element types...
|
||||
let selector = 'a, button, input:not([type=\'hidden\']), textarea, select, details, [tabindex]:not([tabindex=\'-1\'])'
|
||||
return [...$el.querySelectorAll(selector)]
|
||||
// All non-disabled elements...
|
||||
.filter(el => ! el.hasAttribute('disabled'))
|
||||
},
|
||||
firstFocusable() { return this.focusables()[0] },
|
||||
lastFocusable() { return this.focusables().slice(-1)[0] },
|
||||
nextFocusable() { return this.focusables()[this.nextFocusableIndex()] || this.firstFocusable() },
|
||||
prevFocusable() { return this.focusables()[this.prevFocusableIndex()] || this.lastFocusable() },
|
||||
nextFocusableIndex() { return (this.focusables().indexOf(document.activeElement) + 1) % (this.focusables().length + 1) },
|
||||
prevFocusableIndex() { return Math.max(0, this.focusables().indexOf(document.activeElement)) -1 },
|
||||
}"
|
||||
x-init="$watch('show', value => {
|
||||
if (value) {
|
||||
document.body.classList.add('overflow-y-hidden');
|
||||
{{ $attributes->has('focusable') ? 'setTimeout(() => firstFocusable().focus(), 100)' : '' }}
|
||||
} else {
|
||||
document.body.classList.remove('overflow-y-hidden');
|
||||
}
|
||||
})"
|
||||
x-on:open-modal.window="$event.detail == '{{ $name }}' ? show = true : null"
|
||||
x-on:close-modal.window="$event.detail == '{{ $name }}' ? show = false : null"
|
||||
x-on:close.stop="show = false"
|
||||
x-on:keydown.escape.window="show = false"
|
||||
x-on:keydown.tab.prevent="$event.shiftKey || nextFocusable().focus()"
|
||||
x-on:keydown.shift.tab.prevent="prevFocusable().focus()"
|
||||
x-show="show"
|
||||
class="fixed inset-0 overflow-y-auto px-4 py-6 sm:px-0 z-50"
|
||||
style="display: {{ $show ? 'block' : 'none' }};"
|
||||
>
|
||||
<div
|
||||
x-show="show"
|
||||
class="fixed inset-0 transform transition-all"
|
||||
x-on:click="show = false"
|
||||
x-transition:enter="ease-out duration-300"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
x-transition:leave="ease-in duration-200"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
>
|
||||
<div class="absolute inset-0 bg-gray-500 opacity-75"></div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
x-show="show"
|
||||
class="mb-6 bg-white rounded-lg overflow-hidden shadow-xl transform transition-all sm:w-full {{ $maxWidth }} sm:mx-auto"
|
||||
x-transition:enter="ease-out duration-300"
|
||||
x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
|
||||
x-transition:leave="ease-in duration-200"
|
||||
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
|
||||
x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
>
|
||||
{{ $slot }}
|
||||
</div>
|
||||
</div>
|
||||
11
resources/views/components/nav-link.blade.php
Normal file
11
resources/views/components/nav-link.blade.php
Normal file
@@ -0,0 +1,11 @@
|
||||
@props(['active'])
|
||||
|
||||
@php
|
||||
$classes = ($active ?? false)
|
||||
? 'inline-flex items-center px-1 pt-1 border-b-2 border-indigo-400 text-sm font-medium leading-5 text-gray-900 focus:outline-none focus:border-indigo-700 transition duration-150 ease-in-out'
|
||||
: 'inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out';
|
||||
@endphp
|
||||
|
||||
<a {{ $attributes->merge(['class' => $classes]) }}>
|
||||
{{ $slot }}
|
||||
</a>
|
||||
3
resources/views/components/primary-button.blade.php
Normal file
3
resources/views/components/primary-button.blade.php
Normal file
@@ -0,0 +1,3 @@
|
||||
<button {{ $attributes->merge(['type' => 'submit', 'class' => 'inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700 focus:bg-gray-700 active:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition ease-in-out duration-150']) }}>
|
||||
{{ $slot }}
|
||||
</button>
|
||||
11
resources/views/components/responsive-nav-link.blade.php
Normal file
11
resources/views/components/responsive-nav-link.blade.php
Normal file
@@ -0,0 +1,11 @@
|
||||
@props(['active'])
|
||||
|
||||
@php
|
||||
$classes = ($active ?? false)
|
||||
? 'block w-full ps-3 pe-4 py-2 border-l-4 border-indigo-400 text-start text-base font-medium text-indigo-700 bg-indigo-50 focus:outline-none focus:text-indigo-800 focus:bg-indigo-100 focus:border-indigo-700 transition duration-150 ease-in-out'
|
||||
: 'block w-full ps-3 pe-4 py-2 border-l-4 border-transparent text-start text-base font-medium text-gray-600 hover:text-gray-800 hover:bg-gray-50 hover:border-gray-300 focus:outline-none focus:text-gray-800 focus:bg-gray-50 focus:border-gray-300 transition duration-150 ease-in-out';
|
||||
@endphp
|
||||
|
||||
<a {{ $attributes->merge(['class' => $classes]) }}>
|
||||
{{ $slot }}
|
||||
</a>
|
||||
3
resources/views/components/secondary-button.blade.php
Normal file
3
resources/views/components/secondary-button.blade.php
Normal file
@@ -0,0 +1,3 @@
|
||||
<button {{ $attributes->merge(['type' => 'button', 'class' => 'inline-flex items-center px-4 py-2 bg-white border border-gray-300 rounded-md font-semibold text-xs text-gray-700 uppercase tracking-widest shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 disabled:opacity-25 transition ease-in-out duration-150']) }}>
|
||||
{{ $slot }}
|
||||
</button>
|
||||
3
resources/views/components/text-input.blade.php
Normal file
3
resources/views/components/text-input.blade.php
Normal file
@@ -0,0 +1,3 @@
|
||||
@props(['disabled' => false])
|
||||
|
||||
<input {{ $disabled ? 'disabled' : '' }} {!! $attributes->merge(['class' => 'border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm']) !!}>
|
||||
65
resources/views/dashboard.blade.php
Normal file
65
resources/views/dashboard.blade.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||
{{ __('Dashboard') }}
|
||||
</h2>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-12">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 space-y-6">
|
||||
<!-- Welcome Message -->
|
||||
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="p-6 text-gray-900">
|
||||
<h3 class="text-lg font-medium text-gray-900">歡迎回來,{{ Auth::user()->name }}!</h3>
|
||||
<p class="mt-1 text-sm text-gray-600">這是您的個人儀表板</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Documents Widget -->
|
||||
@if($recentDocuments->isNotEmpty())
|
||||
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="px-6 py-5 border-b border-gray-200">
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="text-lg font-medium text-gray-900">最新文件</h3>
|
||||
<a href="{{ route('documents.index') }}" class="text-sm text-indigo-600 hover:text-indigo-900">
|
||||
查看全部 →
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="divide-y divide-gray-200">
|
||||
@foreach($recentDocuments as $document)
|
||||
<div class="px-6 py-4 hover:bg-gray-50 transition">
|
||||
<div class="flex items-start">
|
||||
<div class="flex-shrink-0 text-3xl mr-4">
|
||||
{{ $document->currentVersion?->getFileIcon() ?? '📄' }}
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<h4 class="text-sm font-medium text-gray-900">
|
||||
<a href="{{ route('documents.public.show', $document->public_uuid) }}" class="hover:text-indigo-600">
|
||||
{{ $document->title }}
|
||||
</a>
|
||||
</h4>
|
||||
@if($document->description)
|
||||
<p class="mt-1 text-sm text-gray-500 line-clamp-1">{{ $document->description }}</p>
|
||||
@endif
|
||||
<div class="mt-2 flex items-center space-x-4 text-xs text-gray-500">
|
||||
<span>{{ $document->category->icon }} {{ $document->category->name }}</span>
|
||||
<span>📅 {{ $document->created_at->format('Y-m-d') }}</span>
|
||||
<span>📏 {{ $document->currentVersion?->getFileSizeHuman() }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-4 flex-shrink-0">
|
||||
<a href="{{ route('documents.public.download', $document->public_uuid) }}"
|
||||
class="inline-flex items-center px-3 py-1.5 border border-gray-300 rounded-md text-xs font-medium text-gray-700 bg-white hover:bg-gray-50">
|
||||
下載
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
135
resources/views/documents/index.blade.php
Normal file
135
resources/views/documents/index.blade.php
Normal file
@@ -0,0 +1,135 @@
|
||||
<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">
|
||||
<!-- Categories -->
|
||||
<div class="bg-white shadow sm:rounded-lg p-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-4">文件類別</h3>
|
||||
<div class="grid grid-cols-2 gap-4 md:grid-cols-3 lg:grid-cols-6">
|
||||
<a href="{{ route('documents.index') }}"
|
||||
class="flex flex-col items-center p-4 border rounded-lg hover:bg-gray-50 transition
|
||||
{{ !request('category') ? 'border-indigo-500 bg-indigo-50' : 'border-gray-200' }}">
|
||||
<span class="text-3xl mb-2">📚</span>
|
||||
<span class="text-sm font-medium text-gray-900">全部文件</span>
|
||||
<span class="text-xs text-gray-500 mt-1">{{ $documents->total() }}</span>
|
||||
</a>
|
||||
@foreach($categories as $category)
|
||||
<a href="{{ route('documents.index', ['category' => $category->id]) }}"
|
||||
class="flex flex-col items-center p-4 border rounded-lg hover:bg-gray-50 transition
|
||||
{{ request('category') == $category->id ? 'border-indigo-500 bg-indigo-50' : 'border-gray-200' }}">
|
||||
<span class="text-3xl mb-2">{{ $category->getIconDisplay() }}</span>
|
||||
<span class="text-sm font-medium text-gray-900 text-center">{{ $category->name }}</span>
|
||||
<span class="text-xs text-gray-500 mt-1">{{ $category->active_documents_count }}</span>
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Search -->
|
||||
<div class="bg-white shadow sm:rounded-lg p-6">
|
||||
<form method="GET" action="{{ route('documents.index') }}" class="flex gap-4">
|
||||
<input type="hidden" name="category" value="{{ request('category') }}">
|
||||
<div class="flex-1">
|
||||
<input type="text" name="search" value="{{ request('search') }}" placeholder="搜尋文件標題或說明..."
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
</div>
|
||||
<button type="submit" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-6 py-2 text-sm font-medium text-white hover:bg-indigo-700">
|
||||
搜尋
|
||||
</button>
|
||||
@if(request('search') || request('category'))
|
||||
<a href="{{ route('documents.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">
|
||||
清除
|
||||
</a>
|
||||
@endif
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Documents List -->
|
||||
<div class="space-y-4">
|
||||
@forelse($documents as $document)
|
||||
<div class="bg-white shadow sm:rounded-lg hover:shadow-md transition">
|
||||
<div class="px-6 py-5">
|
||||
<div class="flex items-start">
|
||||
<div class="flex-shrink-0 text-4xl mr-4">
|
||||
{{ $document->currentVersion?->getFileIcon() ?? '📄' }}
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex-1">
|
||||
<h3 class="text-lg font-medium text-gray-900">
|
||||
<a href="{{ route('documents.public.show', $document->public_uuid) }}" class="hover:text-indigo-600">
|
||||
{{ $document->title }}
|
||||
</a>
|
||||
</h3>
|
||||
@if($document->description)
|
||||
<p class="mt-1 text-sm text-gray-500">{{ $document->description }}</p>
|
||||
@endif
|
||||
<div class="mt-2 flex flex-wrap items-center gap-x-4 gap-y-2 text-sm text-gray-500">
|
||||
<span class="inline-flex items-center">
|
||||
{{ $document->category->icon }} {{ $document->category->name }}
|
||||
</span>
|
||||
@if($document->document_number)
|
||||
<span class="inline-flex items-center">
|
||||
📋 {{ $document->document_number }}
|
||||
</span>
|
||||
@endif
|
||||
<span class="inline-flex items-center">
|
||||
📅 {{ $document->created_at->format('Y-m-d') }}
|
||||
</span>
|
||||
<span class="inline-flex items-center">
|
||||
🔄 版本 {{ $document->currentVersion?->version_number }}
|
||||
</span>
|
||||
<span class="inline-flex items-center">
|
||||
📏 {{ $document->currentVersion?->getFileSizeHuman() }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-6 flex flex-col space-y-2">
|
||||
<a href="{{ route('documents.public.show', $document->public_uuid) }}"
|
||||
class="inline-flex items-center justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700">
|
||||
檢視
|
||||
</a>
|
||||
<a href="{{ route('documents.public.download', $document->public_uuid) }}"
|
||||
class="inline-flex 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">
|
||||
下載
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<div class="bg-white shadow sm:rounded-lg">
|
||||
<div 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>
|
||||
<h3 class="mt-2 text-sm font-medium text-gray-900">找不到文件</h3>
|
||||
<p class="mt-1 text-sm text-gray-500">目前沒有符合條件的文件</p>
|
||||
@if(request('search') || request('category'))
|
||||
<div class="mt-6">
|
||||
<a href="{{ route('documents.index') }}" class="text-indigo-600 hover:text-indigo-900">
|
||||
瀏覽所有文件
|
||||
</a>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
@if($documents->hasPages())
|
||||
<div class="bg-white shadow sm:rounded-lg p-6">
|
||||
{{ $documents->links() }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
242
resources/views/documents/show.blade.php
Normal file
242
resources/views/documents/show.blade.php
Normal file
@@ -0,0 +1,242 @@
|
||||
<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>
|
||||
<a href="{{ route('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-4xl sm:px-6 lg:px-8 space-y-6">
|
||||
<!-- Document Header -->
|
||||
<div class="bg-white shadow sm:rounded-lg">
|
||||
<div class="px-6 py-8">
|
||||
<div class="flex items-start">
|
||||
<div class="flex-shrink-0 text-6xl mr-6">
|
||||
{{ $document->currentVersion->getFileIcon() }}
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h1 class="text-2xl font-bold text-gray-900">{{ $document->title }}</h1>
|
||||
@if($document->document_number)
|
||||
<p class="mt-1 text-sm text-gray-500">文號:{{ $document->document_number }}</p>
|
||||
@endif
|
||||
@if($document->description)
|
||||
<p class="mt-3 text-gray-700">{{ $document->description }}</p>
|
||||
@endif
|
||||
<div class="mt-4 flex flex-wrap items-center gap-4">
|
||||
<span class="inline-flex items-center rounded-full px-3 py-1 text-sm font-medium
|
||||
@if($document->access_level === 'public') bg-green-100 text-green-800
|
||||
@elseif($document->access_level === 'members') bg-blue-100 text-blue-800
|
||||
@else bg-gray-100 text-gray-800
|
||||
@endif">
|
||||
{{ $document->getAccessLevelLabel() }}
|
||||
</span>
|
||||
<span class="text-sm text-gray-500">
|
||||
{{ $document->category->icon }} {{ $document->category->name }}
|
||||
</span>
|
||||
<span class="text-sm text-gray-500">
|
||||
📅 {{ $document->created_at->format('Y年m月d日') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Current Version Info -->
|
||||
<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">
|
||||
<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">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->currentVersion->original_filename }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">檔案大小</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900">{{ $document->currentVersion->getFileSizeHuman() }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">上傳時間</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900">{{ $document->currentVersion->uploaded_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 text-gray-900">{{ $document->currentVersion->uploadedBy->name }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">檔案格式</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900">{{ strtoupper($document->currentVersion->getFileExtension()) }}</dd>
|
||||
</div>
|
||||
@if($document->currentVersion->version_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">{{ $document->currentVersion->version_notes }}</dd>
|
||||
</div>
|
||||
@endif
|
||||
</dl>
|
||||
|
||||
<!-- Expiration Warning -->
|
||||
@if($document->expires_at)
|
||||
<div class="mt-4">
|
||||
@if($document->isExpired())
|
||||
<div class="rounded-md bg-red-50 p-3">
|
||||
<div class="flex">
|
||||
<div class="flex-shrink-0">
|
||||
<svg class="h-5 w-5 text-red-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<p class="text-sm font-medium text-red-800">
|
||||
此文件已於 {{ $document->expires_at->format('Y年m月d日') }} 過期
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@elseif($document->isExpiringSoon(30))
|
||||
<div class="rounded-md bg-yellow-50 p-3">
|
||||
<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">
|
||||
<p class="text-sm font-medium text-yellow-800">
|
||||
此文件將於 {{ $document->expires_at->format('Y年m月d日') }} 過期
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="mt-6 flex flex-wrap gap-4">
|
||||
<a href="{{ route('documents.public.download', $document->public_uuid) }}"
|
||||
class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-6 py-3 text-base 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="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
|
||||
</svg>
|
||||
下載文件
|
||||
</a>
|
||||
<button onclick="copyToClipboard('{{ $document->getPublicUrl() }}')"
|
||||
class="inline-flex items-center rounded-md border border-gray-300 bg-white px-6 py-3 text-base font-medium text-gray-700 hover:bg-gray-50">
|
||||
<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="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||
</svg>
|
||||
複製連結
|
||||
</button>
|
||||
@if(settings()->isFeatureEnabled('qr_codes') && (!auth()->user() || auth()->user()->can('use_qr_codes')))
|
||||
<a href="{{ route('documents.public.qrcode', $document->public_uuid) }}"
|
||||
class="inline-flex items-center rounded-md border border-gray-300 bg-white px-6 py-3 text-base font-medium text-gray-700 hover:bg-gray-50">
|
||||
<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 4v1m6 11h2m-6 0h-2v4m0-11v3m0 0h.01M12 12h4.01M16 20h4M4 12h4m12 0h.01M5 8h2a1 1 0 001-1V5a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1zm12 0h2a1 1 0 001-1V5a1 1 0 00-1-1h-2a1 1 0 00-1 1v2a1 1 0 001 1zM5 20h2a1 1 0 001-1v-2a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1z" />
|
||||
</svg>
|
||||
QR碼
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Version History -->
|
||||
@if($document->versions->count() > 1)
|
||||
<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">共 {{ $document->version_count }} 個版本</p>
|
||||
</div>
|
||||
<div class="px-6 py-5">
|
||||
<ul class="space-y-4">
|
||||
@foreach($document->versions as $version)
|
||||
<li class="flex items-start {{ $version->is_current ? 'bg-indigo-50 -mx-6 px-6 py-4' : '' }}">
|
||||
<div class="flex-shrink-0 text-2xl mr-4">
|
||||
{{ $version->getFileIcon() }}
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<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 items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-800">
|
||||
當前版本
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
<p class="mt-1 text-sm text-gray-500">
|
||||
{{ $version->original_filename }} · {{ $version->getFileSizeHuman() }}
|
||||
</p>
|
||||
<p class="mt-1 text-xs text-gray-500">
|
||||
{{ $version->uploaded_at->format('Y-m-d H:i') }} · {{ $version->uploadedBy->name }}
|
||||
</p>
|
||||
@if($version->version_notes)
|
||||
<p class="mt-2 text-sm text-gray-600">{{ $version->version_notes }}</p>
|
||||
@endif
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<a href="{{ route('documents.public.download-version', [$document->public_uuid, $version->id]) }}"
|
||||
class="text-sm text-indigo-600 hover:text-indigo-900">
|
||||
下載
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- File Integrity Info -->
|
||||
<div class="bg-gray-50 shadow sm:rounded-lg">
|
||||
<div class="px-6 py-4">
|
||||
<div class="flex items-center text-sm text-gray-600">
|
||||
<svg class="mr-2 h-5 w-5 text-green-500" 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="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
|
||||
</svg>
|
||||
<span>
|
||||
此文件經過完整性驗證,確保內容未被篡改。檔案雜湊:
|
||||
<code class="ml-1 px-2 py-1 bg-gray-100 rounded text-xs">{{ substr($document->currentVersion->file_hash, 0, 16) }}...</code>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Statistics -->
|
||||
<div class="bg-white shadow sm:rounded-lg">
|
||||
<div class="px-6 py-5">
|
||||
<div class="grid grid-cols-2 gap-4 text-center">
|
||||
<div>
|
||||
<div class="text-3xl font-bold text-indigo-600">{{ $document->view_count }}</div>
|
||||
<div class="mt-1 text-sm text-gray-500">檢視次數</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-3xl font-bold text-green-600">{{ $document->download_count }}</div>
|
||||
<div class="mt-1 text-sm text-gray-500">下載次數</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function copyToClipboard(text) {
|
||||
navigator.clipboard.writeText(text).then(function() {
|
||||
alert('連結已複製到剪貼簿');
|
||||
}, function(err) {
|
||||
alert('複製失敗:' + err);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</x-app-layout>
|
||||
@@ -0,0 +1,36 @@
|
||||
<x-mail::message>
|
||||
# Finance Document Awaiting Chair Final Approval
|
||||
|
||||
A finance document has been approved by both cashier and accountant, and is now awaiting your final approval as chair.
|
||||
|
||||
**Document Details:**
|
||||
- **Title:** {{ $document->title }}
|
||||
- **Amount:** KES {{ number_format($document->amount, 2) }}
|
||||
- **Type:** {{ ucfirst(str_replace('_', ' ', $document->type)) }}
|
||||
- **Submitted by:** {{ $document->submittedBy->name }}
|
||||
@if($document->member)
|
||||
- **Member:** {{ $document->member->full_name }}
|
||||
@endif
|
||||
|
||||
**Approval History:**
|
||||
- **Cashier:** {{ $document->cashierApprover->name }} ({{ $document->cashier_approved_at->format('M d, Y H:i') }})
|
||||
- **Accountant:** {{ $document->accountantApprover->name }} ({{ $document->accountant_approved_at->format('M d, Y H:i') }})
|
||||
|
||||
@if($document->description)
|
||||
**Description:**
|
||||
{{ $document->description }}
|
||||
@endif
|
||||
|
||||
@if($document->attachment_path)
|
||||
This document includes an attachment for review.
|
||||
@endif
|
||||
|
||||
<x-mail::button :url="route('finance.show', $document)">
|
||||
Review Document
|
||||
</x-mail::button>
|
||||
|
||||
Please provide your final approval or rejection for this document.
|
||||
|
||||
Thanks,<br>
|
||||
{{ config('app.name') }}
|
||||
</x-mail::message>
|
||||
35
resources/views/emails/finance/approved-by-cashier.blade.php
Normal file
35
resources/views/emails/finance/approved-by-cashier.blade.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<x-mail::message>
|
||||
# Finance Document Awaiting Accountant Review
|
||||
|
||||
A finance document has been approved by the cashier and is now awaiting your review as accountant.
|
||||
|
||||
**Document Details:**
|
||||
- **Title:** {{ $document->title }}
|
||||
- **Amount:** KES {{ number_format($document->amount, 2) }}
|
||||
- **Type:** {{ ucfirst(str_replace('_', ' ', $document->type)) }}
|
||||
- **Submitted by:** {{ $document->submittedBy->name }}
|
||||
@if($document->member)
|
||||
- **Member:** {{ $document->member->full_name }}
|
||||
@endif
|
||||
|
||||
**Approval History:**
|
||||
- **Cashier:** {{ $document->cashierApprover->name }} ({{ $document->cashier_approved_at->format('M d, Y H:i') }})
|
||||
|
||||
@if($document->description)
|
||||
**Description:**
|
||||
{{ $document->description }}
|
||||
@endif
|
||||
|
||||
@if($document->attachment_path)
|
||||
This document includes an attachment for review.
|
||||
@endif
|
||||
|
||||
<x-mail::button :url="route('finance.show', $document)">
|
||||
Review Document
|
||||
</x-mail::button>
|
||||
|
||||
Please review and approve or reject this document at your earliest convenience.
|
||||
|
||||
Thanks,<br>
|
||||
{{ config('app.name') }}
|
||||
</x-mail::message>
|
||||
32
resources/views/emails/finance/fully-approved.blade.php
Normal file
32
resources/views/emails/finance/fully-approved.blade.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<x-mail::message>
|
||||
# Finance Document Fully Approved
|
||||
|
||||
Great news! Your finance document has been fully approved by all required approvers.
|
||||
|
||||
**Document Details:**
|
||||
- **Title:** {{ $document->title }}
|
||||
- **Amount:** KES {{ number_format($document->amount, 2) }}
|
||||
- **Type:** {{ ucfirst(str_replace('_', ' ', $document->type)) }}
|
||||
@if($document->member)
|
||||
- **Member:** {{ $document->member->full_name }}
|
||||
@endif
|
||||
|
||||
**Approval History:**
|
||||
- **Cashier:** {{ $document->cashierApprover->name }} ({{ $document->cashier_approved_at->format('M d, Y H:i') }})
|
||||
- **Accountant:** {{ $document->accountantApprover->name }} ({{ $document->accountant_approved_at->format('M d, Y H:i') }})
|
||||
- **Chair:** {{ $document->chairApprover->name }} ({{ $document->chair_approved_at->format('M d, Y H:i') }})
|
||||
|
||||
@if($document->description)
|
||||
**Description:**
|
||||
{{ $document->description }}
|
||||
@endif
|
||||
|
||||
<x-mail::button :url="route('finance.show', $document)">
|
||||
View Document
|
||||
</x-mail::button>
|
||||
|
||||
The document is now fully approved and can proceed to the next stage.
|
||||
|
||||
Thanks,<br>
|
||||
{{ config('app.name') }}
|
||||
</x-mail::message>
|
||||
31
resources/views/emails/finance/rejected.blade.php
Normal file
31
resources/views/emails/finance/rejected.blade.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<x-mail::message>
|
||||
# Finance Document Rejected
|
||||
|
||||
Your finance document has been rejected.
|
||||
|
||||
**Document Details:**
|
||||
- **Title:** {{ $document->title }}
|
||||
- **Amount:** KES {{ number_format($document->amount, 2) }}
|
||||
- **Type:** {{ ucfirst(str_replace('_', ' ', $document->type)) }}
|
||||
@if($document->member)
|
||||
- **Member:** {{ $document->member->full_name }}
|
||||
@endif
|
||||
|
||||
**Rejection Details:**
|
||||
- **Rejected by:** {{ $document->rejectedBy->name }}
|
||||
- **Rejected at:** {{ $document->rejected_at->format('M d, Y H:i') }}
|
||||
|
||||
@if($document->rejection_reason)
|
||||
**Reason:**
|
||||
{{ $document->rejection_reason }}
|
||||
@endif
|
||||
|
||||
<x-mail::button :url="route('finance.show', $document)">
|
||||
View Document
|
||||
</x-mail::button>
|
||||
|
||||
Please review the rejection reason and contact the approver if you have any questions.
|
||||
|
||||
Thanks,<br>
|
||||
{{ config('app.name') }}
|
||||
</x-mail::message>
|
||||
32
resources/views/emails/finance/submitted.blade.php
Normal file
32
resources/views/emails/finance/submitted.blade.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<x-mail::message>
|
||||
# New Finance Document Awaiting Review
|
||||
|
||||
A new finance document has been submitted and is awaiting cashier review.
|
||||
|
||||
**Document Details:**
|
||||
- **Title:** {{ $document->title }}
|
||||
- **Amount:** KES {{ number_format($document->amount, 2) }}
|
||||
- **Type:** {{ ucfirst(str_replace('_', ' ', $document->type)) }}
|
||||
- **Submitted by:** {{ $document->submittedBy->name }}
|
||||
@if($document->member)
|
||||
- **Member:** {{ $document->member->full_name }}
|
||||
@endif
|
||||
|
||||
@if($document->description)
|
||||
**Description:**
|
||||
{{ $document->description }}
|
||||
@endif
|
||||
|
||||
@if($document->attachment_path)
|
||||
This document includes an attachment for review.
|
||||
@endif
|
||||
|
||||
<x-mail::button :url="route('finance.show', $document)">
|
||||
Review Document
|
||||
</x-mail::button>
|
||||
|
||||
Please review and approve or reject this document at your earliest convenience.
|
||||
|
||||
Thanks,<br>
|
||||
{{ config('app.name') }}
|
||||
</x-mail::message>
|
||||
33
resources/views/emails/issues/assigned.blade.php
Normal file
33
resources/views/emails/issues/assigned.blade.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<x-mail::message>
|
||||
# Issue Assigned to You
|
||||
|
||||
You have been assigned to work on an issue.
|
||||
|
||||
**Issue Details:**
|
||||
- **Issue Number:** {{ $issue->issue_number }}
|
||||
- **Title:** {{ $issue->title }}
|
||||
- **Priority:** {{ ucfirst($issue->priority) }}
|
||||
- **Type:** {{ ucfirst(str_replace('_', ' ', $issue->issue_type)) }}
|
||||
- **Status:** {{ ucfirst(str_replace('_', ' ', $issue->status)) }}
|
||||
@if($issue->due_date)
|
||||
- **Due Date:** {{ $issue->due_date->format('Y-m-d') }} ({{ $issue->due_date->diffForHumans() }})
|
||||
@endif
|
||||
@if($issue->estimated_hours)
|
||||
- **Estimated Hours:** {{ $issue->estimated_hours }}
|
||||
@endif
|
||||
- **Assigned by:** {{ $issue->createdBy->name }}
|
||||
|
||||
@if($issue->description)
|
||||
**Description:**
|
||||
{{ $issue->description }}
|
||||
@endif
|
||||
|
||||
<x-mail::button :url="route('admin.issues.show', $issue)">
|
||||
View Issue
|
||||
</x-mail::button>
|
||||
|
||||
Please review this issue and start working on it at your earliest convenience.
|
||||
|
||||
Thanks,<br>
|
||||
{{ config('app.name') }}
|
||||
</x-mail::message>
|
||||
45
resources/views/emails/issues/closed.blade.php
Normal file
45
resources/views/emails/issues/closed.blade.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<x-mail::message>
|
||||
# Issue Closed - Completed
|
||||
|
||||
An issue you were watching has been closed and marked as complete.
|
||||
|
||||
**Issue Details:**
|
||||
- **Issue Number:** {{ $issue->issue_number }}
|
||||
- **Title:** {{ $issue->title }}
|
||||
- **Type:** {{ ucfirst(str_replace('_', ' ', $issue->issue_type)) }}
|
||||
- **Priority:** {{ ucfirst($issue->priority) }}
|
||||
- **Status:** Closed
|
||||
@if($issue->assignedTo)
|
||||
- **Completed By:** {{ $issue->assignedTo->name }}
|
||||
@endif
|
||||
- **Closed At:** {{ $issue->updated_at->format('Y-m-d H:i') }}
|
||||
|
||||
@if($issue->due_date)
|
||||
**Due Date:** {{ $issue->due_date->format('Y-m-d') }}
|
||||
@if($issue->due_date->isPast())
|
||||
*This issue was completed {{ $issue->updated_at->diffInDays($issue->due_date) }} {{ $issue->updated_at->diffInDays($issue->due_date) === 1 ? 'day' : 'days' }} after the due date.*
|
||||
@else
|
||||
*This issue was completed on time.*
|
||||
@endif
|
||||
@endif
|
||||
|
||||
@if($issue->estimated_hours)
|
||||
**Time Tracking:**
|
||||
- Estimated: {{ $issue->estimated_hours }} hours
|
||||
- Actual: {{ $issue->actual_hours }} hours
|
||||
@if($issue->actual_hours > $issue->estimated_hours)
|
||||
- Variance: +{{ round($issue->actual_hours - $issue->estimated_hours, 2) }} hours over estimate
|
||||
@elseif($issue->actual_hours < $issue->estimated_hours)
|
||||
- Variance: {{ round($issue->estimated_hours - $issue->actual_hours, 2) }} hours under estimate
|
||||
@endif
|
||||
@endif
|
||||
|
||||
<x-mail::button :url="route('admin.issues.show', $issue)">
|
||||
View Closed Issue
|
||||
</x-mail::button>
|
||||
|
||||
Thank you for your work on this issue.
|
||||
|
||||
Thanks,<br>
|
||||
{{ config('app.name') }}
|
||||
</x-mail::message>
|
||||
30
resources/views/emails/issues/commented.blade.php
Normal file
30
resources/views/emails/issues/commented.blade.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<x-mail::message>
|
||||
# New Comment on Issue
|
||||
|
||||
A new comment has been added to an issue you're watching.
|
||||
|
||||
**Issue Details:**
|
||||
- **Issue Number:** {{ $issue->issue_number }}
|
||||
- **Title:** {{ $issue->title }}
|
||||
- **Status:** {{ ucfirst(str_replace('_', ' ', $issue->status)) }}
|
||||
- **Priority:** {{ ucfirst($issue->priority) }}
|
||||
|
||||
**Comment by:** {{ $comment->user->name }}
|
||||
**Posted at:** {{ $comment->created_at->format('Y-m-d H:i') }}
|
||||
|
||||
@if(!$comment->is_internal)
|
||||
**Comment:**
|
||||
{{ $comment->comment_text }}
|
||||
@else
|
||||
*An internal comment has been added to this issue.*
|
||||
@endif
|
||||
|
||||
<x-mail::button :url="route('admin.issues.show', $issue)">
|
||||
View Issue & Comment
|
||||
</x-mail::button>
|
||||
|
||||
Stay engaged with the discussion on this issue.
|
||||
|
||||
Thanks,<br>
|
||||
{{ config('app.name') }}
|
||||
</x-mail::message>
|
||||
35
resources/views/emails/issues/due-soon.blade.php
Normal file
35
resources/views/emails/issues/due-soon.blade.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<x-mail::message>
|
||||
# Issue Due Soon - Reminder
|
||||
|
||||
An issue assigned to you is due soon and requires your attention.
|
||||
|
||||
**Issue Details:**
|
||||
- **Issue Number:** {{ $issue->issue_number }}
|
||||
- **Title:** {{ $issue->title }}
|
||||
- **Priority:** {{ ucfirst($issue->priority) }}
|
||||
- **Status:** {{ ucfirst(str_replace('_', ' ', $issue->status)) }}
|
||||
- **Due Date:** {{ $issue->due_date->format('Y-m-d') }}
|
||||
- **Days Remaining:** {{ $daysRemaining }} {{ $daysRemaining === 1 ? 'day' : 'days' }}
|
||||
|
||||
**Progress:** {{ $issue->progress_percentage }}%
|
||||
|
||||
@if($issue->estimated_hours)
|
||||
**Time Tracking:**
|
||||
- Estimated: {{ $issue->estimated_hours }} hours
|
||||
- Actual: {{ $issue->actual_hours }} hours
|
||||
@endif
|
||||
|
||||
@if($issue->description)
|
||||
**Description:**
|
||||
{{ Str::limit($issue->description, 200) }}
|
||||
@endif
|
||||
|
||||
<x-mail::button :url="route('admin.issues.show', $issue)">
|
||||
View Issue
|
||||
</x-mail::button>
|
||||
|
||||
Please ensure this issue is completed before the due date.
|
||||
|
||||
Thanks,<br>
|
||||
{{ config('app.name') }}
|
||||
</x-mail::message>
|
||||
35
resources/views/emails/issues/overdue.blade.php
Normal file
35
resources/views/emails/issues/overdue.blade.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<x-mail::message>
|
||||
# Issue Overdue - Urgent Attention Required
|
||||
|
||||
An issue assigned to you is now overdue and requires immediate attention.
|
||||
|
||||
**Issue Details:**
|
||||
- **Issue Number:** {{ $issue->issue_number }}
|
||||
- **Title:** {{ $issue->title }}
|
||||
- **Priority:** {{ ucfirst($issue->priority) }}
|
||||
- **Status:** {{ ucfirst(str_replace('_', ' ', $issue->status)) }}
|
||||
- **Due Date:** {{ $issue->due_date->format('Y-m-d') }}
|
||||
- **Days Overdue:** {{ $daysOverdue }} {{ $daysOverdue === 1 ? 'day' : 'days' }}
|
||||
|
||||
**Progress:** {{ $issue->progress_percentage }}%
|
||||
|
||||
@if($issue->estimated_hours)
|
||||
**Time Tracking:**
|
||||
- Estimated: {{ $issue->estimated_hours }} hours
|
||||
- Actual: {{ $issue->actual_hours }} hours
|
||||
@endif
|
||||
|
||||
@if($issue->description)
|
||||
**Description:**
|
||||
{{ Str::limit($issue->description, 200) }}
|
||||
@endif
|
||||
|
||||
<x-mail::button :url="route('admin.issues.show', $issue)">
|
||||
View Issue Immediately
|
||||
</x-mail::button>
|
||||
|
||||
This issue is overdue. Please prioritize completing it or update the due date if needed.
|
||||
|
||||
Thanks,<br>
|
||||
{{ config('app.name') }}
|
||||
</x-mail::message>
|
||||
29
resources/views/emails/issues/status-changed.blade.php
Normal file
29
resources/views/emails/issues/status-changed.blade.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<x-mail::message>
|
||||
# Issue Status Changed
|
||||
|
||||
The status of an issue you're watching has been updated.
|
||||
|
||||
**Issue Details:**
|
||||
- **Issue Number:** {{ $issue->issue_number }}
|
||||
- **Title:** {{ $issue->title }}
|
||||
- **Previous Status:** {{ ucfirst(str_replace('_', ' ', $oldStatus)) }}
|
||||
- **New Status:** {{ ucfirst(str_replace('_', ' ', $newStatus)) }}
|
||||
- **Priority:** {{ ucfirst($issue->priority) }}
|
||||
@if($issue->assignedTo)
|
||||
- **Assigned To:** {{ $issue->assignedTo->name }}
|
||||
@endif
|
||||
@if($issue->due_date)
|
||||
- **Due Date:** {{ $issue->due_date->format('Y-m-d') }}
|
||||
@endif
|
||||
|
||||
**Current Progress:** {{ $issue->progress_percentage }}%
|
||||
|
||||
<x-mail::button :url="route('admin.issues.show', $issue)">
|
||||
View Issue
|
||||
</x-mail::button>
|
||||
|
||||
Stay updated on the progress of this issue.
|
||||
|
||||
Thanks,<br>
|
||||
{{ config('app.name') }}
|
||||
</x-mail::message>
|
||||
21
resources/views/emails/members/activated.blade.php
Normal file
21
resources/views/emails/members/activated.blade.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<x-mail::message>
|
||||
# Membership Activated!
|
||||
|
||||
Congratulations, {{ $member->full_name }}!
|
||||
|
||||
Your membership has been activated and you now have full access to all member benefits.
|
||||
|
||||
**Membership Details:**
|
||||
- **Type:** {{ $member->membership_type_label }}
|
||||
- **Start Date:** {{ $member->membership_started_at->format('Y-m-d') }}
|
||||
- **Expiry Date:** {{ $member->membership_expires_at->format('Y-m-d') }}
|
||||
|
||||
You can now access exclusive resources, participate in events, and enjoy all member privileges.
|
||||
|
||||
<x-mail::button :url="route('member.dashboard')">
|
||||
View Your Membership
|
||||
</x-mail::button>
|
||||
|
||||
Thanks,<br>
|
||||
{{ config('app.name') }}
|
||||
</x-mail::message>
|
||||
17
resources/views/emails/members/activation-text.blade.php
Normal file
17
resources/views/emails/members/activation-text.blade.php
Normal file
@@ -0,0 +1,17 @@
|
||||
@php
|
||||
/** @var \App\Models\User $user */
|
||||
@endphp
|
||||
|
||||
{{ __('Hello') }} {{ $user->name }},
|
||||
|
||||
{{ __('You have been registered as a member on :app.', ['app' => config('app.name')]) }}
|
||||
|
||||
{{ __('To activate your online account and set your password, please open the link below:') }}
|
||||
|
||||
{{ $resetUrl }}
|
||||
|
||||
{{ __('If you did not expect this email, you can ignore it.') }}
|
||||
|
||||
{{ __('Thank you,') }}
|
||||
{{ config('app.name') }}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user