Add membership fee system with disability discount and fix document permissions
Features: - Implement two fee types: entrance fee and annual fee (both NT$1,000) - Add 50% discount for disability certificate holders - Add disability certificate upload in member profile - Integrate disability verification into cashier approval workflow - Add membership fee settings in system admin Document permissions: - Fix hard-coded role logic in Document model - Use permission-based authorization instead of role checks Additional features: - Add announcements, general ledger, and trial balance modules - Add income management and accounting entries - Add comprehensive test suite with factories - Update UI translations to Traditional Chinese 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
76
app/Console/Commands/AnalyzeAccountingData.php
Normal file
76
app/Console/Commands/AnalyzeAccountingData.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Maatwebsite\Excel\Facades\Excel;
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
|
||||
class AnalyzeAccountingData extends Command
|
||||
{
|
||||
protected $signature = 'analyze:accounting';
|
||||
protected $description = 'Analyze accounting data files';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$this->info('=== 協會帳務資料分析 ===');
|
||||
$this->newLine();
|
||||
|
||||
$files = [
|
||||
'2024尤塞氏症及視聽雙弱協會帳務.xlsx' => '協會行政資料/協會帳務/2024尤塞氏症及視聽雙弱協會帳務.xlsx',
|
||||
'2025 收入支出總表 (含會計科目編號).xlsx' => '協會行政資料/協會帳務/2025 收入支出總表 (含會計科目編號).xlsx',
|
||||
'2025 協會預算試編.xlsx' => '協會行政資料/協會帳務/2025 協會預算試編.xlsx',
|
||||
];
|
||||
|
||||
foreach ($files as $name => $path) {
|
||||
if (!file_exists($path)) {
|
||||
$this->error("檔案不存在: {$name}");
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->info("📊 分析檔案: {$name}");
|
||||
$this->info("📅 最後更新: " . date('Y-m-d H:i:s', filemtime($path)));
|
||||
$this->info("📦 檔案大小: " . number_format(filesize($path)) . " bytes");
|
||||
$this->newLine();
|
||||
|
||||
try {
|
||||
$spreadsheet = IOFactory::load($path);
|
||||
|
||||
$this->info("工作表列表:");
|
||||
foreach ($spreadsheet->getAllSheets() as $index => $sheet) {
|
||||
$sheetName = $sheet->getTitle();
|
||||
$highestRow = $sheet->getHighestRow();
|
||||
$highestColumn = $sheet->getHighestColumn();
|
||||
|
||||
$this->line(" - {$sheetName} (範圍: A1:{$highestColumn}{$highestRow}, 共 {$highestRow} 列)");
|
||||
|
||||
// 讀取前5行作為預覽
|
||||
if ($highestRow > 0) {
|
||||
$this->info(" 前5行預覽:");
|
||||
for ($row = 1; $row <= min(5, $highestRow); $row++) {
|
||||
$rowData = [];
|
||||
for ($col = 'A'; $col <= min('J', $highestColumn); $col++) {
|
||||
$cellValue = $sheet->getCell($col . $row)->getValue();
|
||||
if (!empty($cellValue)) {
|
||||
$rowData[] = substr($cellValue, 0, 30);
|
||||
}
|
||||
}
|
||||
if (!empty($rowData)) {
|
||||
$this->line(" Row {$row}: " . implode(' | ', $rowData));
|
||||
}
|
||||
}
|
||||
$this->newLine();
|
||||
}
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->error("讀取失敗: " . $e->getMessage());
|
||||
}
|
||||
|
||||
$this->info(str_repeat('─', 80));
|
||||
$this->newLine();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
436
app/Console/Commands/ImportAccountingData.php
Normal file
436
app/Console/Commands/ImportAccountingData.php
Normal file
@@ -0,0 +1,436 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\AccountingEntry;
|
||||
use App\Models\ChartOfAccount;
|
||||
use App\Models\FinanceDocument;
|
||||
use Illuminate\Console\Command;
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date as ExcelDate;
|
||||
|
||||
class ImportAccountingData extends Command
|
||||
{
|
||||
protected $signature = 'import:accounting-data
|
||||
{file? : Path to Excel file}
|
||||
{--dry-run : Preview without importing}';
|
||||
|
||||
protected $description = 'Import accounting data from Excel files';
|
||||
|
||||
protected $mapping;
|
||||
protected $expenseKeywords;
|
||||
protected $accountCache = [];
|
||||
protected $stats = [
|
||||
'income_count' => 0,
|
||||
'expense_count' => 0,
|
||||
'skipped_count' => 0,
|
||||
'error_count' => 0,
|
||||
];
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$this->info('=== 會計資料匯入工具 ===');
|
||||
$this->newLine();
|
||||
|
||||
// Load configuration
|
||||
$this->mapping = config('accounting_mapping.excel_to_system', []);
|
||||
$this->expenseKeywords = config('accounting_mapping.expense_keywords', []);
|
||||
|
||||
// Get file path
|
||||
$filePath = $this->argument('file') ?? $this->askForFile();
|
||||
|
||||
if (!file_exists($filePath)) {
|
||||
$this->error("檔案不存在: {$filePath}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
$this->info("📂 檔案: {$filePath}");
|
||||
$this->info("📅 檔案日期: " . date('Y-m-d H:i:s', filemtime($filePath)));
|
||||
$this->newLine();
|
||||
|
||||
if ($this->option('dry-run')) {
|
||||
$this->warn('⚠️ DRY RUN MODE - 不會實際寫入資料庫');
|
||||
$this->newLine();
|
||||
}
|
||||
|
||||
try {
|
||||
$spreadsheet = IOFactory::load($filePath);
|
||||
|
||||
// Import income
|
||||
$this->info('📊 匯入收入資料...');
|
||||
$this->importIncome($spreadsheet);
|
||||
$this->newLine();
|
||||
|
||||
// Import expenses
|
||||
$this->info('📊 匯入支出資料...');
|
||||
$this->importExpenses($spreadsheet);
|
||||
$this->newLine();
|
||||
|
||||
// Show summary
|
||||
$this->showSummary();
|
||||
|
||||
// Verify balance
|
||||
if (!$this->option('dry-run')) {
|
||||
$this->verifyBalance();
|
||||
}
|
||||
|
||||
return 0;
|
||||
} catch (\Exception $e) {
|
||||
$this->error('匯入失敗: ' . $e->getMessage());
|
||||
$this->error($e->getTraceAsString());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
protected function askForFile(): string
|
||||
{
|
||||
$defaultPath = '協會行政資料/協會帳務/2025 收入支出總表 (含會計科目編號).xlsx';
|
||||
|
||||
$this->info('可用檔案:');
|
||||
$this->line('1. ' . $defaultPath);
|
||||
|
||||
return $this->ask('請輸入檔案路徑', $defaultPath);
|
||||
}
|
||||
|
||||
protected function importIncome($spreadsheet)
|
||||
{
|
||||
// Find the "收入" sheet
|
||||
$sheet = null;
|
||||
foreach ($spreadsheet->getAllSheets() as $s) {
|
||||
if (in_array($s->getTitle(), ['收入', 'Income', '收入明細'])) {
|
||||
$sheet = $s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$sheet) {
|
||||
$this->warn('找不到「收入」工作表,跳過');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->info("工作表: {$sheet->getTitle()}");
|
||||
|
||||
// Read header row to find columns
|
||||
$headerRow = 1;
|
||||
$headers = [];
|
||||
$maxCol = $sheet->getHighestColumn();
|
||||
for ($col = 'A'; $col <= $maxCol; $col++) {
|
||||
$value = $sheet->getCell($col . $headerRow)->getValue();
|
||||
if ($value) {
|
||||
$headers[$col] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$this->line('欄位: ' . implode(', ', $headers));
|
||||
|
||||
// Detect columns
|
||||
$dateCol = $this->findColumn($headers, ['日期', 'Date']);
|
||||
$accountCodeCol = $this->findColumn($headers, ['科目編號', '科目代碼', 'Code']);
|
||||
$accountNameCol = $this->findColumn($headers, ['科目名稱', 'Account']);
|
||||
$amountCol = $this->findColumn($headers, ['收入金額', '金額', 'Amount']);
|
||||
$descCol = $this->findColumn($headers, ['收入來源備註', '備註', '說明', 'Description']);
|
||||
|
||||
if (!$dateCol || !$amountCol) {
|
||||
$this->error('缺少必要欄位(日期、金額)');
|
||||
return;
|
||||
}
|
||||
|
||||
// Import rows
|
||||
$highestRow = $sheet->getHighestRow();
|
||||
$bar = $this->output->createProgressBar($highestRow - 1);
|
||||
|
||||
for ($row = $headerRow + 1; $row <= $highestRow; $row++) {
|
||||
$amount = $sheet->getCell($amountCol . $row)->getValue();
|
||||
|
||||
if (empty($amount) || $amount == 0) {
|
||||
$this->stats['skipped_count']++;
|
||||
$bar->advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$date = $this->parseDate($sheet->getCell($dateCol . $row)->getValue());
|
||||
$excelAccountCode = $accountCodeCol ? $sheet->getCell($accountCodeCol . $row)->getValue() : null;
|
||||
$description = $descCol ? $sheet->getCell($descCol . $row)->getValue() : '';
|
||||
|
||||
// Map to system account
|
||||
$systemAccountCode = $this->mapAccountCode($excelAccountCode);
|
||||
$account = $this->getAccount($systemAccountCode);
|
||||
|
||||
if (!$account) {
|
||||
$this->stats['error_count']++;
|
||||
$this->warn("\n找不到科目: {$systemAccountCode} (Excel: {$excelAccountCode})");
|
||||
$bar->advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$this->option('dry-run')) {
|
||||
$this->createIncomeEntry($date, $account, $amount, $description);
|
||||
}
|
||||
|
||||
$this->stats['income_count']++;
|
||||
} catch (\Exception $e) {
|
||||
$this->stats['error_count']++;
|
||||
$this->warn("\nRow {$row} 錯誤: " . $e->getMessage());
|
||||
}
|
||||
|
||||
$bar->advance();
|
||||
}
|
||||
|
||||
$bar->finish();
|
||||
$this->newLine();
|
||||
}
|
||||
|
||||
protected function importExpenses($spreadsheet)
|
||||
{
|
||||
// Find the "支出" sheet
|
||||
$sheet = null;
|
||||
foreach ($spreadsheet->getAllSheets() as $s) {
|
||||
if (in_array($s->getTitle(), ['支出', 'Expense', '支出明細'])) {
|
||||
$sheet = $s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$sheet) {
|
||||
$this->warn('找不到「支出」工作表,跳過');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->info("工作表: {$sheet->getTitle()}");
|
||||
|
||||
// Read header row
|
||||
$headerRow = 1;
|
||||
$headers = [];
|
||||
$maxCol = $sheet->getHighestColumn();
|
||||
for ($col = 'A'; $col <= $maxCol; $col++) {
|
||||
$value = $sheet->getCell($col . $headerRow)->getValue();
|
||||
if ($value) {
|
||||
$headers[$col] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$this->line('欄位: ' . implode(', ', $headers));
|
||||
|
||||
// Detect columns
|
||||
$dateCol = $this->findColumn($headers, ['日期', 'Date']);
|
||||
$accountCodeCol = $this->findColumn($headers, ['科目編號', '科目代碼', 'Code']);
|
||||
$amountCol = $this->findColumn($headers, ['支出金額', '金額', 'Amount']);
|
||||
$descCol = $this->findColumn($headers, ['支出用途備註', '用途', '備註', 'Description']);
|
||||
|
||||
if (!$dateCol || !$amountCol) {
|
||||
$this->error('缺少必要欄位(日期、金額)');
|
||||
return;
|
||||
}
|
||||
|
||||
// Import rows
|
||||
$highestRow = $sheet->getHighestRow();
|
||||
$bar = $this->output->createProgressBar($highestRow - 1);
|
||||
|
||||
for ($row = $headerRow + 1; $row <= $highestRow; $row++) {
|
||||
$amount = $sheet->getCell($amountCol . $row)->getValue();
|
||||
|
||||
if (empty($amount) || $amount == 0) {
|
||||
$this->stats['skipped_count']++;
|
||||
$bar->advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$date = $this->parseDate($sheet->getCell($dateCol . $row)->getValue());
|
||||
$excelAccountCode = $accountCodeCol ? $sheet->getCell($accountCodeCol . $row)->getValue() : '5100';
|
||||
$description = $descCol ? $sheet->getCell($descCol . $row)->getValue() : '';
|
||||
|
||||
// For 5100, classify by keywords
|
||||
if ($excelAccountCode == '5100') {
|
||||
$systemAccountCode = $this->classifyExpense($description);
|
||||
} else {
|
||||
$systemAccountCode = $this->mapAccountCode($excelAccountCode);
|
||||
}
|
||||
|
||||
$account = $this->getAccount($systemAccountCode);
|
||||
|
||||
if (!$account) {
|
||||
$this->stats['error_count']++;
|
||||
$this->warn("\n找不到科目: {$systemAccountCode}");
|
||||
$bar->advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$this->option('dry-run')) {
|
||||
$this->createExpenseEntry($date, $account, $amount, $description);
|
||||
}
|
||||
|
||||
$this->stats['expense_count']++;
|
||||
} catch (\Exception $e) {
|
||||
$this->stats['error_count']++;
|
||||
$this->warn("\nRow {$row} 錯誤: " . $e->getMessage());
|
||||
}
|
||||
|
||||
$bar->advance();
|
||||
}
|
||||
|
||||
$bar->finish();
|
||||
$this->newLine();
|
||||
}
|
||||
|
||||
protected function createIncomeEntry($date, $account, $amount, $description)
|
||||
{
|
||||
// Create finance document
|
||||
$document = FinanceDocument::create([
|
||||
'title' => '收入 - ' . $account->account_name_zh,
|
||||
'amount' => $amount,
|
||||
'description' => $description,
|
||||
'chart_of_account_id' => $account->id,
|
||||
'submitted_at' => $date,
|
||||
'status' => FinanceDocument::STATUS_APPROVED_CHAIR,
|
||||
]);
|
||||
|
||||
// Create accounting entries (double-entry)
|
||||
// Debit: Cash
|
||||
AccountingEntry::create([
|
||||
'finance_document_id' => $document->id,
|
||||
'chart_of_account_id' => $this->getAccount('1101')->id,
|
||||
'entry_type' => AccountingEntry::ENTRY_TYPE_DEBIT,
|
||||
'amount' => $amount,
|
||||
'entry_date' => $date,
|
||||
'description' => '收入 - ' . $description,
|
||||
]);
|
||||
|
||||
// Credit: Income account
|
||||
AccountingEntry::create([
|
||||
'finance_document_id' => $document->id,
|
||||
'chart_of_account_id' => $account->id,
|
||||
'entry_type' => AccountingEntry::ENTRY_TYPE_CREDIT,
|
||||
'amount' => $amount,
|
||||
'entry_date' => $date,
|
||||
'description' => $description,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function createExpenseEntry($date, $account, $amount, $description)
|
||||
{
|
||||
// Create finance document
|
||||
$document = FinanceDocument::create([
|
||||
'title' => '支出 - ' . $account->account_name_zh,
|
||||
'amount' => $amount,
|
||||
'description' => $description,
|
||||
'chart_of_account_id' => $account->id,
|
||||
'submitted_at' => $date,
|
||||
'status' => FinanceDocument::STATUS_APPROVED_CHAIR,
|
||||
]);
|
||||
|
||||
// Create accounting entries (double-entry)
|
||||
// Debit: Expense account
|
||||
AccountingEntry::create([
|
||||
'finance_document_id' => $document->id,
|
||||
'chart_of_account_id' => $account->id,
|
||||
'entry_type' => AccountingEntry::ENTRY_TYPE_DEBIT,
|
||||
'amount' => $amount,
|
||||
'entry_date' => $date,
|
||||
'description' => $description,
|
||||
]);
|
||||
|
||||
// Credit: Cash
|
||||
AccountingEntry::create([
|
||||
'finance_document_id' => $document->id,
|
||||
'chart_of_account_id' => $this->getAccount('1101')->id,
|
||||
'entry_type' => AccountingEntry::ENTRY_TYPE_CREDIT,
|
||||
'amount' => $amount,
|
||||
'entry_date' => $date,
|
||||
'description' => '支出 - ' . $description,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function mapAccountCode($excelCode)
|
||||
{
|
||||
return $this->mapping[$excelCode] ?? $excelCode;
|
||||
}
|
||||
|
||||
protected function classifyExpense($description): string
|
||||
{
|
||||
foreach ($this->expenseKeywords as $rule) {
|
||||
if (empty($rule['keywords'])) {
|
||||
if ($rule['is_default'] ?? false) {
|
||||
return $rule['account_code'];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($rule['keywords'] as $keyword) {
|
||||
if (mb_strpos($description, $keyword) !== false) {
|
||||
return $rule['account_code'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return '5901'; // Default: 雜項支出
|
||||
}
|
||||
|
||||
protected function getAccount($accountCode)
|
||||
{
|
||||
if (!isset($this->accountCache[$accountCode])) {
|
||||
$this->accountCache[$accountCode] = ChartOfAccount::where('account_code', $accountCode)->first();
|
||||
}
|
||||
|
||||
return $this->accountCache[$accountCode];
|
||||
}
|
||||
|
||||
protected function parseDate($value)
|
||||
{
|
||||
if (is_numeric($value)) {
|
||||
return ExcelDate::excelToDateTimeObject($value);
|
||||
}
|
||||
|
||||
if ($value instanceof \DateTime) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return new \DateTime($value);
|
||||
}
|
||||
|
||||
protected function findColumn($headers, $patterns)
|
||||
{
|
||||
foreach ($headers as $col => $header) {
|
||||
foreach ($patterns as $pattern) {
|
||||
if (mb_strpos($header, $pattern) !== false) {
|
||||
return $col;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function showSummary()
|
||||
{
|
||||
$this->info('=== 匯入統計 ===');
|
||||
$this->table(
|
||||
['項目', '數量'],
|
||||
[
|
||||
['收入筆數', $this->stats['income_count']],
|
||||
['支出筆數', $this->stats['expense_count']],
|
||||
['跳過筆數', $this->stats['skipped_count']],
|
||||
['錯誤筆數', $this->stats['error_count']],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
protected function verifyBalance()
|
||||
{
|
||||
$this->info('=== 驗證借貸平衡 ===');
|
||||
|
||||
$debitTotal = AccountingEntry::where('entry_type', AccountingEntry::ENTRY_TYPE_DEBIT)->sum('amount');
|
||||
$creditTotal = AccountingEntry::where('entry_type', AccountingEntry::ENTRY_TYPE_CREDIT)->sum('amount');
|
||||
|
||||
$this->line("借方總計: " . number_format($debitTotal, 2));
|
||||
$this->line("貸方總計: " . number_format($creditTotal, 2));
|
||||
|
||||
if (bccomp((string)$debitTotal, (string)$creditTotal, 2) === 0) {
|
||||
$this->info('✅ 借貸平衡');
|
||||
} else {
|
||||
$diff = $debitTotal - $creditTotal;
|
||||
$this->error("❌ 借貸不平衡,差額: " . number_format($diff, 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
346
app/Http/Controllers/Admin/AnnouncementController.php
Normal file
346
app/Http/Controllers/Admin/AnnouncementController.php
Normal file
@@ -0,0 +1,346 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Announcement;
|
||||
use App\Models\AuditLog;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class AnnouncementController extends Controller
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('can:view_announcements')->only(['index', 'show']);
|
||||
$this->middleware('can:create_announcements')->only(['create', 'store']);
|
||||
$this->middleware('can:edit_announcements')->only(['edit', 'update']);
|
||||
$this->middleware('can:delete_announcements')->only(['destroy']);
|
||||
$this->middleware('can:publish_announcements')->only(['publish', 'archive']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of announcements
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$query = Announcement::with(['creator', 'lastUpdatedBy'])
|
||||
->orderByDesc('is_pinned')
|
||||
->orderByDesc('created_at');
|
||||
|
||||
// Filter by status
|
||||
if ($request->filled('status')) {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
// Filter by access level
|
||||
if ($request->filled('access_level')) {
|
||||
$query->where('access_level', $request->access_level);
|
||||
}
|
||||
|
||||
// Filter by pinned
|
||||
if ($request->filled('pinned')) {
|
||||
$query->where('is_pinned', $request->pinned === 'yes');
|
||||
}
|
||||
|
||||
// Search
|
||||
if ($request->filled('search')) {
|
||||
$search = $request->search;
|
||||
$query->where(function($q) use ($search) {
|
||||
$q->where('title', 'like', "%{$search}%")
|
||||
->orWhere('content', 'like', "%{$search}%");
|
||||
});
|
||||
}
|
||||
|
||||
$announcements = $query->paginate(20);
|
||||
|
||||
// Statistics
|
||||
$stats = [
|
||||
'total' => Announcement::count(),
|
||||
'draft' => Announcement::draft()->count(),
|
||||
'published' => Announcement::published()->count(),
|
||||
'archived' => Announcement::archived()->count(),
|
||||
'pinned' => Announcement::pinned()->count(),
|
||||
];
|
||||
|
||||
return view('admin.announcements.index', compact('announcements', 'stats'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new announcement
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
return view('admin.announcements.create');
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created announcement
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'title' => 'required|string|max:255',
|
||||
'content' => 'required|string',
|
||||
'access_level' => 'required|in:public,members,board,admin',
|
||||
'published_at' => 'nullable|date',
|
||||
'expires_at' => 'nullable|date|after:published_at',
|
||||
'is_pinned' => 'boolean',
|
||||
'display_order' => 'nullable|integer',
|
||||
'save_action' => 'required|in:draft,publish',
|
||||
]);
|
||||
|
||||
$announcement = Announcement::create([
|
||||
'title' => $validated['title'],
|
||||
'content' => $validated['content'],
|
||||
'access_level' => $validated['access_level'],
|
||||
'status' => $validated['save_action'] === 'publish' ? Announcement::STATUS_PUBLISHED : Announcement::STATUS_DRAFT,
|
||||
'published_at' => $validated['save_action'] === 'publish' ? ($validated['published_at'] ?? now()) : null,
|
||||
'expires_at' => $validated['expires_at'] ?? null,
|
||||
'is_pinned' => $validated['is_pinned'] ?? false,
|
||||
'display_order' => $validated['display_order'] ?? 0,
|
||||
'created_by_user_id' => auth()->id(),
|
||||
'last_updated_by_user_id' => auth()->id(),
|
||||
]);
|
||||
|
||||
// Audit log
|
||||
AuditLog::create([
|
||||
'user_id' => auth()->id(),
|
||||
'action' => 'announcement.created',
|
||||
'description' => "建立公告:{$announcement->title} (狀態:{$announcement->getStatusLabel()})",
|
||||
'ip_address' => request()->ip(),
|
||||
]);
|
||||
|
||||
$message = $validated['save_action'] === 'publish' ? '公告已成功發布' : '公告已儲存為草稿';
|
||||
|
||||
return redirect()
|
||||
->route('admin.announcements.show', $announcement)
|
||||
->with('status', $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified announcement
|
||||
*/
|
||||
public function show(Announcement $announcement)
|
||||
{
|
||||
// Check if user can view this announcement
|
||||
if (!$announcement->canBeViewedBy(auth()->user())) {
|
||||
abort(403, '您沒有權限查看此公告');
|
||||
}
|
||||
|
||||
$announcement->load(['creator', 'lastUpdatedBy']);
|
||||
|
||||
// Increment view count if viewing published announcement
|
||||
if ($announcement->isPublished()) {
|
||||
$announcement->incrementViewCount();
|
||||
}
|
||||
|
||||
return view('admin.announcements.show', compact('announcement'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified announcement
|
||||
*/
|
||||
public function edit(Announcement $announcement)
|
||||
{
|
||||
// Check if user can edit this announcement
|
||||
if (!$announcement->canBeEditedBy(auth()->user())) {
|
||||
abort(403, '您沒有權限編輯此公告');
|
||||
}
|
||||
|
||||
return view('admin.announcements.edit', compact('announcement'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified announcement
|
||||
*/
|
||||
public function update(Request $request, Announcement $announcement)
|
||||
{
|
||||
// Check if user can edit this announcement
|
||||
if (!$announcement->canBeEditedBy(auth()->user())) {
|
||||
abort(403, '您沒有權限編輯此公告');
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'title' => 'required|string|max:255',
|
||||
'content' => 'required|string',
|
||||
'access_level' => 'required|in:public,members,board,admin',
|
||||
'published_at' => 'nullable|date',
|
||||
'expires_at' => 'nullable|date|after:published_at',
|
||||
'is_pinned' => 'boolean',
|
||||
'display_order' => 'nullable|integer',
|
||||
]);
|
||||
|
||||
$announcement->update([
|
||||
'title' => $validated['title'],
|
||||
'content' => $validated['content'],
|
||||
'access_level' => $validated['access_level'],
|
||||
'published_at' => $validated['published_at'],
|
||||
'expires_at' => $validated['expires_at'] ?? null,
|
||||
'is_pinned' => $validated['is_pinned'] ?? false,
|
||||
'display_order' => $validated['display_order'] ?? 0,
|
||||
'last_updated_by_user_id' => auth()->id(),
|
||||
]);
|
||||
|
||||
// Audit log
|
||||
AuditLog::create([
|
||||
'user_id' => auth()->id(),
|
||||
'action' => 'announcement.updated',
|
||||
'description' => "更新公告:{$announcement->title}",
|
||||
'ip_address' => request()->ip(),
|
||||
]);
|
||||
|
||||
return redirect()
|
||||
->route('admin.announcements.show', $announcement)
|
||||
->with('status', '公告已成功更新');
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified announcement (soft delete)
|
||||
*/
|
||||
public function destroy(Announcement $announcement)
|
||||
{
|
||||
// Check if user can delete this announcement
|
||||
if (!$announcement->canBeEditedBy(auth()->user())) {
|
||||
abort(403, '您沒有權限刪除此公告');
|
||||
}
|
||||
|
||||
$title = $announcement->title;
|
||||
$announcement->delete();
|
||||
|
||||
// Audit log
|
||||
AuditLog::create([
|
||||
'user_id' => auth()->id(),
|
||||
'action' => 'announcement.deleted',
|
||||
'description' => "刪除公告:{$title}",
|
||||
'ip_address' => request()->ip(),
|
||||
]);
|
||||
|
||||
return redirect()
|
||||
->route('admin.announcements.index')
|
||||
->with('status', '公告已成功刪除');
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish a draft announcement
|
||||
*/
|
||||
public function publish(Announcement $announcement)
|
||||
{
|
||||
// Check permission
|
||||
if (!auth()->user()->can('publish_announcements')) {
|
||||
abort(403, '您沒有權限發布公告');
|
||||
}
|
||||
|
||||
// Check if user can edit this announcement
|
||||
if (!$announcement->canBeEditedBy(auth()->user())) {
|
||||
abort(403, '您沒有權限發布此公告');
|
||||
}
|
||||
|
||||
if ($announcement->isPublished()) {
|
||||
return back()->with('error', '此公告已經發布');
|
||||
}
|
||||
|
||||
$announcement->publish(auth()->user());
|
||||
|
||||
// Audit log
|
||||
AuditLog::create([
|
||||
'user_id' => auth()->id(),
|
||||
'action' => 'announcement.published',
|
||||
'description' => "發布公告:{$announcement->title}",
|
||||
'ip_address' => request()->ip(),
|
||||
]);
|
||||
|
||||
return back()->with('status', '公告已成功發布');
|
||||
}
|
||||
|
||||
/**
|
||||
* Archive an announcement
|
||||
*/
|
||||
public function archive(Announcement $announcement)
|
||||
{
|
||||
// Check permission
|
||||
if (!auth()->user()->can('publish_announcements')) {
|
||||
abort(403, '您沒有權限歸檔公告');
|
||||
}
|
||||
|
||||
// Check if user can edit this announcement
|
||||
if (!$announcement->canBeEditedBy(auth()->user())) {
|
||||
abort(403, '您沒有權限歸檔此公告');
|
||||
}
|
||||
|
||||
if ($announcement->isArchived()) {
|
||||
return back()->with('error', '此公告已經歸檔');
|
||||
}
|
||||
|
||||
$announcement->archive(auth()->user());
|
||||
|
||||
// Audit log
|
||||
AuditLog::create([
|
||||
'user_id' => auth()->id(),
|
||||
'action' => 'announcement.archived',
|
||||
'description' => "歸檔公告:{$announcement->title}",
|
||||
'ip_address' => request()->ip(),
|
||||
]);
|
||||
|
||||
return back()->with('status', '公告已成功歸檔');
|
||||
}
|
||||
|
||||
/**
|
||||
* Pin an announcement
|
||||
*/
|
||||
public function pin(Request $request, Announcement $announcement)
|
||||
{
|
||||
// Check permission
|
||||
if (!auth()->user()->can('edit_announcements')) {
|
||||
abort(403, '您沒有權限置頂公告');
|
||||
}
|
||||
|
||||
// Check if user can edit this announcement
|
||||
if (!$announcement->canBeEditedBy(auth()->user())) {
|
||||
abort(403, '您沒有權限置頂此公告');
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'display_order' => 'nullable|integer',
|
||||
]);
|
||||
|
||||
$announcement->pin($validated['display_order'] ?? 0, auth()->user());
|
||||
|
||||
// Audit log
|
||||
AuditLog::create([
|
||||
'user_id' => auth()->id(),
|
||||
'action' => 'announcement.pinned',
|
||||
'description' => "置頂公告:{$announcement->title}",
|
||||
'ip_address' => request()->ip(),
|
||||
]);
|
||||
|
||||
return back()->with('status', '公告已成功置頂');
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpin an announcement
|
||||
*/
|
||||
public function unpin(Announcement $announcement)
|
||||
{
|
||||
// Check permission
|
||||
if (!auth()->user()->can('edit_announcements')) {
|
||||
abort(403, '您沒有權限取消置頂公告');
|
||||
}
|
||||
|
||||
// Check if user can edit this announcement
|
||||
if (!$announcement->canBeEditedBy(auth()->user())) {
|
||||
abort(403, '您沒有權限取消置頂此公告');
|
||||
}
|
||||
|
||||
$announcement->unpin(auth()->user());
|
||||
|
||||
// Audit log
|
||||
AuditLog::create([
|
||||
'user_id' => auth()->id(),
|
||||
'action' => 'announcement.unpinned',
|
||||
'description' => "取消置頂公告:{$announcement->title}",
|
||||
'ip_address' => request()->ip(),
|
||||
]);
|
||||
|
||||
return back()->with('status', '公告已取消置頂');
|
||||
}
|
||||
}
|
||||
@@ -83,8 +83,8 @@ class DocumentController extends Controller
|
||||
$document = Document::create([
|
||||
'document_category_id' => $validated['document_category_id'],
|
||||
'title' => $validated['title'],
|
||||
'document_number' => $validated['document_number'],
|
||||
'description' => $validated['description'],
|
||||
'document_number' => $validated['document_number'] ?? null,
|
||||
'description' => $validated['description'] ?? null,
|
||||
'access_level' => $validated['access_level'],
|
||||
'status' => 'active',
|
||||
'created_by_user_id' => auth()->id(),
|
||||
@@ -360,7 +360,7 @@ class DocumentController extends Controller
|
||||
->get();
|
||||
|
||||
// Monthly upload trends (last 6 months)
|
||||
$uploadTrends = Document::selectRaw('DATE_FORMAT(created_at, "%Y-%m") as month, COUNT(*) as count')
|
||||
$uploadTrends = Document::selectRaw("strftime('%Y-%m', created_at) as month, COUNT(*) as count")
|
||||
->where('created_at', '>=', now()->subMonths(6))
|
||||
->groupBy('month')
|
||||
->orderBy('month', 'desc')
|
||||
|
||||
87
app/Http/Controllers/Admin/GeneralLedgerController.php
Normal file
87
app/Http/Controllers/Admin/GeneralLedgerController.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\AccountingEntry;
|
||||
use App\Models\ChartOfAccount;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class GeneralLedgerController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the general ledger
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$accounts = ChartOfAccount::where('is_active', true)
|
||||
->orderBy('account_code')
|
||||
->get();
|
||||
|
||||
$selectedAccountId = $request->input('account_id');
|
||||
$startDate = $request->input('start_date', now()->startOfYear()->format('Y-m-d'));
|
||||
$endDate = $request->input('end_date', now()->format('Y-m-d'));
|
||||
|
||||
$entries = null;
|
||||
$selectedAccount = null;
|
||||
$openingBalance = 0;
|
||||
$debitTotal = 0;
|
||||
$creditTotal = 0;
|
||||
$closingBalance = 0;
|
||||
|
||||
if ($selectedAccountId) {
|
||||
$selectedAccount = ChartOfAccount::findOrFail($selectedAccountId);
|
||||
|
||||
// Get opening balance (all entries before start date)
|
||||
$openingDebit = AccountingEntry::where('chart_of_account_id', $selectedAccountId)
|
||||
->where('entry_date', '<', $startDate)
|
||||
->where('entry_type', AccountingEntry::ENTRY_TYPE_DEBIT)
|
||||
->sum('amount');
|
||||
|
||||
$openingCredit = AccountingEntry::where('chart_of_account_id', $selectedAccountId)
|
||||
->where('entry_date', '<', $startDate)
|
||||
->where('entry_type', AccountingEntry::ENTRY_TYPE_CREDIT)
|
||||
->sum('amount');
|
||||
|
||||
// Calculate opening balance based on account type
|
||||
if (in_array($selectedAccount->account_type, ['asset', 'expense'])) {
|
||||
// Assets and Expenses: Debit increases, Credit decreases
|
||||
$openingBalance = $openingDebit - $openingCredit;
|
||||
} else {
|
||||
// Liabilities, Equity, Income: Credit increases, Debit decreases
|
||||
$openingBalance = $openingCredit - $openingDebit;
|
||||
}
|
||||
|
||||
// Get entries for the period
|
||||
$entries = AccountingEntry::with(['financeDocument', 'chartOfAccount'])
|
||||
->where('chart_of_account_id', $selectedAccountId)
|
||||
->whereBetween('entry_date', [$startDate, $endDate])
|
||||
->orderBy('entry_date')
|
||||
->orderBy('id')
|
||||
->get();
|
||||
|
||||
// Calculate totals for the period
|
||||
$debitTotal = $entries->where('entry_type', AccountingEntry::ENTRY_TYPE_DEBIT)->sum('amount');
|
||||
$creditTotal = $entries->where('entry_type', AccountingEntry::ENTRY_TYPE_CREDIT)->sum('amount');
|
||||
|
||||
// Calculate closing balance
|
||||
if (in_array($selectedAccount->account_type, ['asset', 'expense'])) {
|
||||
$closingBalance = $openingBalance + $debitTotal - $creditTotal;
|
||||
} else {
|
||||
$closingBalance = $openingBalance + $creditTotal - $debitTotal;
|
||||
}
|
||||
}
|
||||
|
||||
return view('admin.general-ledger.index', compact(
|
||||
'accounts',
|
||||
'selectedAccount',
|
||||
'entries',
|
||||
'startDate',
|
||||
'endDate',
|
||||
'openingBalance',
|
||||
'debitTotal',
|
||||
'creditTotal',
|
||||
'closingBalance'
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Admin;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\AuditLog;
|
||||
use App\Models\SystemSetting;
|
||||
use App\Services\MembershipFeeCalculator;
|
||||
use App\Services\SettingsService;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
@@ -270,4 +271,45 @@ class SystemSettingsController extends Controller
|
||||
|
||||
return redirect()->route('admin.settings.advanced')->with('status', '進階設定已更新');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show membership fee settings page
|
||||
*/
|
||||
public function membership()
|
||||
{
|
||||
$feeCalculator = app(MembershipFeeCalculator::class);
|
||||
|
||||
$settings = [
|
||||
'entrance_fee' => $feeCalculator->getEntranceFee(),
|
||||
'annual_fee' => $feeCalculator->getAnnualFee(),
|
||||
'disability_discount_rate' => $feeCalculator->getDisabilityDiscountRate() * 100, // Convert to percentage
|
||||
];
|
||||
|
||||
return view('admin.settings.membership', compact('settings'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update membership fee settings
|
||||
*/
|
||||
public function updateMembership(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'entrance_fee' => 'required|numeric|min:0|max:100000',
|
||||
'annual_fee' => 'required|numeric|min:0|max:100000',
|
||||
'disability_discount_rate' => 'required|numeric|min:0|max:100',
|
||||
]);
|
||||
|
||||
SystemSetting::set('membership_fee.entrance_fee', $validated['entrance_fee'], 'float', 'membership');
|
||||
SystemSetting::set('membership_fee.annual_fee', $validated['annual_fee'], 'float', 'membership');
|
||||
SystemSetting::set('membership_fee.disability_discount_rate', $validated['disability_discount_rate'] / 100, 'float', 'membership');
|
||||
|
||||
AuditLog::create([
|
||||
'user_id' => auth()->id(),
|
||||
'action' => 'settings.membership.updated',
|
||||
'description' => '更新會費設定',
|
||||
'ip_address' => $request->ip(),
|
||||
]);
|
||||
|
||||
return redirect()->route('admin.settings.membership')->with('status', '會費設定已更新');
|
||||
}
|
||||
}
|
||||
|
||||
75
app/Http/Controllers/Admin/TrialBalanceController.php
Normal file
75
app/Http/Controllers/Admin/TrialBalanceController.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\AccountingEntry;
|
||||
use App\Models\ChartOfAccount;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class TrialBalanceController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the trial balance
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$startDate = $request->input('start_date', now()->startOfYear()->format('Y-m-d'));
|
||||
$endDate = $request->input('end_date', now()->format('Y-m-d'));
|
||||
|
||||
// Get all active accounts with their balances
|
||||
$accounts = ChartOfAccount::where('is_active', true)
|
||||
->orderBy('account_code')
|
||||
->get()
|
||||
->map(function ($account) use ($startDate, $endDate) {
|
||||
// Get debit and credit totals for this account
|
||||
$debitTotal = AccountingEntry::where('chart_of_account_id', $account->id)
|
||||
->whereBetween('entry_date', [$startDate, $endDate])
|
||||
->where('entry_type', AccountingEntry::ENTRY_TYPE_DEBIT)
|
||||
->sum('amount');
|
||||
|
||||
$creditTotal = AccountingEntry::where('chart_of_account_id', $account->id)
|
||||
->whereBetween('entry_date', [$startDate, $endDate])
|
||||
->where('entry_type', AccountingEntry::ENTRY_TYPE_CREDIT)
|
||||
->sum('amount');
|
||||
|
||||
// Only include accounts with activity
|
||||
if ($debitTotal == 0 && $creditTotal == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'account' => $account,
|
||||
'debit_total' => $debitTotal,
|
||||
'credit_total' => $creditTotal,
|
||||
];
|
||||
})
|
||||
->filter() // Remove null entries
|
||||
->values();
|
||||
|
||||
// Calculate grand totals
|
||||
$grandDebitTotal = $accounts->sum('debit_total');
|
||||
$grandCreditTotal = $accounts->sum('credit_total');
|
||||
|
||||
// Check if balanced
|
||||
$isBalanced = bccomp((string)$grandDebitTotal, (string)$grandCreditTotal, 2) === 0;
|
||||
$difference = $grandDebitTotal - $grandCreditTotal;
|
||||
|
||||
// Group accounts by type
|
||||
$accountsByType = $accounts->groupBy(function ($item) {
|
||||
return $item['account']->account_type;
|
||||
});
|
||||
|
||||
return view('admin.trial-balance.index', compact(
|
||||
'accounts',
|
||||
'accountsByType',
|
||||
'startDate',
|
||||
'endDate',
|
||||
'grandDebitTotal',
|
||||
'grandCreditTotal',
|
||||
'isBalanced',
|
||||
'difference'
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ namespace App\Http\Controllers;
|
||||
use App\Models\Member;
|
||||
use App\Models\MembershipPayment;
|
||||
use App\Models\FinanceDocument;
|
||||
use App\Models\Announcement;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class AdminDashboardController extends Controller
|
||||
@@ -47,16 +48,26 @@ class AdminDashboardController extends Controller
|
||||
// Documents pending user's approval
|
||||
$user = auth()->user();
|
||||
$myPendingApprovals = 0;
|
||||
if ($user->hasRole('cashier')) {
|
||||
if ($user->hasRole('finance_cashier')) {
|
||||
$myPendingApprovals += FinanceDocument::where('status', FinanceDocument::STATUS_PENDING)->count();
|
||||
}
|
||||
if ($user->hasRole('accountant')) {
|
||||
if ($user->hasRole('finance_accountant')) {
|
||||
$myPendingApprovals += FinanceDocument::where('status', FinanceDocument::STATUS_APPROVED_CASHIER)->count();
|
||||
}
|
||||
if ($user->hasRole('chair')) {
|
||||
if ($user->hasRole('finance_chair')) {
|
||||
$myPendingApprovals += FinanceDocument::where('status', FinanceDocument::STATUS_APPROVED_ACCOUNTANT)->count();
|
||||
}
|
||||
|
||||
// Recent announcements
|
||||
$recentAnnouncements = Announcement::query()
|
||||
->published()
|
||||
->active()
|
||||
->forAccessLevel($user)
|
||||
->orderByDesc('is_pinned')
|
||||
->orderByDesc('published_at')
|
||||
->limit(5)
|
||||
->get();
|
||||
|
||||
return view('admin.dashboard.index', compact(
|
||||
'totalMembers',
|
||||
'activeMembers',
|
||||
@@ -70,7 +81,8 @@ class AdminDashboardController extends Controller
|
||||
'pendingApprovals',
|
||||
'fullyApprovedDocs',
|
||||
'rejectedDocs',
|
||||
'myPendingApprovals'
|
||||
'myPendingApprovals',
|
||||
'recentAnnouncements'
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,7 +217,7 @@ class AdminMemberController extends Controller
|
||||
public function showActivate(Member $member)
|
||||
{
|
||||
// Check if user has permission
|
||||
if (!auth()->user()->can('activate_memberships') && !auth()->user()->is_admin) {
|
||||
if (!auth()->user()->can('activate_memberships') && !auth()->user()->hasRole('admin')) {
|
||||
abort(403, 'You do not have permission to activate memberships.');
|
||||
}
|
||||
|
||||
@@ -227,7 +227,7 @@ class AdminMemberController extends Controller
|
||||
->latest()
|
||||
->first();
|
||||
|
||||
if (!$approvedPayment && !auth()->user()->is_admin) {
|
||||
if (!$approvedPayment && !auth()->user()->hasRole('admin')) {
|
||||
return redirect()->route('admin.members.show', $member)
|
||||
->with('error', __('Member must have an approved payment before activation.'));
|
||||
}
|
||||
@@ -241,7 +241,7 @@ class AdminMemberController extends Controller
|
||||
public function activate(Request $request, Member $member)
|
||||
{
|
||||
// Check if user has permission
|
||||
if (!auth()->user()->can('activate_memberships') && !auth()->user()->is_admin) {
|
||||
if (!auth()->user()->can('activate_memberships') && !auth()->user()->hasRole('admin')) {
|
||||
abort(403, 'You do not have permission to activate memberships.');
|
||||
}
|
||||
|
||||
@@ -344,4 +344,53 @@ class AdminMemberController extends Controller
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function batchDestroy(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'ids' => ['required', 'array'],
|
||||
'ids.*' => ['exists:members,id'],
|
||||
]);
|
||||
|
||||
$count = Member::whereIn('id', $validated['ids'])->delete();
|
||||
|
||||
AuditLogger::log('members.batch_deleted', null, ['ids' => $validated['ids'], 'count' => $count]);
|
||||
|
||||
return back()->with('status', __(':count members deleted successfully.', ['count' => $count]));
|
||||
}
|
||||
|
||||
public function batchUpdateStatus(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'ids' => ['required', 'array'],
|
||||
'ids.*' => ['exists:members,id'],
|
||||
'status' => ['required', 'in:pending,active,expired,suspended'],
|
||||
]);
|
||||
|
||||
$count = Member::whereIn('id', $validated['ids'])->update(['membership_status' => $validated['status']]);
|
||||
|
||||
AuditLogger::log('members.batch_status_updated', null, [
|
||||
'ids' => $validated['ids'],
|
||||
'status' => $validated['status'],
|
||||
'count' => $count
|
||||
]);
|
||||
|
||||
return back()->with('status', __(':count members updated successfully.', ['count' => $count]));
|
||||
}
|
||||
|
||||
/**
|
||||
* View member's disability certificate
|
||||
*/
|
||||
public function viewDisabilityCertificate(Member $member)
|
||||
{
|
||||
if (!$member->disability_certificate_path) {
|
||||
abort(404, '找不到身心障礙手冊');
|
||||
}
|
||||
|
||||
if (!\Illuminate\Support\Facades\Storage::disk('private')->exists($member->disability_certificate_path)) {
|
||||
abort(404, '檔案不存在');
|
||||
}
|
||||
|
||||
return \Illuminate\Support\Facades\Storage::disk('private')->response($member->disability_certificate_path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Member;
|
||||
use App\Models\User;
|
||||
use App\Providers\RouteServiceProvider;
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
@@ -42,6 +43,15 @@ class RegisteredUserController extends Controller
|
||||
'password' => Hash::make($request->password),
|
||||
]);
|
||||
|
||||
// Auto-create member record
|
||||
Member::create([
|
||||
'user_id' => $user->id,
|
||||
'full_name' => $user->name,
|
||||
'email' => $user->email,
|
||||
'membership_status' => Member::STATUS_PENDING,
|
||||
'membership_type' => Member::TYPE_REGULAR,
|
||||
]);
|
||||
|
||||
event(new Registered($user));
|
||||
|
||||
Auth::login($user);
|
||||
|
||||
@@ -201,7 +201,7 @@ class BudgetController extends Controller
|
||||
|
||||
// Check if user has permission (admin or chair)
|
||||
$user = $request->user();
|
||||
if (!$user->hasRole('chair') && !$user->is_admin && !$user->hasRole('admin')) {
|
||||
if (!$user->hasRole('finance_chair') && !$user->hasRole('admin')) {
|
||||
abort(403, 'Only chair can approve budgets.');
|
||||
}
|
||||
|
||||
|
||||
@@ -26,11 +26,6 @@ class FinanceDocumentController extends Controller
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
// Filter by request type
|
||||
if ($request->filled('request_type')) {
|
||||
$query->where('request_type', $request->request_type);
|
||||
}
|
||||
|
||||
// Filter by amount tier
|
||||
if ($request->filled('amount_tier')) {
|
||||
$query->where('amount_tier', $request->amount_tier);
|
||||
@@ -79,7 +74,6 @@ class FinanceDocumentController extends Controller
|
||||
'member_id' => ['nullable', 'exists:members,id'],
|
||||
'title' => ['required', 'string', 'max:255'],
|
||||
'amount' => ['required', 'numeric', 'min:0'],
|
||||
'request_type' => ['required', 'in:expense_reimbursement,advance_payment,purchase_request,petty_cash'],
|
||||
'description' => ['nullable', 'string'],
|
||||
'attachment' => ['nullable', 'file', 'max:10240'], // 10MB max
|
||||
]);
|
||||
@@ -95,7 +89,6 @@ class FinanceDocumentController extends Controller
|
||||
'submitted_by_user_id' => $request->user()->id,
|
||||
'title' => $validated['title'],
|
||||
'amount' => $validated['amount'],
|
||||
'request_type' => $validated['request_type'],
|
||||
'description' => $validated['description'] ?? null,
|
||||
'attachment_path' => $attachmentPath,
|
||||
'status' => FinanceDocument::STATUS_PENDING,
|
||||
@@ -115,17 +108,13 @@ class FinanceDocumentController extends Controller
|
||||
|
||||
// Send email notification to finance cashiers
|
||||
$cashiers = User::role('finance_cashier')->get();
|
||||
if ($cashiers->isEmpty()) {
|
||||
// Fallback to old cashier role for backward compatibility
|
||||
$cashiers = User::role('cashier')->get();
|
||||
}
|
||||
foreach ($cashiers as $cashier) {
|
||||
Mail::to($cashier->email)->queue(new FinanceDocumentSubmitted($document));
|
||||
}
|
||||
|
||||
return redirect()
|
||||
->route('admin.finance.index')
|
||||
->with('status', '財務申請單已提交。申請類型:' . $document->getRequestTypeText() . ',金額級別:' . $document->getAmountTierText());
|
||||
->with('status', '報銷申請單已提交。金額級別:' . $document->getAmountTierText());
|
||||
}
|
||||
|
||||
public function show(FinanceDocument $financeDocument)
|
||||
@@ -133,13 +122,19 @@ class FinanceDocumentController extends Controller
|
||||
$financeDocument->load([
|
||||
'member',
|
||||
'submittedBy',
|
||||
// 新工作流程 relationships
|
||||
'approvedBySecretary',
|
||||
'approvedByChair',
|
||||
'approvedByBoardMeeting',
|
||||
'requesterConfirmedBy',
|
||||
'cashierConfirmedBy',
|
||||
'accountantRecordedBy',
|
||||
// Legacy relationships
|
||||
'approvedByCashier',
|
||||
'approvedByAccountant',
|
||||
'approvedByChair',
|
||||
'rejectedBy',
|
||||
'chartOfAccount',
|
||||
'budgetItem',
|
||||
'approvedByBoardMeeting',
|
||||
'paymentOrderCreatedByAccountant',
|
||||
'paymentVerifiedByCashier',
|
||||
'paymentExecutedByCashier',
|
||||
@@ -159,72 +154,48 @@ class FinanceDocumentController extends Controller
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
// Check if user has any finance approval permissions
|
||||
$isCashier = $user->hasRole('finance_cashier') || $user->hasRole('cashier');
|
||||
$isAccountant = $user->hasRole('finance_accountant') || $user->hasRole('accountant');
|
||||
$isChair = $user->hasRole('finance_chair') || $user->hasRole('chair');
|
||||
// 新工作流程:秘書長 → 理事長 → 董理事會
|
||||
$isSecretary = $user->hasRole('secretary_general');
|
||||
$isChair = $user->hasRole('finance_chair');
|
||||
$isBoardMember = $user->hasRole('finance_board_member');
|
||||
$isAdmin = $user->hasRole('admin');
|
||||
|
||||
// Determine which level of approval based on current status and user role
|
||||
if ($financeDocument->canBeApprovedByCashier() && $isCashier) {
|
||||
// 秘書長審核(第一階段)
|
||||
if ($financeDocument->canBeApprovedBySecretary($user) && ($isSecretary || $isAdmin)) {
|
||||
$financeDocument->update([
|
||||
'approved_by_cashier_id' => $user->id,
|
||||
'cashier_approved_at' => now(),
|
||||
'status' => FinanceDocument::STATUS_APPROVED_CASHIER,
|
||||
'approved_by_secretary_id' => $user->id,
|
||||
'secretary_approved_at' => now(),
|
||||
'status' => FinanceDocument::STATUS_APPROVED_SECRETARY,
|
||||
]);
|
||||
|
||||
AuditLogger::log('finance_document.approved_by_cashier', $financeDocument, [
|
||||
AuditLogger::log('finance_document.approved_by_secretary', $financeDocument, [
|
||||
'approved_by' => $user->name,
|
||||
'amount_tier' => $financeDocument->amount_tier,
|
||||
]);
|
||||
|
||||
// Send email notification to accountants
|
||||
$accountants = User::role('finance_accountant')->get();
|
||||
if ($accountants->isEmpty()) {
|
||||
$accountants = User::role('accountant')->get();
|
||||
}
|
||||
foreach ($accountants as $accountant) {
|
||||
Mail::to($accountant->email)->queue(new FinanceDocumentApprovedByCashier($financeDocument));
|
||||
}
|
||||
|
||||
return redirect()
|
||||
->route('admin.finance.show', $financeDocument)
|
||||
->with('status', '出納已審核通過。已送交會計審核。');
|
||||
}
|
||||
|
||||
if ($financeDocument->canBeApprovedByAccountant() && $isAccountant) {
|
||||
$financeDocument->update([
|
||||
'approved_by_accountant_id' => $user->id,
|
||||
'accountant_approved_at' => now(),
|
||||
'status' => FinanceDocument::STATUS_APPROVED_ACCOUNTANT,
|
||||
]);
|
||||
|
||||
AuditLogger::log('finance_document.approved_by_accountant', $financeDocument, [
|
||||
'approved_by' => $user->name,
|
||||
'amount_tier' => $financeDocument->amount_tier,
|
||||
]);
|
||||
|
||||
// For small amounts, approval is complete (no chair needed)
|
||||
// 小額:審核完成
|
||||
if ($financeDocument->amount_tier === FinanceDocument::AMOUNT_TIER_SMALL) {
|
||||
// 通知申請人審核已完成,可以領款
|
||||
Mail::to($financeDocument->submittedBy->email)->queue(new FinanceDocumentFullyApproved($financeDocument));
|
||||
|
||||
return redirect()
|
||||
->route('admin.finance.show', $financeDocument)
|
||||
->with('status', '會計已審核通過。小額申請審核完成,可以製作付款單。');
|
||||
->with('status', '秘書長已核准。小額申請審核完成,申請人可向出納領款。');
|
||||
}
|
||||
|
||||
// For medium and large amounts, send to chair
|
||||
// 中額/大額:送交理事長
|
||||
$chairs = User::role('finance_chair')->get();
|
||||
if ($chairs->isEmpty()) {
|
||||
$chairs = User::role('chair')->get();
|
||||
}
|
||||
foreach ($chairs as $chair) {
|
||||
Mail::to($chair->email)->queue(new FinanceDocumentApprovedByAccountant($financeDocument));
|
||||
}
|
||||
|
||||
return redirect()
|
||||
->route('admin.finance.show', $financeDocument)
|
||||
->with('status', '會計已審核通過。已送交理事長審核。');
|
||||
->with('status', '秘書長已核准。已送交理事長審核。');
|
||||
}
|
||||
|
||||
if ($financeDocument->canBeApprovedByChair() && $isChair) {
|
||||
// 理事長審核(第二階段:中額或大額)
|
||||
if ($financeDocument->canBeApprovedByChair($user) && ($isChair || $isAdmin)) {
|
||||
$financeDocument->update([
|
||||
'approved_by_chair_id' => $user->id,
|
||||
'chair_approved_at' => now(),
|
||||
@@ -234,25 +205,147 @@ class FinanceDocumentController extends Controller
|
||||
AuditLogger::log('finance_document.approved_by_chair', $financeDocument, [
|
||||
'approved_by' => $user->name,
|
||||
'amount_tier' => $financeDocument->amount_tier,
|
||||
'requires_board_meeting' => $financeDocument->requires_board_meeting,
|
||||
]);
|
||||
|
||||
// For large amounts, notify that board meeting approval is still needed
|
||||
if ($financeDocument->requires_board_meeting && !$financeDocument->board_meeting_approved_at) {
|
||||
// 中額:審核完成
|
||||
if ($financeDocument->amount_tier === FinanceDocument::AMOUNT_TIER_MEDIUM) {
|
||||
Mail::to($financeDocument->submittedBy->email)->queue(new FinanceDocumentFullyApproved($financeDocument));
|
||||
|
||||
return redirect()
|
||||
->route('admin.finance.show', $financeDocument)
|
||||
->with('status', '理事長已審核通過。大額申請仍需理事會核准。');
|
||||
->with('status', '理事長已核准。中額申請審核完成,申請人可向出納領款。');
|
||||
}
|
||||
|
||||
// For medium amounts or large amounts with board approval, complete
|
||||
// 大額:送交董理事會
|
||||
$boardMembers = User::role('finance_board_member')->get();
|
||||
foreach ($boardMembers as $member) {
|
||||
Mail::to($member->email)->queue(new FinanceDocumentApprovedByAccountant($financeDocument));
|
||||
}
|
||||
|
||||
return redirect()
|
||||
->route('admin.finance.show', $financeDocument)
|
||||
->with('status', '理事長已核准。大額申請需送交董理事會審核。');
|
||||
}
|
||||
|
||||
// 董理事會審核(第三階段:大額)
|
||||
if ($financeDocument->canBeApprovedByBoard($user) && ($isBoardMember || $isAdmin)) {
|
||||
$financeDocument->update([
|
||||
'board_meeting_approved_by_id' => $user->id,
|
||||
'board_meeting_approved_at' => now(),
|
||||
'status' => FinanceDocument::STATUS_APPROVED_BOARD,
|
||||
]);
|
||||
|
||||
AuditLogger::log('finance_document.approved_by_board', $financeDocument, [
|
||||
'approved_by' => $user->name,
|
||||
'amount_tier' => $financeDocument->amount_tier,
|
||||
]);
|
||||
|
||||
Mail::to($financeDocument->submittedBy->email)->queue(new FinanceDocumentFullyApproved($financeDocument));
|
||||
|
||||
return redirect()
|
||||
->route('admin.finance.show', $financeDocument)
|
||||
->with('status', '審核流程完成。會計可以製作付款單。');
|
||||
->with('status', '董理事會已核准。審核流程完成,申請人可向出納領款。');
|
||||
}
|
||||
|
||||
abort(403, 'You are not authorized to approve this document at this stage.');
|
||||
abort(403, '您無權在此階段審核此文件。');
|
||||
}
|
||||
|
||||
/**
|
||||
* 出帳確認(雙重確認:申請人 + 出納)
|
||||
*/
|
||||
public function confirmDisbursement(Request $request, FinanceDocument $financeDocument)
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
$isRequester = $financeDocument->submitted_by_user_id === $user->id;
|
||||
$isCashier = $user->hasRole('finance_cashier');
|
||||
$isAdmin = $user->hasRole('admin');
|
||||
|
||||
// 申請人確認
|
||||
if ($isRequester && $financeDocument->canRequesterConfirmDisbursement($user)) {
|
||||
$financeDocument->update([
|
||||
'requester_confirmed_at' => now(),
|
||||
'requester_confirmed_by_id' => $user->id,
|
||||
]);
|
||||
|
||||
AuditLogger::log('finance_document.requester_confirmed_disbursement', $financeDocument, [
|
||||
'confirmed_by' => $user->name,
|
||||
]);
|
||||
|
||||
// 檢查是否雙重確認完成
|
||||
if ($financeDocument->isDisbursementComplete()) {
|
||||
$financeDocument->update(['disbursement_status' => FinanceDocument::DISBURSEMENT_COMPLETED]);
|
||||
return redirect()
|
||||
->route('admin.finance.show', $financeDocument)
|
||||
->with('status', '出帳確認完成。等待會計入帳。');
|
||||
}
|
||||
|
||||
return redirect()
|
||||
->route('admin.finance.show', $financeDocument)
|
||||
->with('status', '申請人已確認領款。等待出納確認。');
|
||||
}
|
||||
|
||||
// 出納確認
|
||||
if (($isCashier || $isAdmin) && $financeDocument->canCashierConfirmDisbursement()) {
|
||||
$financeDocument->update([
|
||||
'cashier_confirmed_at' => now(),
|
||||
'cashier_confirmed_by_id' => $user->id,
|
||||
]);
|
||||
|
||||
AuditLogger::log('finance_document.cashier_confirmed_disbursement', $financeDocument, [
|
||||
'confirmed_by' => $user->name,
|
||||
]);
|
||||
|
||||
// 檢查是否雙重確認完成
|
||||
if ($financeDocument->isDisbursementComplete()) {
|
||||
$financeDocument->update(['disbursement_status' => FinanceDocument::DISBURSEMENT_COMPLETED]);
|
||||
return redirect()
|
||||
->route('admin.finance.show', $financeDocument)
|
||||
->with('status', '出帳確認完成。等待會計入帳。');
|
||||
}
|
||||
|
||||
return redirect()
|
||||
->route('admin.finance.show', $financeDocument)
|
||||
->with('status', '出納已確認出帳。等待申請人確認。');
|
||||
}
|
||||
|
||||
abort(403, '您無權確認此出帳。');
|
||||
}
|
||||
|
||||
/**
|
||||
* 入帳確認(會計)
|
||||
*/
|
||||
public function confirmRecording(Request $request, FinanceDocument $financeDocument)
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
$isAccountant = $user->hasRole('finance_accountant');
|
||||
$isAdmin = $user->hasRole('admin');
|
||||
|
||||
if (!$financeDocument->canAccountantConfirmRecording()) {
|
||||
abort(403, '此文件尚未完成出帳確認,無法入帳。');
|
||||
}
|
||||
|
||||
if (!$isAccountant && !$isAdmin) {
|
||||
abort(403, '只有會計可以確認入帳。');
|
||||
}
|
||||
|
||||
$financeDocument->update([
|
||||
'accountant_recorded_at' => now(),
|
||||
'accountant_recorded_by_id' => $user->id,
|
||||
'recording_status' => FinanceDocument::RECORDING_COMPLETED,
|
||||
]);
|
||||
|
||||
// 自動產生會計分錄
|
||||
$financeDocument->autoGenerateAccountingEntries();
|
||||
|
||||
AuditLogger::log('finance_document.accountant_confirmed_recording', $financeDocument, [
|
||||
'confirmed_by' => $user->name,
|
||||
]);
|
||||
|
||||
return redirect()
|
||||
->route('admin.finance.show', $financeDocument)
|
||||
->with('status', '會計已確認入帳。財務流程完成。');
|
||||
}
|
||||
|
||||
public function reject(Request $request, FinanceDocument $financeDocument)
|
||||
@@ -269,9 +362,12 @@ class FinanceDocumentController extends Controller
|
||||
}
|
||||
|
||||
// Check if user has permission to reject
|
||||
$canReject = $user->hasRole('finance_cashier') || $user->hasRole('cashier') ||
|
||||
$user->hasRole('finance_accountant') || $user->hasRole('accountant') ||
|
||||
$user->hasRole('finance_chair') || $user->hasRole('chair');
|
||||
$canReject = $user->hasRole('admin') ||
|
||||
$user->hasRole('secretary_general') ||
|
||||
$user->hasRole('finance_cashier') ||
|
||||
$user->hasRole('finance_accountant') ||
|
||||
$user->hasRole('finance_chair') ||
|
||||
$user->hasRole('finance_board_member');
|
||||
|
||||
if (!$canReject) {
|
||||
abort(403, '您無權駁回此文件。');
|
||||
@@ -295,7 +391,7 @@ class FinanceDocumentController extends Controller
|
||||
|
||||
return redirect()
|
||||
->route('admin.finance.show', $financeDocument)
|
||||
->with('status', '財務申請單已駁回。');
|
||||
->with('status', '報銷申請單已駁回。');
|
||||
}
|
||||
|
||||
public function download(FinanceDocument $financeDocument)
|
||||
|
||||
409
app/Http/Controllers/IncomeController.php
Normal file
409
app/Http/Controllers/IncomeController.php
Normal file
@@ -0,0 +1,409 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\ChartOfAccount;
|
||||
use App\Models\Income;
|
||||
use App\Models\Member;
|
||||
use App\Support\AuditLogger;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class IncomeController extends Controller
|
||||
{
|
||||
/**
|
||||
* 收入列表
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$query = Income::query()
|
||||
->with(['chartOfAccount', 'member', 'recordedByCashier', 'confirmedByAccountant'])
|
||||
->orderByDesc('income_date')
|
||||
->orderByDesc('id');
|
||||
|
||||
// 篩選狀態
|
||||
if ($request->filled('status')) {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
// 篩選收入類型
|
||||
if ($request->filled('income_type')) {
|
||||
$query->where('income_type', $request->income_type);
|
||||
}
|
||||
|
||||
// 篩選付款方式
|
||||
if ($request->filled('payment_method')) {
|
||||
$query->where('payment_method', $request->payment_method);
|
||||
}
|
||||
|
||||
// 篩選會員
|
||||
if ($request->filled('member_id')) {
|
||||
$query->where('member_id', $request->member_id);
|
||||
}
|
||||
|
||||
// 篩選日期範圍
|
||||
if ($request->filled('date_from')) {
|
||||
$query->where('income_date', '>=', $request->date_from);
|
||||
}
|
||||
if ($request->filled('date_to')) {
|
||||
$query->where('income_date', '<=', $request->date_to);
|
||||
}
|
||||
|
||||
$incomes = $query->paginate(20);
|
||||
|
||||
// 統計資料
|
||||
$statistics = [
|
||||
'pending_count' => Income::pending()->count(),
|
||||
'pending_amount' => Income::pending()->sum('amount'),
|
||||
'confirmed_count' => Income::confirmed()->count(),
|
||||
'confirmed_amount' => Income::confirmed()->sum('amount'),
|
||||
];
|
||||
|
||||
return view('admin.incomes.index', [
|
||||
'incomes' => $incomes,
|
||||
'statistics' => $statistics,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增收入表單
|
||||
*/
|
||||
public function create(Request $request)
|
||||
{
|
||||
// 取得收入類會計科目
|
||||
$chartOfAccounts = ChartOfAccount::where('account_type', 'income')
|
||||
->where('is_active', true)
|
||||
->orderBy('account_code')
|
||||
->get();
|
||||
|
||||
// 取得會員列表(可選關聯)
|
||||
$members = Member::orderBy('full_name')->get();
|
||||
|
||||
// 預選會員
|
||||
$selectedMember = null;
|
||||
if ($request->filled('member_id')) {
|
||||
$selectedMember = Member::find($request->member_id);
|
||||
}
|
||||
|
||||
return view('admin.incomes.create', [
|
||||
'chartOfAccounts' => $chartOfAccounts,
|
||||
'members' => $members,
|
||||
'selectedMember' => $selectedMember,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 儲存收入(出納記錄)
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'title' => ['required', 'string', 'max:255'],
|
||||
'income_date' => ['required', 'date'],
|
||||
'amount' => ['required', 'numeric', 'min:0.01'],
|
||||
'income_type' => ['required', 'in:membership_fee,entrance_fee,donation,activity,grant,interest,other'],
|
||||
'chart_of_account_id' => ['required', 'exists:chart_of_accounts,id'],
|
||||
'payment_method' => ['required', 'in:cash,bank_transfer,check'],
|
||||
'bank_account' => ['nullable', 'string', 'max:255'],
|
||||
'payer_name' => ['nullable', 'string', 'max:255'],
|
||||
'member_id' => ['nullable', 'exists:members,id'],
|
||||
'receipt_number' => ['nullable', 'string', 'max:255'],
|
||||
'transaction_reference' => ['nullable', 'string', 'max:255'],
|
||||
'description' => ['nullable', 'string'],
|
||||
'notes' => ['nullable', 'string'],
|
||||
'attachment' => ['nullable', 'file', 'max:10240'],
|
||||
]);
|
||||
|
||||
// 處理附件上傳
|
||||
$attachmentPath = null;
|
||||
if ($request->hasFile('attachment')) {
|
||||
$attachmentPath = $request->file('attachment')->store('incomes', 'local');
|
||||
}
|
||||
|
||||
$income = Income::create([
|
||||
'title' => $validated['title'],
|
||||
'income_date' => $validated['income_date'],
|
||||
'amount' => $validated['amount'],
|
||||
'income_type' => $validated['income_type'],
|
||||
'chart_of_account_id' => $validated['chart_of_account_id'],
|
||||
'payment_method' => $validated['payment_method'],
|
||||
'bank_account' => $validated['bank_account'] ?? null,
|
||||
'payer_name' => $validated['payer_name'] ?? null,
|
||||
'member_id' => $validated['member_id'] ?? null,
|
||||
'receipt_number' => $validated['receipt_number'] ?? null,
|
||||
'transaction_reference' => $validated['transaction_reference'] ?? null,
|
||||
'description' => $validated['description'] ?? null,
|
||||
'notes' => $validated['notes'] ?? null,
|
||||
'attachment_path' => $attachmentPath,
|
||||
'status' => Income::STATUS_PENDING,
|
||||
'recorded_by_cashier_id' => $request->user()->id,
|
||||
'recorded_at' => now(),
|
||||
]);
|
||||
|
||||
AuditLogger::log('income.created', $income, $validated);
|
||||
|
||||
return redirect()
|
||||
->route('admin.incomes.show', $income)
|
||||
->with('status', '收入記錄已建立,等待會計確認。收入編號:' . $income->income_number);
|
||||
}
|
||||
|
||||
/**
|
||||
* 收入詳情
|
||||
*/
|
||||
public function show(Income $income)
|
||||
{
|
||||
$income->load([
|
||||
'chartOfAccount',
|
||||
'member',
|
||||
'recordedByCashier',
|
||||
'confirmedByAccountant',
|
||||
'cashierLedgerEntry',
|
||||
'accountingEntries.chartOfAccount',
|
||||
]);
|
||||
|
||||
return view('admin.incomes.show', [
|
||||
'income' => $income,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 會計確認收入
|
||||
*/
|
||||
public function confirm(Request $request, Income $income)
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
// 檢查權限
|
||||
$canConfirm = $user->hasRole('admin') ||
|
||||
$user->hasRole('finance_accountant');
|
||||
|
||||
if (!$canConfirm) {
|
||||
abort(403, '您無權確認此收入。');
|
||||
}
|
||||
|
||||
if (!$income->canBeConfirmed()) {
|
||||
return redirect()
|
||||
->route('admin.incomes.show', $income)
|
||||
->with('error', '此收入無法確認。');
|
||||
}
|
||||
|
||||
try {
|
||||
$income->confirmByAccountant($user);
|
||||
|
||||
AuditLogger::log('income.confirmed', $income, [
|
||||
'confirmed_by' => $user->name,
|
||||
]);
|
||||
|
||||
return redirect()
|
||||
->route('admin.incomes.show', $income)
|
||||
->with('status', '收入已確認。已自動產生出納日記帳和會計分錄。');
|
||||
} catch (\Exception $e) {
|
||||
return redirect()
|
||||
->route('admin.incomes.show', $income)
|
||||
->with('error', '確認失敗:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消收入
|
||||
*/
|
||||
public function cancel(Request $request, Income $income)
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
// 檢查權限
|
||||
$canCancel = $user->hasRole('admin') ||
|
||||
$user->hasRole('finance_accountant');
|
||||
|
||||
if (!$canCancel) {
|
||||
abort(403, '您無權取消此收入。');
|
||||
}
|
||||
|
||||
if (!$income->canBeCancelled()) {
|
||||
return redirect()
|
||||
->route('admin.incomes.show', $income)
|
||||
->with('error', '此收入無法取消。');
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'cancel_reason' => ['nullable', 'string', 'max:1000'],
|
||||
]);
|
||||
|
||||
$income->cancel();
|
||||
|
||||
AuditLogger::log('income.cancelled', $income, [
|
||||
'cancelled_by' => $user->name,
|
||||
'reason' => $validated['cancel_reason'] ?? null,
|
||||
]);
|
||||
|
||||
return redirect()
|
||||
->route('admin.incomes.show', $income)
|
||||
->with('status', '收入已取消。');
|
||||
}
|
||||
|
||||
/**
|
||||
* 收入統計
|
||||
*/
|
||||
public function statistics(Request $request)
|
||||
{
|
||||
$year = $request->input('year', date('Y'));
|
||||
$month = $request->input('month');
|
||||
|
||||
// 依收入類型統計
|
||||
$byTypeQuery = Income::confirmed()
|
||||
->whereYear('income_date', $year);
|
||||
|
||||
if ($month) {
|
||||
$byTypeQuery->whereMonth('income_date', $month);
|
||||
}
|
||||
|
||||
$byType = $byTypeQuery
|
||||
->selectRaw('income_type, SUM(amount) as total_amount, COUNT(*) as count')
|
||||
->groupBy('income_type')
|
||||
->get();
|
||||
|
||||
// 依月份統計
|
||||
$byMonth = Income::confirmed()
|
||||
->whereYear('income_date', $year)
|
||||
->selectRaw("CAST(strftime('%m', income_date) AS INTEGER) as month, SUM(amount) as total_amount, COUNT(*) as count")
|
||||
->groupBy('month')
|
||||
->orderBy('month')
|
||||
->get();
|
||||
|
||||
// 依會計科目統計
|
||||
$byAccountQuery = Income::confirmed()
|
||||
->whereYear('income_date', $year);
|
||||
|
||||
if ($month) {
|
||||
$byAccountQuery->whereMonth('income_date', $month);
|
||||
}
|
||||
|
||||
$byAccountResults = $byAccountQuery
|
||||
->selectRaw('chart_of_account_id, SUM(amount) as total_amount, COUNT(*) as count')
|
||||
->groupBy('chart_of_account_id')
|
||||
->get();
|
||||
|
||||
// 手動載入會計科目關聯
|
||||
$accountIds = $byAccountResults->pluck('chart_of_account_id')->filter()->unique();
|
||||
$accounts = \App\Models\ChartOfAccount::whereIn('id', $accountIds)->get()->keyBy('id');
|
||||
|
||||
$byAccount = $byAccountResults->map(function ($item) use ($accounts) {
|
||||
$item->chartOfAccount = $accounts->get($item->chart_of_account_id);
|
||||
return $item;
|
||||
});
|
||||
|
||||
// 總計
|
||||
$totalQuery = Income::confirmed()
|
||||
->whereYear('income_date', $year);
|
||||
|
||||
if ($month) {
|
||||
$totalQuery->whereMonth('income_date', $month);
|
||||
}
|
||||
|
||||
$total = [
|
||||
'amount' => $totalQuery->sum('amount'),
|
||||
'count' => $totalQuery->count(),
|
||||
];
|
||||
|
||||
return view('admin.incomes.statistics', [
|
||||
'year' => $year,
|
||||
'month' => $month,
|
||||
'byType' => $byType,
|
||||
'byMonth' => $byMonth,
|
||||
'byAccount' => $byAccount,
|
||||
'total' => $total,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 匯出收入
|
||||
*/
|
||||
public function export(Request $request)
|
||||
{
|
||||
$query = Income::confirmed()
|
||||
->with(['chartOfAccount', 'member', 'recordedByCashier'])
|
||||
->orderByDesc('income_date');
|
||||
|
||||
// 篩選日期範圍
|
||||
if ($request->filled('date_from')) {
|
||||
$query->where('income_date', '>=', $request->date_from);
|
||||
}
|
||||
if ($request->filled('date_to')) {
|
||||
$query->where('income_date', '<=', $request->date_to);
|
||||
}
|
||||
|
||||
$incomes = $query->get();
|
||||
|
||||
// 產生 CSV
|
||||
$filename = 'incomes_' . date('Y-m-d_His') . '.csv';
|
||||
|
||||
$headers = [
|
||||
'Content-Type' => 'text/csv; charset=UTF-8',
|
||||
'Content-Disposition' => "attachment; filename=\"{$filename}\"",
|
||||
];
|
||||
|
||||
$callback = function () use ($incomes) {
|
||||
$file = fopen('php://output', 'w');
|
||||
|
||||
// BOM for Excel UTF-8
|
||||
fprintf($file, chr(0xEF) . chr(0xBB) . chr(0xBF));
|
||||
|
||||
// Header
|
||||
fputcsv($file, [
|
||||
'收入編號',
|
||||
'日期',
|
||||
'標題',
|
||||
'金額',
|
||||
'收入類型',
|
||||
'會計科目',
|
||||
'付款方式',
|
||||
'付款人',
|
||||
'會員',
|
||||
'收據編號',
|
||||
'狀態',
|
||||
'記錄人',
|
||||
'確認人',
|
||||
]);
|
||||
|
||||
foreach ($incomes as $income) {
|
||||
fputcsv($file, [
|
||||
$income->income_number,
|
||||
$income->income_date->format('Y-m-d'),
|
||||
$income->title,
|
||||
$income->amount,
|
||||
$income->getIncomeTypeText(),
|
||||
$income->chartOfAccount->account_name_zh ?? '',
|
||||
$income->getPaymentMethodText(),
|
||||
$income->payer_name,
|
||||
$income->member->full_name ?? '',
|
||||
$income->receipt_number,
|
||||
$income->getStatusText(),
|
||||
$income->recordedByCashier->name ?? '',
|
||||
$income->confirmedByAccountant->name ?? '',
|
||||
]);
|
||||
}
|
||||
|
||||
fclose($file);
|
||||
};
|
||||
|
||||
return response()->stream($callback, 200, $headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下載附件
|
||||
*/
|
||||
public function download(Income $income)
|
||||
{
|
||||
if (!$income->attachment_path) {
|
||||
abort(404, '找不到附件。');
|
||||
}
|
||||
|
||||
$path = storage_path('app/' . $income->attachment_path);
|
||||
|
||||
if (!file_exists($path)) {
|
||||
abort(404, '附件檔案不存在。');
|
||||
}
|
||||
|
||||
return response()->download($path);
|
||||
}
|
||||
}
|
||||
@@ -196,7 +196,7 @@ class IssueController extends Controller
|
||||
|
||||
public function edit(Issue $issue)
|
||||
{
|
||||
if ($issue->isClosed() && !Auth::user()->is_admin) {
|
||||
if ($issue->isClosed() && !Auth::user()->hasRole('admin')) {
|
||||
return redirect()->route('admin.issues.show', $issue)
|
||||
->with('error', __('Cannot edit closed issues.'));
|
||||
}
|
||||
@@ -211,7 +211,7 @@ class IssueController extends Controller
|
||||
|
||||
public function update(Request $request, Issue $issue)
|
||||
{
|
||||
if ($issue->isClosed() && !Auth::user()->is_admin) {
|
||||
if ($issue->isClosed() && !Auth::user()->hasRole('admin')) {
|
||||
return redirect()->route('admin.issues.show', $issue)
|
||||
->with('error', __('Cannot edit closed issues.'));
|
||||
}
|
||||
@@ -262,7 +262,7 @@ class IssueController extends Controller
|
||||
|
||||
public function destroy(Issue $issue)
|
||||
{
|
||||
if (!Auth::user()->is_admin) {
|
||||
if (!Auth::user()->hasRole('admin')) {
|
||||
abort(403, 'Only administrators can delete issues.');
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ class IssueLabelController extends Controller
|
||||
|
||||
public function destroy(IssueLabel $issueLabel)
|
||||
{
|
||||
if (!Auth::user()->is_admin) {
|
||||
if (!Auth::user()->hasRole('admin')) {
|
||||
abort(403, 'Only administrators can delete labels.');
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Mail\PaymentSubmittedMail;
|
||||
use App\Models\Member;
|
||||
use App\Models\MembershipPayment;
|
||||
use App\Models\User;
|
||||
use App\Services\MembershipFeeCalculator;
|
||||
use App\Support\AuditLogger;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
@@ -14,6 +15,13 @@ use Illuminate\Validation\Rule;
|
||||
|
||||
class MemberPaymentController extends Controller
|
||||
{
|
||||
protected MembershipFeeCalculator $feeCalculator;
|
||||
|
||||
public function __construct(MembershipFeeCalculator $feeCalculator)
|
||||
{
|
||||
$this->feeCalculator = $feeCalculator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show payment submission form
|
||||
*/
|
||||
@@ -32,7 +40,10 @@ class MemberPaymentController extends Controller
|
||||
->with('error', __('You cannot submit payment at this time. You may already have a pending payment or your membership is already active.'));
|
||||
}
|
||||
|
||||
return view('member.submit-payment', compact('member'));
|
||||
// Calculate fee details
|
||||
$feeDetails = $this->feeCalculator->calculateNextFee($member);
|
||||
|
||||
return view('member.submit-payment', compact('member', 'feeDetails'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,8 +58,11 @@ class MemberPaymentController extends Controller
|
||||
->with('error', __('You cannot submit payment at this time.'));
|
||||
}
|
||||
|
||||
// Calculate fee details
|
||||
$feeDetails = $this->feeCalculator->calculateNextFee($member);
|
||||
|
||||
$validated = $request->validate([
|
||||
'amount' => ['required', 'numeric', 'min:0'],
|
||||
'amount' => ['required', 'numeric', 'min:' . $feeDetails['final_amount']],
|
||||
'paid_at' => ['required', 'date', 'before_or_equal:today'],
|
||||
'payment_method' => ['required', Rule::in([
|
||||
MembershipPayment::METHOD_BANK_TRANSFER,
|
||||
@@ -65,10 +79,15 @@ class MemberPaymentController extends Controller
|
||||
$receiptFile = $request->file('receipt');
|
||||
$receiptPath = $receiptFile->store('payment-receipts', 'private');
|
||||
|
||||
// Create payment record
|
||||
// Create payment record with fee details
|
||||
$payment = MembershipPayment::create([
|
||||
'member_id' => $member->id,
|
||||
'fee_type' => $feeDetails['fee_type'],
|
||||
'amount' => $validated['amount'],
|
||||
'base_amount' => $feeDetails['base_amount'],
|
||||
'discount_amount' => $feeDetails['discount_amount'],
|
||||
'final_amount' => $feeDetails['final_amount'],
|
||||
'disability_discount' => $feeDetails['disability_discount'],
|
||||
'paid_at' => $validated['paid_at'],
|
||||
'payment_method' => $validated['payment_method'],
|
||||
'reference' => $validated['reference'] ?? null,
|
||||
|
||||
@@ -59,14 +59,14 @@ class PaymentOrderController extends Controller
|
||||
if (!$financeDocument->canCreatePaymentOrder()) {
|
||||
return redirect()
|
||||
->route('admin.finance.show', $financeDocument)
|
||||
->with('error', '此財務申請單尚未完成審核流程,無法製作付款單。');
|
||||
->with('error', '此報銷申請單尚未完成審核流程,無法製作付款單。');
|
||||
}
|
||||
|
||||
// Check if payment order already exists
|
||||
if ($financeDocument->paymentOrder !== null) {
|
||||
return redirect()
|
||||
->route('admin.payment-orders.show', $financeDocument->paymentOrder)
|
||||
->with('error', '此財務申請單已有付款單。');
|
||||
->with('error', '此報銷申請單已有付款單。');
|
||||
}
|
||||
|
||||
$financeDocument->load(['member', 'submittedBy']);
|
||||
@@ -98,7 +98,7 @@ class PaymentOrderController extends Controller
|
||||
}
|
||||
return redirect()
|
||||
->route('admin.finance.show', $financeDocument)
|
||||
->with('error', '此財務申請單尚未完成審核流程,無法製作付款單。');
|
||||
->with('error', '此報銷申請單尚未完成審核流程,無法製作付款單。');
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
|
||||
@@ -88,8 +88,20 @@ class PaymentVerificationController extends Controller
|
||||
|
||||
$validated = $request->validate([
|
||||
'notes' => ['nullable', 'string', 'max:1000'],
|
||||
'disability_action' => ['nullable', 'in:approve,reject'],
|
||||
'disability_rejection_reason' => ['required_if:disability_action,reject', 'nullable', 'string', 'max:500'],
|
||||
]);
|
||||
|
||||
// Handle disability certificate verification if applicable
|
||||
$member = $payment->member;
|
||||
if ($member && $member->hasDisabilityCertificate() && $member->isDisabilityPending()) {
|
||||
if ($validated['disability_action'] === 'approve') {
|
||||
$member->approveDisabilityCertificate(Auth::user());
|
||||
} elseif ($validated['disability_action'] === 'reject') {
|
||||
$member->rejectDisabilityCertificate(Auth::user(), $validated['disability_rejection_reason']);
|
||||
}
|
||||
}
|
||||
|
||||
$payment->update([
|
||||
'status' => MembershipPayment::STATUS_APPROVED_CASHIER,
|
||||
'verified_by_cashier_id' => Auth::id(),
|
||||
|
||||
@@ -97,4 +97,57 @@ class ProfileController extends Controller
|
||||
|
||||
return Redirect::to('/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload disability certificate.
|
||||
*/
|
||||
public function uploadDisabilityCertificate(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'disability_certificate' => 'required|file|mimes:jpg,jpeg,png,pdf|max:10240',
|
||||
]);
|
||||
|
||||
$member = $request->user()->member;
|
||||
|
||||
if (!$member) {
|
||||
return Redirect::route('profile.edit')->with('error', '請先建立會員資料');
|
||||
}
|
||||
|
||||
// Delete old certificate if exists
|
||||
if ($member->disability_certificate_path) {
|
||||
Storage::disk('private')->delete($member->disability_certificate_path);
|
||||
}
|
||||
|
||||
// Upload new certificate
|
||||
$path = $request->file('disability_certificate')->store('disability-certificates', 'private');
|
||||
|
||||
// Update member record
|
||||
$member->update([
|
||||
'disability_certificate_path' => $path,
|
||||
'disability_certificate_status' => Member::DISABILITY_STATUS_PENDING,
|
||||
'disability_verified_by' => null,
|
||||
'disability_verified_at' => null,
|
||||
'disability_rejection_reason' => null,
|
||||
]);
|
||||
|
||||
return Redirect::route('profile.edit')->with('status', 'disability-certificate-uploaded');
|
||||
}
|
||||
|
||||
/**
|
||||
* View disability certificate.
|
||||
*/
|
||||
public function viewDisabilityCertificate(Request $request)
|
||||
{
|
||||
$member = $request->user()->member;
|
||||
|
||||
if (!$member || !$member->disability_certificate_path) {
|
||||
abort(404, '找不到身心障礙手冊');
|
||||
}
|
||||
|
||||
if (!Storage::disk('private')->exists($member->disability_certificate_path)) {
|
||||
abort(404, '檔案不存在');
|
||||
}
|
||||
|
||||
return Storage::disk('private')->response($member->disability_certificate_path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ class PublicDocumentController extends Controller
|
||||
if (!$user) {
|
||||
// Only public documents for guests
|
||||
$query->where('access_level', 'public');
|
||||
} elseif (!$user->is_admin && !$user->hasRole('admin')) {
|
||||
} elseif (!$user->hasRole('admin')) {
|
||||
// Members can see public + members-only
|
||||
$query->whereIn('access_level', ['public', 'members']);
|
||||
}
|
||||
@@ -49,7 +49,7 @@ class PublicDocumentController extends Controller
|
||||
'activeDocuments' => function($query) use ($user) {
|
||||
if (!$user) {
|
||||
$query->where('access_level', 'public');
|
||||
} elseif (!$user->is_admin && !$user->hasRole('admin')) {
|
||||
} elseif (!$user->hasRole('admin')) {
|
||||
$query->whereIn('access_level', ['public', 'members']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ class EnsureUserIsAdmin
|
||||
}
|
||||
|
||||
// Allow access for admins or any user with explicit permissions (e.g. finance/cashier roles)
|
||||
if (! $user->is_admin && ! $user->hasRole('admin') && $user->getAllPermissions()->isEmpty()) {
|
||||
if (! $user->hasRole('admin') && $user->getAllPermissions()->isEmpty()) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
|
||||
101
app/Models/AccountingEntry.php
Normal file
101
app/Models/AccountingEntry.php
Normal file
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class AccountingEntry extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
const ENTRY_TYPE_DEBIT = 'debit';
|
||||
const ENTRY_TYPE_CREDIT = 'credit';
|
||||
|
||||
protected $fillable = [
|
||||
'finance_document_id',
|
||||
'income_id',
|
||||
'chart_of_account_id',
|
||||
'entry_type',
|
||||
'amount',
|
||||
'entry_date',
|
||||
'description',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'entry_date' => 'date',
|
||||
'amount' => 'decimal:2',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the finance document that owns this entry
|
||||
*/
|
||||
public function financeDocument()
|
||||
{
|
||||
return $this->belongsTo(FinanceDocument::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the income that owns this entry
|
||||
*/
|
||||
public function income()
|
||||
{
|
||||
return $this->belongsTo(Income::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the chart of account for this entry
|
||||
*/
|
||||
public function chartOfAccount()
|
||||
{
|
||||
return $this->belongsTo(ChartOfAccount::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this is a debit entry
|
||||
*/
|
||||
public function isDebit(): bool
|
||||
{
|
||||
return $this->entry_type === self::ENTRY_TYPE_DEBIT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this is a credit entry
|
||||
*/
|
||||
public function isCredit(): bool
|
||||
{
|
||||
return $this->entry_type === self::ENTRY_TYPE_CREDIT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope to filter debit entries
|
||||
*/
|
||||
public function scopeDebits($query)
|
||||
{
|
||||
return $query->where('entry_type', self::ENTRY_TYPE_DEBIT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope to filter credit entries
|
||||
*/
|
||||
public function scopeCredits($query)
|
||||
{
|
||||
return $query->where('entry_type', self::ENTRY_TYPE_CREDIT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope to filter by account
|
||||
*/
|
||||
public function scopeForAccount($query, $accountId)
|
||||
{
|
||||
return $query->where('chart_of_account_id', $accountId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope to filter by date range
|
||||
*/
|
||||
public function scopeDateRange($query, $startDate, $endDate)
|
||||
{
|
||||
return $query->whereBetween('entry_date', [$startDate, $endDate]);
|
||||
}
|
||||
}
|
||||
427
app/Models/Announcement.php
Normal file
427
app/Models/Announcement.php
Normal file
@@ -0,0 +1,427 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class Announcement extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
// ==================== Constants ====================
|
||||
|
||||
const STATUS_DRAFT = 'draft';
|
||||
const STATUS_PUBLISHED = 'published';
|
||||
const STATUS_ARCHIVED = 'archived';
|
||||
|
||||
const ACCESS_LEVEL_PUBLIC = 'public';
|
||||
const ACCESS_LEVEL_MEMBERS = 'members';
|
||||
const ACCESS_LEVEL_BOARD = 'board';
|
||||
const ACCESS_LEVEL_ADMIN = 'admin';
|
||||
|
||||
// ==================== Configuration ====================
|
||||
|
||||
protected $fillable = [
|
||||
'title',
|
||||
'content',
|
||||
'status',
|
||||
'is_pinned',
|
||||
'display_order',
|
||||
'access_level',
|
||||
'published_at',
|
||||
'expires_at',
|
||||
'archived_at',
|
||||
'view_count',
|
||||
'created_by_user_id',
|
||||
'last_updated_by_user_id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_pinned' => 'boolean',
|
||||
'display_order' => 'integer',
|
||||
'view_count' => 'integer',
|
||||
'published_at' => 'datetime',
|
||||
'expires_at' => 'datetime',
|
||||
'archived_at' => 'datetime',
|
||||
];
|
||||
|
||||
// ==================== Relationships ====================
|
||||
|
||||
/**
|
||||
* Get the user who created this announcement
|
||||
*/
|
||||
public function creator()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by_user_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user who last updated this announcement
|
||||
*/
|
||||
public function lastUpdatedBy()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'last_updated_by_user_id');
|
||||
}
|
||||
|
||||
// ==================== Status Check Methods ====================
|
||||
|
||||
/**
|
||||
* Check if announcement is draft
|
||||
*/
|
||||
public function isDraft(): bool
|
||||
{
|
||||
return $this->status === self::STATUS_DRAFT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if announcement is published
|
||||
*/
|
||||
public function isPublished(): bool
|
||||
{
|
||||
return $this->status === self::STATUS_PUBLISHED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if announcement is archived
|
||||
*/
|
||||
public function isArchived(): bool
|
||||
{
|
||||
return $this->status === self::STATUS_ARCHIVED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if announcement is pinned
|
||||
*/
|
||||
public function isPinned(): bool
|
||||
{
|
||||
return $this->is_pinned;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if announcement is expired
|
||||
*/
|
||||
public function isExpired(): bool
|
||||
{
|
||||
if (!$this->expires_at) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->expires_at->isPast();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if announcement is scheduled (published_at is in the future)
|
||||
*/
|
||||
public function isScheduled(): bool
|
||||
{
|
||||
if (!$this->published_at) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->published_at->isFuture();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if announcement is currently active
|
||||
*/
|
||||
public function isActive(): bool
|
||||
{
|
||||
return $this->isPublished()
|
||||
&& !$this->isExpired()
|
||||
&& (!$this->published_at || $this->published_at->isPast());
|
||||
}
|
||||
|
||||
// ==================== Access Control Methods ====================
|
||||
|
||||
/**
|
||||
* Check if a user can view this announcement
|
||||
*/
|
||||
public function canBeViewedBy(?User $user): bool
|
||||
{
|
||||
// Draft announcements - only creator and admins can view
|
||||
if ($this->isDraft()) {
|
||||
if (!$user) {
|
||||
return false;
|
||||
}
|
||||
return $user->id === $this->created_by_user_id
|
||||
|| $user->hasRole('admin')
|
||||
|| $user->can('manage_all_announcements');
|
||||
}
|
||||
|
||||
// Archived announcements - only admins can view
|
||||
if ($this->isArchived()) {
|
||||
if (!$user) {
|
||||
return false;
|
||||
}
|
||||
return $user->hasRole('admin') || $user->can('manage_all_announcements');
|
||||
}
|
||||
|
||||
// Expired announcements - hidden from regular users
|
||||
if ($this->isExpired()) {
|
||||
if (!$user) {
|
||||
return false;
|
||||
}
|
||||
return $user->hasRole('admin') || $user->can('manage_all_announcements');
|
||||
}
|
||||
|
||||
// Scheduled announcements - not yet visible
|
||||
if ($this->isScheduled()) {
|
||||
if (!$user) {
|
||||
return false;
|
||||
}
|
||||
return $user->id === $this->created_by_user_id
|
||||
|| $user->hasRole('admin')
|
||||
|| $user->can('manage_all_announcements');
|
||||
}
|
||||
|
||||
// Check access level for published announcements
|
||||
if ($this->access_level === self::ACCESS_LEVEL_PUBLIC) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!$user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($user->hasRole('admin')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->access_level === self::ACCESS_LEVEL_MEMBERS) {
|
||||
return $user->member && $user->member->hasPaidMembership();
|
||||
}
|
||||
|
||||
if ($this->access_level === self::ACCESS_LEVEL_BOARD) {
|
||||
return $user->hasRole(['admin', 'finance_chair', 'finance_board_member']);
|
||||
}
|
||||
|
||||
if ($this->access_level === self::ACCESS_LEVEL_ADMIN) {
|
||||
return $user->hasRole('admin');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a user can edit this announcement
|
||||
*/
|
||||
public function canBeEditedBy(User $user): bool
|
||||
{
|
||||
// Admin and users with manage_all_announcements can edit all
|
||||
if ($user->hasRole('admin') || $user->can('manage_all_announcements')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// User must have edit_announcements permission
|
||||
if (!$user->can('edit_announcements')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Can only edit own announcements
|
||||
return $user->id === $this->created_by_user_id;
|
||||
}
|
||||
|
||||
// ==================== Query Scopes ====================
|
||||
|
||||
/**
|
||||
* Scope to only published announcements
|
||||
*/
|
||||
public function scopePublished(Builder $query): Builder
|
||||
{
|
||||
return $query->where('status', self::STATUS_PUBLISHED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope to only draft announcements
|
||||
*/
|
||||
public function scopeDraft(Builder $query): Builder
|
||||
{
|
||||
return $query->where('status', self::STATUS_DRAFT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope to only archived announcements
|
||||
*/
|
||||
public function scopeArchived(Builder $query): Builder
|
||||
{
|
||||
return $query->where('status', self::STATUS_ARCHIVED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope to only active announcements (published, not expired, not scheduled)
|
||||
*/
|
||||
public function scopeActive(Builder $query): Builder
|
||||
{
|
||||
return $query->where('status', self::STATUS_PUBLISHED)
|
||||
->where(function ($q) {
|
||||
$q->whereNull('published_at')
|
||||
->orWhere('published_at', '<=', now());
|
||||
})
|
||||
->where(function ($q) {
|
||||
$q->whereNull('expires_at')
|
||||
->orWhere('expires_at', '>', now());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope to only pinned announcements
|
||||
*/
|
||||
public function scopePinned(Builder $query): Builder
|
||||
{
|
||||
return $query->where('is_pinned', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope to filter by access level
|
||||
*/
|
||||
public function scopeForAccessLevel(Builder $query, User $user): Builder
|
||||
{
|
||||
if ($user->hasRole('admin')) {
|
||||
return $query;
|
||||
}
|
||||
|
||||
$accessLevels = [self::ACCESS_LEVEL_PUBLIC];
|
||||
|
||||
if ($user->member && $user->member->hasPaidMembership()) {
|
||||
$accessLevels[] = self::ACCESS_LEVEL_MEMBERS;
|
||||
}
|
||||
|
||||
if ($user->hasRole(['finance_chair', 'finance_board_member'])) {
|
||||
$accessLevels[] = self::ACCESS_LEVEL_BOARD;
|
||||
}
|
||||
|
||||
return $query->whereIn('access_level', $accessLevels);
|
||||
}
|
||||
|
||||
// ==================== Helper Methods ====================
|
||||
|
||||
/**
|
||||
* Publish this announcement
|
||||
*/
|
||||
public function publish(?User $user = null): void
|
||||
{
|
||||
$updates = [
|
||||
'status' => self::STATUS_PUBLISHED,
|
||||
];
|
||||
|
||||
if (!$this->published_at) {
|
||||
$updates['published_at'] = now();
|
||||
}
|
||||
|
||||
if ($user) {
|
||||
$updates['last_updated_by_user_id'] = $user->id;
|
||||
}
|
||||
|
||||
$this->update($updates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Archive this announcement
|
||||
*/
|
||||
public function archive(?User $user = null): void
|
||||
{
|
||||
$updates = [
|
||||
'status' => self::STATUS_ARCHIVED,
|
||||
'archived_at' => now(),
|
||||
];
|
||||
|
||||
if ($user) {
|
||||
$updates['last_updated_by_user_id'] = $user->id;
|
||||
}
|
||||
|
||||
$this->update($updates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pin this announcement
|
||||
*/
|
||||
public function pin(?int $order = null, ?User $user = null): void
|
||||
{
|
||||
$updates = [
|
||||
'is_pinned' => true,
|
||||
'display_order' => $order ?? 0,
|
||||
];
|
||||
|
||||
if ($user) {
|
||||
$updates['last_updated_by_user_id'] = $user->id;
|
||||
}
|
||||
|
||||
$this->update($updates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpin this announcement
|
||||
*/
|
||||
public function unpin(?User $user = null): void
|
||||
{
|
||||
$updates = [
|
||||
'is_pinned' => false,
|
||||
'display_order' => 0,
|
||||
];
|
||||
|
||||
if ($user) {
|
||||
$updates['last_updated_by_user_id'] = $user->id;
|
||||
}
|
||||
|
||||
$this->update($updates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment view count
|
||||
*/
|
||||
public function incrementViewCount(): void
|
||||
{
|
||||
$this->increment('view_count');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the access level label in Chinese
|
||||
*/
|
||||
public function getAccessLevelLabel(): string
|
||||
{
|
||||
return match($this->access_level) {
|
||||
self::ACCESS_LEVEL_PUBLIC => '公開',
|
||||
self::ACCESS_LEVEL_MEMBERS => '會員',
|
||||
self::ACCESS_LEVEL_BOARD => '理事會',
|
||||
self::ACCESS_LEVEL_ADMIN => '管理員',
|
||||
default => '未知',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get status label in Chinese
|
||||
*/
|
||||
public function getStatusLabel(): string
|
||||
{
|
||||
return match($this->status) {
|
||||
self::STATUS_DRAFT => '草稿',
|
||||
self::STATUS_PUBLISHED => '已發布',
|
||||
self::STATUS_ARCHIVED => '已歸檔',
|
||||
default => '未知',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get status badge color
|
||||
*/
|
||||
public function getStatusBadgeColor(): string
|
||||
{
|
||||
return match($this->status) {
|
||||
self::STATUS_DRAFT => 'gray',
|
||||
self::STATUS_PUBLISHED => 'green',
|
||||
self::STATUS_ARCHIVED => 'yellow',
|
||||
default => 'gray',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get content excerpt (first 150 characters)
|
||||
*/
|
||||
public function getExcerpt(int $length = 150): string
|
||||
{
|
||||
return \Illuminate\Support\Str::limit($this->content, $length);
|
||||
}
|
||||
}
|
||||
31
app/Models/BoardMeeting.php
Normal file
31
app/Models/BoardMeeting.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class BoardMeeting extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'meeting_date',
|
||||
'title',
|
||||
'notes',
|
||||
'status',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'meeting_date' => 'date',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the finance documents approved by this board meeting.
|
||||
*/
|
||||
public function approvedFinanceDocuments(): HasMany
|
||||
{
|
||||
return $this->hasMany(FinanceDocument::class, 'approved_by_board_meeting_id');
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,7 @@ class CashierLedgerEntry extends Model
|
||||
const PAYMENT_METHOD_CASH = 'cash';
|
||||
|
||||
/**
|
||||
* 關聯到財務申請單
|
||||
* 關聯到報銷申請單
|
||||
*/
|
||||
public function financeDocument(): BelongsTo
|
||||
{
|
||||
|
||||
@@ -178,7 +178,7 @@ class Document extends Model
|
||||
'original_filename' => $originalFilename,
|
||||
'mime_type' => $mimeType,
|
||||
'file_size' => $fileSize,
|
||||
'file_hash' => hash_file('sha256', storage_path('app/' . $filePath)),
|
||||
'file_hash' => hash_file('sha256', \Illuminate\Support\Facades\Storage::disk('private')->path($filePath)),
|
||||
'uploaded_by_user_id' => $uploadedBy->id,
|
||||
'uploaded_at' => now(),
|
||||
]);
|
||||
@@ -265,24 +265,44 @@ class Document extends Model
|
||||
*/
|
||||
public function canBeViewedBy(?User $user): bool
|
||||
{
|
||||
// 公開文件:任何人可看
|
||||
if ($this->isPublic()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 非公開文件需要登入
|
||||
if (!$user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($user->is_admin || $user->hasRole('admin')) {
|
||||
// 有文件管理權限者可看所有文件
|
||||
if ($user->can('manage_documents')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 會員等級:已繳費會員可看
|
||||
if ($this->access_level === 'members') {
|
||||
return $user->member && $user->member->hasPaidMembership();
|
||||
}
|
||||
|
||||
// 管理員等級:有任何管理權限者可看
|
||||
if ($this->access_level === 'admin') {
|
||||
return $user->hasAnyPermission([
|
||||
'manage_documents',
|
||||
'manage_members',
|
||||
'manage_finance',
|
||||
'manage_system_settings',
|
||||
]);
|
||||
}
|
||||
|
||||
// 理事會等級:有理事會相關權限者可看
|
||||
if ($this->access_level === 'board') {
|
||||
return $user->hasRole(['admin', 'chair', 'board']);
|
||||
return $user->hasAnyPermission([
|
||||
'manage_documents',
|
||||
'approve_finance_documents',
|
||||
'verify_payments_chair',
|
||||
'activate_memberships',
|
||||
]);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -11,18 +11,26 @@ class FinanceDocument extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
// Status constants
|
||||
public const STATUS_PENDING = 'pending';
|
||||
// Status constants (審核階段)
|
||||
public const STATUS_PENDING = 'pending'; // 待審核
|
||||
public const STATUS_APPROVED_SECRETARY = 'approved_secretary'; // 秘書長已核准
|
||||
public const STATUS_APPROVED_CHAIR = 'approved_chair'; // 理事長已核准
|
||||
public const STATUS_APPROVED_BOARD = 'approved_board'; // 董理事會已核准
|
||||
public const STATUS_REJECTED = 'rejected'; // 已駁回
|
||||
|
||||
// Legacy status constants (保留向後相容)
|
||||
public const STATUS_APPROVED_CASHIER = 'approved_cashier';
|
||||
public const STATUS_APPROVED_ACCOUNTANT = 'approved_accountant';
|
||||
public const STATUS_APPROVED_CHAIR = 'approved_chair';
|
||||
public const STATUS_REJECTED = 'rejected';
|
||||
|
||||
// Request type constants
|
||||
public const REQUEST_TYPE_EXPENSE_REIMBURSEMENT = 'expense_reimbursement';
|
||||
public const REQUEST_TYPE_ADVANCE_PAYMENT = 'advance_payment';
|
||||
public const REQUEST_TYPE_PURCHASE_REQUEST = 'purchase_request';
|
||||
public const REQUEST_TYPE_PETTY_CASH = 'petty_cash';
|
||||
// Disbursement status constants (出帳階段)
|
||||
public const DISBURSEMENT_PENDING = 'pending'; // 待出帳
|
||||
public const DISBURSEMENT_REQUESTER_CONFIRMED = 'requester_confirmed'; // 申請人已確認
|
||||
public const DISBURSEMENT_CASHIER_CONFIRMED = 'cashier_confirmed'; // 出納已確認
|
||||
public const DISBURSEMENT_COMPLETED = 'completed'; // 已出帳
|
||||
|
||||
// Recording status constants (入帳階段)
|
||||
public const RECORDING_PENDING = 'pending'; // 待入帳
|
||||
public const RECORDING_COMPLETED = 'completed'; // 已入帳
|
||||
|
||||
// Amount tier constants
|
||||
public const AMOUNT_TIER_SMALL = 'small'; // < 5,000
|
||||
@@ -63,7 +71,6 @@ class FinanceDocument extends Model
|
||||
'rejected_at',
|
||||
'rejection_reason',
|
||||
// New payment stage fields
|
||||
'request_type',
|
||||
'amount_tier',
|
||||
'chart_of_account_id',
|
||||
'budget_item_id',
|
||||
@@ -89,6 +96,17 @@ class FinanceDocument extends Model
|
||||
'bank_reconciliation_id',
|
||||
'reconciliation_status',
|
||||
'reconciled_at',
|
||||
// 新工作流程欄位
|
||||
'approved_by_secretary_id',
|
||||
'secretary_approved_at',
|
||||
'disbursement_status',
|
||||
'requester_confirmed_at',
|
||||
'requester_confirmed_by_id',
|
||||
'cashier_confirmed_at',
|
||||
'cashier_confirmed_by_id',
|
||||
'recording_status',
|
||||
'accountant_recorded_at',
|
||||
'accountant_recorded_by_id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
@@ -106,6 +124,11 @@ class FinanceDocument extends Model
|
||||
'payment_executed_at' => 'datetime',
|
||||
'actual_payment_amount' => 'decimal:2',
|
||||
'reconciled_at' => 'datetime',
|
||||
// 新工作流程欄位
|
||||
'secretary_approved_at' => 'datetime',
|
||||
'requester_confirmed_at' => 'datetime',
|
||||
'cashier_confirmed_at' => 'datetime',
|
||||
'accountant_recorded_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function member()
|
||||
@@ -138,6 +161,29 @@ class FinanceDocument extends Model
|
||||
return $this->belongsTo(User::class, 'rejected_by_user_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 新工作流程 Relationships
|
||||
*/
|
||||
public function approvedBySecretary(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'approved_by_secretary_id');
|
||||
}
|
||||
|
||||
public function requesterConfirmedBy(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'requester_confirmed_by_id');
|
||||
}
|
||||
|
||||
public function cashierConfirmedBy(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'cashier_confirmed_by_id');
|
||||
}
|
||||
|
||||
public function accountantRecordedBy(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'accountant_recorded_by_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* New payment stage relationships
|
||||
*/
|
||||
@@ -187,9 +233,140 @@ class FinanceDocument extends Model
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if document can be approved by cashier
|
||||
* Get all accounting entries for this document
|
||||
*/
|
||||
public function canBeApprovedByCashier(?User $user = null): bool
|
||||
public function accountingEntries()
|
||||
{
|
||||
return $this->hasMany(AccountingEntry::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get debit entries for this document
|
||||
*/
|
||||
public function debitEntries()
|
||||
{
|
||||
return $this->accountingEntries()->where('entry_type', AccountingEntry::ENTRY_TYPE_DEBIT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get credit entries for this document
|
||||
*/
|
||||
public function creditEntries()
|
||||
{
|
||||
return $this->accountingEntries()->where('entry_type', AccountingEntry::ENTRY_TYPE_CREDIT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that debit and credit entries balance
|
||||
*/
|
||||
public function validateBalance(): bool
|
||||
{
|
||||
$debitTotal = $this->debitEntries()->sum('amount');
|
||||
$creditTotal = $this->creditEntries()->sum('amount');
|
||||
|
||||
return bccomp((string)$debitTotal, (string)$creditTotal, 2) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate accounting entries for this document
|
||||
* This creates the double-entry bookkeeping records
|
||||
*/
|
||||
public function generateAccountingEntries(array $entries): void
|
||||
{
|
||||
// Delete existing entries
|
||||
$this->accountingEntries()->delete();
|
||||
|
||||
// Create new entries
|
||||
foreach ($entries as $entry) {
|
||||
$this->accountingEntries()->create([
|
||||
'chart_of_account_id' => $entry['chart_of_account_id'],
|
||||
'entry_type' => $entry['entry_type'],
|
||||
'amount' => $entry['amount'],
|
||||
'entry_date' => $entry['entry_date'] ?? $this->submitted_at ?? now(),
|
||||
'description' => $entry['description'] ?? $this->description,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-generate simple accounting entries based on document type
|
||||
* For basic income/expense transactions
|
||||
*/
|
||||
public function autoGenerateAccountingEntries(): void
|
||||
{
|
||||
// Only auto-generate if chart_of_account_id is set
|
||||
if (!$this->chart_of_account_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
$entries = [];
|
||||
$entryDate = $this->submitted_at ?? now();
|
||||
|
||||
// Determine if this is income or expense based on request type or account type
|
||||
$account = $this->chartOfAccount;
|
||||
if (!$account) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($account->account_type === 'income') {
|
||||
// Income: Debit Cash, Credit Income Account
|
||||
$entries[] = [
|
||||
'chart_of_account_id' => $this->getCashAccountId(),
|
||||
'entry_type' => AccountingEntry::ENTRY_TYPE_DEBIT,
|
||||
'amount' => $this->amount,
|
||||
'entry_date' => $entryDate,
|
||||
'description' => '收入 - ' . ($this->description ?? $this->title),
|
||||
];
|
||||
$entries[] = [
|
||||
'chart_of_account_id' => $this->chart_of_account_id,
|
||||
'entry_type' => AccountingEntry::ENTRY_TYPE_CREDIT,
|
||||
'amount' => $this->amount,
|
||||
'entry_date' => $entryDate,
|
||||
'description' => $this->description ?? $this->title,
|
||||
];
|
||||
} elseif ($account->account_type === 'expense') {
|
||||
// Expense: Debit Expense Account, Credit Cash
|
||||
$entries[] = [
|
||||
'chart_of_account_id' => $this->chart_of_account_id,
|
||||
'entry_type' => AccountingEntry::ENTRY_TYPE_DEBIT,
|
||||
'amount' => $this->amount,
|
||||
'entry_date' => $entryDate,
|
||||
'description' => $this->description ?? $this->title,
|
||||
];
|
||||
$entries[] = [
|
||||
'chart_of_account_id' => $this->getCashAccountId(),
|
||||
'entry_type' => AccountingEntry::ENTRY_TYPE_CREDIT,
|
||||
'amount' => $this->amount,
|
||||
'entry_date' => $entryDate,
|
||||
'description' => '支出 - ' . ($this->description ?? $this->title),
|
||||
];
|
||||
}
|
||||
|
||||
if (!empty($entries)) {
|
||||
$this->generateAccountingEntries($entries);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cash account ID (1101 - 現金)
|
||||
*/
|
||||
protected function getCashAccountId(): int
|
||||
{
|
||||
static $cashAccountId = null;
|
||||
|
||||
if ($cashAccountId === null) {
|
||||
$cashAccount = ChartOfAccount::where('account_code', '1101')->first();
|
||||
$cashAccountId = $cashAccount ? $cashAccount->id : 1;
|
||||
}
|
||||
|
||||
return $cashAccountId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新工作流程:秘書長可審核
|
||||
* 條件:待審核狀態 + 不能審核自己的申請
|
||||
*/
|
||||
public function canBeApprovedBySecretary(?User $user = null): bool
|
||||
{
|
||||
if ($this->status !== self::STATUS_PENDING) {
|
||||
return false;
|
||||
@@ -203,27 +380,164 @@ class FinanceDocument extends Model
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if document can be approved by accountant
|
||||
* 新工作流程:理事長可審核
|
||||
* 條件:秘書長已核准 + 中額或大額
|
||||
*/
|
||||
public function canBeApprovedByAccountant(): bool
|
||||
public function canBeApprovedByChair(?User $user = null): bool
|
||||
{
|
||||
return $this->status === self::STATUS_APPROVED_CASHIER;
|
||||
$tier = $this->amount_tier ?? $this->determineAmountTier();
|
||||
|
||||
if ($this->status !== self::STATUS_APPROVED_SECRETARY) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!in_array($tier, [self::AMOUNT_TIER_MEDIUM, self::AMOUNT_TIER_LARGE])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($user && $this->submitted_by_user_id && $this->submitted_by_user_id === $user->id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if document can be approved by chair
|
||||
* 新工作流程:董理事會可審核
|
||||
* 條件:理事長已核准 + 大額
|
||||
*/
|
||||
public function canBeApprovedByChair(): bool
|
||||
public function canBeApprovedByBoard(?User $user = null): bool
|
||||
{
|
||||
return $this->status === self::STATUS_APPROVED_ACCOUNTANT;
|
||||
$tier = $this->amount_tier ?? $this->determineAmountTier();
|
||||
|
||||
if ($this->status !== self::STATUS_APPROVED_CHAIR) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($tier !== self::AMOUNT_TIER_LARGE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if document is fully approved
|
||||
* 新工作流程:審核是否完成
|
||||
* 依金額級別判斷
|
||||
*/
|
||||
public function isApprovalComplete(): bool
|
||||
{
|
||||
$tier = $this->amount_tier ?? $this->determineAmountTier();
|
||||
|
||||
// 小額:秘書長核准即可
|
||||
if ($tier === self::AMOUNT_TIER_SMALL) {
|
||||
return $this->status === self::STATUS_APPROVED_SECRETARY;
|
||||
}
|
||||
|
||||
// 中額:理事長核准
|
||||
if ($tier === self::AMOUNT_TIER_MEDIUM) {
|
||||
return $this->status === self::STATUS_APPROVED_CHAIR;
|
||||
}
|
||||
|
||||
// 大額:董理事會核准
|
||||
return $this->status === self::STATUS_APPROVED_BOARD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if document is fully approved (alias for isApprovalComplete)
|
||||
*/
|
||||
public function isFullyApproved(): bool
|
||||
{
|
||||
return $this->status === self::STATUS_APPROVED_CHAIR;
|
||||
return $this->isApprovalComplete();
|
||||
}
|
||||
|
||||
// ========== 出帳階段方法 ==========
|
||||
|
||||
/**
|
||||
* 申請人可確認出帳
|
||||
* 條件:審核完成 + 尚未確認 + 是原申請人
|
||||
*/
|
||||
public function canRequesterConfirmDisbursement(?User $user = null): bool
|
||||
{
|
||||
if (!$this->isApprovalComplete()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->requester_confirmed_at !== null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 只有原申請人可以確認
|
||||
if ($user && $this->submitted_by_user_id !== $user->id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 出納可確認出帳
|
||||
* 條件:審核完成 + 尚未確認
|
||||
*/
|
||||
public function canCashierConfirmDisbursement(): bool
|
||||
{
|
||||
if (!$this->isApprovalComplete()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->cashier_confirmed_at !== null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 出帳是否完成(雙重確認)
|
||||
*/
|
||||
public function isDisbursementComplete(): bool
|
||||
{
|
||||
return $this->requester_confirmed_at !== null
|
||||
&& $this->cashier_confirmed_at !== null;
|
||||
}
|
||||
|
||||
// ========== 入帳階段方法 ==========
|
||||
|
||||
/**
|
||||
* 會計可入帳
|
||||
* 條件:出帳完成 + 尚未入帳
|
||||
*/
|
||||
public function canAccountantConfirmRecording(): bool
|
||||
{
|
||||
return $this->isDisbursementComplete()
|
||||
&& $this->accountant_recorded_at === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 入帳是否完成
|
||||
*/
|
||||
public function isRecordingComplete(): bool
|
||||
{
|
||||
return $this->accountant_recorded_at !== null;
|
||||
}
|
||||
|
||||
// ========== Legacy methods for backward compatibility ==========
|
||||
|
||||
/**
|
||||
* @deprecated Use canBeApprovedBySecretary instead
|
||||
*/
|
||||
public function canBeApprovedByCashier(?User $user = null): bool
|
||||
{
|
||||
return $this->canBeApprovedBySecretary($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use isApprovalComplete with amount tier logic
|
||||
*/
|
||||
public function canBeApprovedByAccountant(): bool
|
||||
{
|
||||
// Legacy: accountant approval after cashier
|
||||
return $this->status === self::STATUS_APPROVED_CASHIER;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -235,20 +549,87 @@ class FinanceDocument extends Model
|
||||
}
|
||||
|
||||
/**
|
||||
* Get human-readable status
|
||||
* Get human-readable status (中文)
|
||||
*/
|
||||
public function getStatusLabelAttribute(): string
|
||||
{
|
||||
return match($this->status) {
|
||||
self::STATUS_PENDING => 'Pending Cashier Approval',
|
||||
self::STATUS_APPROVED_CASHIER => 'Pending Accountant Approval',
|
||||
self::STATUS_APPROVED_ACCOUNTANT => 'Pending Chair Approval',
|
||||
self::STATUS_APPROVED_CHAIR => 'Fully Approved',
|
||||
self::STATUS_REJECTED => 'Rejected',
|
||||
self::STATUS_PENDING => '待審核',
|
||||
self::STATUS_APPROVED_SECRETARY => '秘書長已核准',
|
||||
self::STATUS_APPROVED_CHAIR => '理事長已核准',
|
||||
self::STATUS_APPROVED_BOARD => '董理事會已核准',
|
||||
self::STATUS_REJECTED => '已駁回',
|
||||
// Legacy statuses
|
||||
self::STATUS_APPROVED_CASHIER => '出納已審核',
|
||||
self::STATUS_APPROVED_ACCOUNTANT => '會計已審核',
|
||||
default => ucfirst($this->status),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get disbursement status label (中文)
|
||||
*/
|
||||
public function getDisbursementStatusLabelAttribute(): string
|
||||
{
|
||||
if (!$this->isApprovalComplete()) {
|
||||
return '審核中';
|
||||
}
|
||||
|
||||
if ($this->isDisbursementComplete()) {
|
||||
return '已出帳';
|
||||
}
|
||||
|
||||
if ($this->requester_confirmed_at !== null && $this->cashier_confirmed_at === null) {
|
||||
return '申請人已確認,待出納確認';
|
||||
}
|
||||
|
||||
if ($this->requester_confirmed_at === null && $this->cashier_confirmed_at !== null) {
|
||||
return '出納已確認,待申請人確認';
|
||||
}
|
||||
|
||||
return '待出帳';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recording status label (中文)
|
||||
*/
|
||||
public function getRecordingStatusLabelAttribute(): string
|
||||
{
|
||||
if (!$this->isDisbursementComplete()) {
|
||||
return '尚未出帳';
|
||||
}
|
||||
|
||||
if ($this->accountant_recorded_at !== null) {
|
||||
return '已入帳';
|
||||
}
|
||||
|
||||
return '待入帳';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get overall workflow stage label (中文)
|
||||
*/
|
||||
public function getWorkflowStageLabelAttribute(): string
|
||||
{
|
||||
if ($this->isRejected()) {
|
||||
return '已駁回';
|
||||
}
|
||||
|
||||
if (!$this->isApprovalComplete()) {
|
||||
return '審核階段';
|
||||
}
|
||||
|
||||
if (!$this->isDisbursementComplete()) {
|
||||
return '出帳階段';
|
||||
}
|
||||
|
||||
if (!$this->isRecordingComplete()) {
|
||||
return '入帳階段';
|
||||
}
|
||||
|
||||
return '已完成';
|
||||
}
|
||||
|
||||
/**
|
||||
* New payment stage business logic methods
|
||||
*/
|
||||
@@ -277,29 +658,12 @@ class FinanceDocument extends Model
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if approval stage is complete (ready for payment order creation)
|
||||
* Check if approval stage is complete (ready for disbursement)
|
||||
* 新工作流程:使用 isApprovalComplete()
|
||||
*/
|
||||
public function isApprovalStageComplete(): bool
|
||||
{
|
||||
$tier = $this->amount_tier ?? $this->determineAmountTier();
|
||||
|
||||
// For small amounts: cashier + accountant
|
||||
if ($tier === self::AMOUNT_TIER_SMALL) {
|
||||
return $this->status === self::STATUS_APPROVED_ACCOUNTANT;
|
||||
}
|
||||
|
||||
// For medium amounts: cashier + accountant + chair
|
||||
if ($tier === self::AMOUNT_TIER_MEDIUM) {
|
||||
return $this->status === self::STATUS_APPROVED_CHAIR;
|
||||
}
|
||||
|
||||
// For large amounts: cashier + accountant + chair + board meeting
|
||||
if ($tier === self::AMOUNT_TIER_LARGE) {
|
||||
return $this->status === self::STATUS_APPROVED_CHAIR &&
|
||||
$this->board_meeting_approved_at !== null;
|
||||
}
|
||||
|
||||
return false;
|
||||
return $this->isApprovalComplete();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -341,21 +705,13 @@ class FinanceDocument extends Model
|
||||
return $this->payment_executed_at !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if recording stage is complete
|
||||
*/
|
||||
public function isRecordingComplete(): bool
|
||||
{
|
||||
return $this->cashier_recorded_at !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if document is fully processed (all stages complete)
|
||||
*/
|
||||
public function isFullyProcessed(): bool
|
||||
{
|
||||
return $this->isApprovalStageComplete() &&
|
||||
$this->isPaymentCompleted() &&
|
||||
return $this->isApprovalComplete() &&
|
||||
$this->isDisbursementComplete() &&
|
||||
$this->isRecordingComplete();
|
||||
}
|
||||
|
||||
@@ -425,20 +781,6 @@ class FinanceDocument extends Model
|
||||
$this->attributes['approved_by_board_meeting_id'] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get request type text
|
||||
*/
|
||||
public function getRequestTypeText(): string
|
||||
{
|
||||
return match ($this->request_type) {
|
||||
self::REQUEST_TYPE_EXPENSE_REIMBURSEMENT => '費用報銷',
|
||||
self::REQUEST_TYPE_ADVANCE_PAYMENT => '預支款項',
|
||||
self::REQUEST_TYPE_PURCHASE_REQUEST => '採購申請',
|
||||
self::REQUEST_TYPE_PETTY_CASH => '零用金',
|
||||
default => '未知',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get amount tier text
|
||||
*/
|
||||
|
||||
446
app/Models/Income.php
Normal file
446
app/Models/Income.php
Normal file
@@ -0,0 +1,446 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class Income extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
// 收入類型常數
|
||||
const TYPE_MEMBERSHIP_FEE = 'membership_fee'; // 會費收入
|
||||
const TYPE_ENTRANCE_FEE = 'entrance_fee'; // 入會費收入
|
||||
const TYPE_DONATION = 'donation'; // 捐款收入
|
||||
const TYPE_ACTIVITY = 'activity'; // 活動收入
|
||||
const TYPE_GRANT = 'grant'; // 補助收入
|
||||
const TYPE_INTEREST = 'interest'; // 利息收入
|
||||
const TYPE_OTHER = 'other'; // 其他收入
|
||||
|
||||
// 狀態常數
|
||||
const STATUS_PENDING = 'pending'; // 待確認
|
||||
const STATUS_CONFIRMED = 'confirmed'; // 已確認
|
||||
const STATUS_CANCELLED = 'cancelled'; // 已取消
|
||||
|
||||
// 付款方式常數
|
||||
const PAYMENT_METHOD_CASH = 'cash';
|
||||
const PAYMENT_METHOD_BANK_TRANSFER = 'bank_transfer';
|
||||
const PAYMENT_METHOD_CHECK = 'check';
|
||||
|
||||
protected $fillable = [
|
||||
'income_number',
|
||||
'title',
|
||||
'description',
|
||||
'income_date',
|
||||
'amount',
|
||||
'income_type',
|
||||
'chart_of_account_id',
|
||||
'payment_method',
|
||||
'bank_account',
|
||||
'payer_name',
|
||||
'receipt_number',
|
||||
'transaction_reference',
|
||||
'attachment_path',
|
||||
'member_id',
|
||||
'status',
|
||||
'recorded_by_cashier_id',
|
||||
'recorded_at',
|
||||
'confirmed_by_accountant_id',
|
||||
'confirmed_at',
|
||||
'cashier_ledger_entry_id',
|
||||
'notes',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'income_date' => 'date',
|
||||
'amount' => 'decimal:2',
|
||||
'recorded_at' => 'datetime',
|
||||
'confirmed_at' => 'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
* Boot 方法 - 自動產生收入編號
|
||||
*/
|
||||
protected static function boot()
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
static::creating(function ($income) {
|
||||
if (empty($income->income_number)) {
|
||||
$income->income_number = self::generateIncomeNumber();
|
||||
}
|
||||
if (empty($income->recorded_at)) {
|
||||
$income->recorded_at = now();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 產生收入編號 INC-2025-0001
|
||||
*/
|
||||
public static function generateIncomeNumber(): string
|
||||
{
|
||||
$year = date('Y');
|
||||
$prefix = "INC-{$year}-";
|
||||
|
||||
$lastIncome = self::where('income_number', 'like', "{$prefix}%")
|
||||
->orderBy('income_number', 'desc')
|
||||
->first();
|
||||
|
||||
if ($lastIncome) {
|
||||
$lastNumber = (int) substr($lastIncome->income_number, -4);
|
||||
$newNumber = $lastNumber + 1;
|
||||
} else {
|
||||
$newNumber = 1;
|
||||
}
|
||||
|
||||
return $prefix . str_pad($newNumber, 4, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
// ========== 關聯 ==========
|
||||
|
||||
/**
|
||||
* 會計科目
|
||||
*/
|
||||
public function chartOfAccount(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(ChartOfAccount::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 關聯會員
|
||||
*/
|
||||
public function member(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Member::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 記錄的出納人員
|
||||
*/
|
||||
public function recordedByCashier(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'recorded_by_cashier_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 確認的會計人員
|
||||
*/
|
||||
public function confirmedByAccountant(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'confirmed_by_accountant_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 關聯的出納日記帳
|
||||
*/
|
||||
public function cashierLedgerEntry(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(CashierLedgerEntry::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 會計分錄
|
||||
*/
|
||||
public function accountingEntries(): HasMany
|
||||
{
|
||||
return $this->hasMany(AccountingEntry::class);
|
||||
}
|
||||
|
||||
// ========== 狀態查詢 ==========
|
||||
|
||||
/**
|
||||
* 是否待確認
|
||||
*/
|
||||
public function isPending(): bool
|
||||
{
|
||||
return $this->status === self::STATUS_PENDING;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否已確認
|
||||
*/
|
||||
public function isConfirmed(): bool
|
||||
{
|
||||
return $this->status === self::STATUS_CONFIRMED;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否已取消
|
||||
*/
|
||||
public function isCancelled(): bool
|
||||
{
|
||||
return $this->status === self::STATUS_CANCELLED;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否可以被會計確認
|
||||
*/
|
||||
public function canBeConfirmed(): bool
|
||||
{
|
||||
return $this->status === self::STATUS_PENDING;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否可以被取消
|
||||
*/
|
||||
public function canBeCancelled(): bool
|
||||
{
|
||||
return $this->status === self::STATUS_PENDING;
|
||||
}
|
||||
|
||||
// ========== 業務方法 ==========
|
||||
|
||||
/**
|
||||
* 會計確認收入
|
||||
*/
|
||||
public function confirmByAccountant(User $accountant): void
|
||||
{
|
||||
if (!$this->canBeConfirmed()) {
|
||||
throw new \Exception('此收入無法確認');
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($accountant) {
|
||||
// 1. 更新收入狀態
|
||||
$this->update([
|
||||
'status' => self::STATUS_CONFIRMED,
|
||||
'confirmed_by_accountant_id' => $accountant->id,
|
||||
'confirmed_at' => now(),
|
||||
]);
|
||||
|
||||
// 2. 產生出納日記帳記錄
|
||||
$ledgerEntry = $this->createCashierLedgerEntry();
|
||||
|
||||
// 3. 產生會計分錄
|
||||
$this->generateAccountingEntries();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消收入
|
||||
*/
|
||||
public function cancel(): void
|
||||
{
|
||||
if (!$this->canBeCancelled()) {
|
||||
throw new \Exception('此收入無法取消');
|
||||
}
|
||||
|
||||
$this->update([
|
||||
'status' => self::STATUS_CANCELLED,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 建立出納日記帳記錄
|
||||
*/
|
||||
protected function createCashierLedgerEntry(): CashierLedgerEntry
|
||||
{
|
||||
$bankAccount = $this->bank_account ?? 'Main Account';
|
||||
$balanceBefore = CashierLedgerEntry::getLatestBalance($bankAccount);
|
||||
|
||||
$ledgerEntry = CashierLedgerEntry::create([
|
||||
'entry_date' => $this->income_date,
|
||||
'entry_type' => CashierLedgerEntry::ENTRY_TYPE_RECEIPT,
|
||||
'payment_method' => $this->payment_method,
|
||||
'bank_account' => $bankAccount,
|
||||
'amount' => $this->amount,
|
||||
'balance_before' => $balanceBefore,
|
||||
'balance_after' => $balanceBefore + $this->amount,
|
||||
'receipt_number' => $this->receipt_number,
|
||||
'transaction_reference' => $this->transaction_reference,
|
||||
'recorded_by_cashier_id' => $this->recorded_by_cashier_id,
|
||||
'recorded_at' => now(),
|
||||
'notes' => "收入確認:{$this->title} ({$this->income_number})",
|
||||
]);
|
||||
|
||||
$this->update(['cashier_ledger_entry_id' => $ledgerEntry->id]);
|
||||
|
||||
return $ledgerEntry;
|
||||
}
|
||||
|
||||
/**
|
||||
* 產生會計分錄
|
||||
*/
|
||||
protected function generateAccountingEntries(): void
|
||||
{
|
||||
// 借方:資產帳戶(現金或銀行存款)
|
||||
$assetAccountId = $this->getAssetAccountId();
|
||||
|
||||
AccountingEntry::create([
|
||||
'income_id' => $this->id,
|
||||
'chart_of_account_id' => $assetAccountId,
|
||||
'entry_type' => AccountingEntry::ENTRY_TYPE_DEBIT,
|
||||
'amount' => $this->amount,
|
||||
'entry_date' => $this->income_date,
|
||||
'description' => "收入:{$this->title} ({$this->income_number})",
|
||||
]);
|
||||
|
||||
// 貸方:收入科目
|
||||
AccountingEntry::create([
|
||||
'income_id' => $this->id,
|
||||
'chart_of_account_id' => $this->chart_of_account_id,
|
||||
'entry_type' => AccountingEntry::ENTRY_TYPE_CREDIT,
|
||||
'amount' => $this->amount,
|
||||
'entry_date' => $this->income_date,
|
||||
'description' => "收入:{$this->title} ({$this->income_number})",
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根據付款方式取得資產帳戶 ID
|
||||
*/
|
||||
protected function getAssetAccountId(): int
|
||||
{
|
||||
$accountCode = match ($this->payment_method) {
|
||||
self::PAYMENT_METHOD_BANK_TRANSFER => '1201', // 銀行存款
|
||||
self::PAYMENT_METHOD_CHECK => '1201', // 銀行存款
|
||||
default => '1101', // 現金
|
||||
};
|
||||
|
||||
return ChartOfAccount::where('account_code', $accountCode)->value('id') ?? 1;
|
||||
}
|
||||
|
||||
// ========== 文字取得 ==========
|
||||
|
||||
/**
|
||||
* 取得收入類型文字
|
||||
*/
|
||||
public function getIncomeTypeText(): string
|
||||
{
|
||||
return match ($this->income_type) {
|
||||
self::TYPE_MEMBERSHIP_FEE => '會費收入',
|
||||
self::TYPE_ENTRANCE_FEE => '入會費收入',
|
||||
self::TYPE_DONATION => '捐款收入',
|
||||
self::TYPE_ACTIVITY => '活動收入',
|
||||
self::TYPE_GRANT => '補助收入',
|
||||
self::TYPE_INTEREST => '利息收入',
|
||||
self::TYPE_OTHER => '其他收入',
|
||||
default => '未知',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得狀態文字
|
||||
*/
|
||||
public function getStatusText(): string
|
||||
{
|
||||
return match ($this->status) {
|
||||
self::STATUS_PENDING => '待確認',
|
||||
self::STATUS_CONFIRMED => '已確認',
|
||||
self::STATUS_CANCELLED => '已取消',
|
||||
default => '未知',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得付款方式文字
|
||||
*/
|
||||
public function getPaymentMethodText(): string
|
||||
{
|
||||
return match ($this->payment_method) {
|
||||
self::PAYMENT_METHOD_CASH => '現金',
|
||||
self::PAYMENT_METHOD_BANK_TRANSFER => '銀行轉帳',
|
||||
self::PAYMENT_METHOD_CHECK => '支票',
|
||||
default => '未知',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得狀態標籤屬性
|
||||
*/
|
||||
public function getStatusLabelAttribute(): string
|
||||
{
|
||||
return $this->getStatusText();
|
||||
}
|
||||
|
||||
// ========== 收入類型與科目對應 ==========
|
||||
|
||||
/**
|
||||
* 取得收入類型對應的預設會計科目代碼
|
||||
*/
|
||||
public static function getDefaultAccountCode(string $incomeType): string
|
||||
{
|
||||
return match ($incomeType) {
|
||||
self::TYPE_MEMBERSHIP_FEE => '4101',
|
||||
self::TYPE_ENTRANCE_FEE => '4102',
|
||||
self::TYPE_DONATION => '4201',
|
||||
self::TYPE_ACTIVITY => '4402',
|
||||
self::TYPE_GRANT => '4301',
|
||||
self::TYPE_INTEREST => '4401',
|
||||
self::TYPE_OTHER => '4901',
|
||||
default => '4901',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得收入類型對應的預設會計科目 ID
|
||||
*/
|
||||
public static function getDefaultAccountId(string $incomeType): ?int
|
||||
{
|
||||
$accountCode = self::getDefaultAccountCode($incomeType);
|
||||
return ChartOfAccount::where('account_code', $accountCode)->value('id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 靜態方法:取得收入類型文字標籤
|
||||
*/
|
||||
public static function getIncomeTypeLabel(string $incomeType): string
|
||||
{
|
||||
return match ($incomeType) {
|
||||
self::TYPE_MEMBERSHIP_FEE => '會費收入',
|
||||
self::TYPE_ENTRANCE_FEE => '入會費收入',
|
||||
self::TYPE_DONATION => '捐款收入',
|
||||
self::TYPE_ACTIVITY => '活動收入',
|
||||
self::TYPE_GRANT => '補助收入',
|
||||
self::TYPE_INTEREST => '利息收入',
|
||||
self::TYPE_OTHER => '其他收入',
|
||||
default => '未知',
|
||||
};
|
||||
}
|
||||
|
||||
// ========== 查詢範圍 ==========
|
||||
|
||||
/**
|
||||
* 篩選待確認的收入
|
||||
*/
|
||||
public function scopePending($query)
|
||||
{
|
||||
return $query->where('status', self::STATUS_PENDING);
|
||||
}
|
||||
|
||||
/**
|
||||
* 篩選已確認的收入
|
||||
*/
|
||||
public function scopeConfirmed($query)
|
||||
{
|
||||
return $query->where('status', self::STATUS_CONFIRMED);
|
||||
}
|
||||
|
||||
/**
|
||||
* 篩選特定收入類型
|
||||
*/
|
||||
public function scopeOfType($query, string $type)
|
||||
{
|
||||
return $query->where('income_type', $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 篩選特定會員
|
||||
*/
|
||||
public function scopeForMember($query, int $memberId)
|
||||
{
|
||||
return $query->where('member_id', $memberId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 篩選日期範圍
|
||||
*/
|
||||
public function scopeDateRange($query, $startDate, $endDate)
|
||||
{
|
||||
return $query->whereBetween('income_date', [$startDate, $endDate]);
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,11 @@ class Member extends Model
|
||||
const TYPE_LIFETIME = 'lifetime';
|
||||
const TYPE_STUDENT = 'student';
|
||||
|
||||
// Disability certificate status constants
|
||||
const DISABILITY_STATUS_PENDING = 'pending';
|
||||
const DISABILITY_STATUS_APPROVED = 'approved';
|
||||
const DISABILITY_STATUS_REJECTED = 'rejected';
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'full_name',
|
||||
@@ -39,11 +44,17 @@ class Member extends Model
|
||||
'membership_expires_at',
|
||||
'membership_status',
|
||||
'membership_type',
|
||||
'disability_certificate_path',
|
||||
'disability_certificate_status',
|
||||
'disability_verified_by',
|
||||
'disability_verified_at',
|
||||
'disability_rejection_reason',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'membership_started_at' => 'date',
|
||||
'membership_expires_at' => 'date',
|
||||
'disability_verified_at' => 'datetime',
|
||||
];
|
||||
|
||||
protected $appends = ['national_id'];
|
||||
@@ -58,6 +69,37 @@ class Member extends Model
|
||||
return $this->hasMany(MembershipPayment::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 關聯的收入記錄
|
||||
*/
|
||||
public function incomes()
|
||||
{
|
||||
return $this->hasMany(Income::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得會員的會費收入記錄
|
||||
*/
|
||||
public function getMembershipFeeIncomes()
|
||||
{
|
||||
return $this->incomes()
|
||||
->whereIn('income_type', [
|
||||
Income::TYPE_MEMBERSHIP_FEE,
|
||||
Income::TYPE_ENTRANCE_FEE
|
||||
])
|
||||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得會員的總收入金額
|
||||
*/
|
||||
public function getTotalIncomeAttribute(): float
|
||||
{
|
||||
return $this->incomes()
|
||||
->where('status', Income::STATUS_CONFIRMED)
|
||||
->sum('amount');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the decrypted national ID
|
||||
*/
|
||||
@@ -203,4 +245,120 @@ class Member extends Model
|
||||
// Can submit if pending status and no pending payment
|
||||
return $this->isPending() && !$this->getPendingPayment();
|
||||
}
|
||||
|
||||
// ========== 身心障礙相關 ==========
|
||||
|
||||
/**
|
||||
* 身心障礙手冊審核人
|
||||
*/
|
||||
public function disabilityVerifiedBy()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'disability_verified_by');
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否有上傳身心障礙手冊
|
||||
*/
|
||||
public function hasDisabilityCertificate(): bool
|
||||
{
|
||||
return !empty($this->disability_certificate_path);
|
||||
}
|
||||
|
||||
/**
|
||||
* 身心障礙手冊是否待審核
|
||||
*/
|
||||
public function isDisabilityPending(): bool
|
||||
{
|
||||
return $this->disability_certificate_status === self::DISABILITY_STATUS_PENDING;
|
||||
}
|
||||
|
||||
/**
|
||||
* 身心障礙手冊是否已通過審核
|
||||
*/
|
||||
public function hasApprovedDisability(): bool
|
||||
{
|
||||
return $this->disability_certificate_status === self::DISABILITY_STATUS_APPROVED;
|
||||
}
|
||||
|
||||
/**
|
||||
* 身心障礙手冊是否被駁回
|
||||
*/
|
||||
public function isDisabilityRejected(): bool
|
||||
{
|
||||
return $this->disability_certificate_status === self::DISABILITY_STATUS_REJECTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得身心障礙狀態標籤
|
||||
*/
|
||||
public function getDisabilityStatusLabelAttribute(): string
|
||||
{
|
||||
if (!$this->hasDisabilityCertificate()) {
|
||||
return '未上傳';
|
||||
}
|
||||
|
||||
return match ($this->disability_certificate_status) {
|
||||
self::DISABILITY_STATUS_PENDING => '審核中',
|
||||
self::DISABILITY_STATUS_APPROVED => '已通過',
|
||||
self::DISABILITY_STATUS_REJECTED => '已駁回',
|
||||
default => '未知',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得身心障礙狀態的 Badge 樣式
|
||||
*/
|
||||
public function getDisabilityStatusBadgeAttribute(): string
|
||||
{
|
||||
if (!$this->hasDisabilityCertificate()) {
|
||||
return 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-200';
|
||||
}
|
||||
|
||||
return match ($this->disability_certificate_status) {
|
||||
self::DISABILITY_STATUS_PENDING => 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200',
|
||||
self::DISABILITY_STATUS_APPROVED => 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200',
|
||||
self::DISABILITY_STATUS_REJECTED => 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200',
|
||||
default => 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-200',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 審核通過身心障礙手冊
|
||||
*/
|
||||
public function approveDisabilityCertificate(User $verifier): void
|
||||
{
|
||||
$this->update([
|
||||
'disability_certificate_status' => self::DISABILITY_STATUS_APPROVED,
|
||||
'disability_verified_by' => $verifier->id,
|
||||
'disability_verified_at' => now(),
|
||||
'disability_rejection_reason' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 駁回身心障礙手冊
|
||||
*/
|
||||
public function rejectDisabilityCertificate(User $verifier, string $reason): void
|
||||
{
|
||||
$this->update([
|
||||
'disability_certificate_status' => self::DISABILITY_STATUS_REJECTED,
|
||||
'disability_verified_by' => $verifier->id,
|
||||
'disability_verified_at' => now(),
|
||||
'disability_rejection_reason' => $reason,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判斷下一次應繳哪種會費
|
||||
*/
|
||||
public function getNextFeeType(): string
|
||||
{
|
||||
// 新會員(從未啟用過)= 入會會費
|
||||
if ($this->membership_started_at === null) {
|
||||
return MembershipPayment::FEE_TYPE_ENTRANCE;
|
||||
}
|
||||
|
||||
// 已有會籍 = 常年會費
|
||||
return MembershipPayment::FEE_TYPE_ANNUAL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,10 +23,19 @@ class MembershipPayment extends Model
|
||||
const METHOD_CASH = 'cash';
|
||||
const METHOD_CREDIT_CARD = 'credit_card';
|
||||
|
||||
// Fee type constants
|
||||
const FEE_TYPE_ENTRANCE = 'entrance_fee'; // 入會會費
|
||||
const FEE_TYPE_ANNUAL = 'annual_fee'; // 常年會費
|
||||
|
||||
protected $fillable = [
|
||||
'member_id',
|
||||
'fee_type',
|
||||
'paid_at',
|
||||
'amount',
|
||||
'base_amount',
|
||||
'discount_amount',
|
||||
'final_amount',
|
||||
'disability_discount',
|
||||
'method',
|
||||
'reference',
|
||||
'status',
|
||||
@@ -51,6 +60,10 @@ class MembershipPayment extends Model
|
||||
'accountant_verified_at' => 'datetime',
|
||||
'chair_verified_at' => 'datetime',
|
||||
'rejected_at' => 'datetime',
|
||||
'base_amount' => 'decimal:2',
|
||||
'discount_amount' => 'decimal:2',
|
||||
'final_amount' => 'decimal:2',
|
||||
'disability_discount' => 'boolean',
|
||||
];
|
||||
|
||||
// Relationships
|
||||
@@ -151,6 +164,36 @@ class MembershipPayment extends Model
|
||||
};
|
||||
}
|
||||
|
||||
// Accessor for fee type label
|
||||
public function getFeeTypeLabelAttribute(): string
|
||||
{
|
||||
return match($this->fee_type) {
|
||||
self::FEE_TYPE_ENTRANCE => '入會會費',
|
||||
self::FEE_TYPE_ANNUAL => '常年會費',
|
||||
default => $this->fee_type ?? '未指定',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否有使用身心障礙優惠
|
||||
*/
|
||||
public function hasDisabilityDiscount(): bool
|
||||
{
|
||||
return (bool) $this->disability_discount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得折扣說明
|
||||
*/
|
||||
public function getDiscountDescriptionAttribute(): ?string
|
||||
{
|
||||
if (!$this->hasDisabilityDiscount()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return '身心障礙優惠 50%';
|
||||
}
|
||||
|
||||
// Clean up receipt file when payment is deleted
|
||||
protected static function boot()
|
||||
{
|
||||
|
||||
@@ -67,7 +67,7 @@ class PaymentOrder extends Model
|
||||
const PAYMENT_METHOD_CASH = 'cash';
|
||||
|
||||
/**
|
||||
* 關聯到財務申請單
|
||||
* 關聯到報銷申請單
|
||||
*/
|
||||
public function financeDocument(): BelongsTo
|
||||
{
|
||||
|
||||
@@ -23,7 +23,6 @@ class User extends Authenticatable
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'email',
|
||||
'is_admin',
|
||||
'profile_photo_path',
|
||||
'password',
|
||||
];
|
||||
@@ -46,7 +45,6 @@ class User extends Authenticatable
|
||||
protected $casts = [
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
'is_admin' => 'boolean',
|
||||
];
|
||||
|
||||
public function member(): HasOne
|
||||
@@ -54,6 +52,15 @@ class User extends Authenticatable
|
||||
return $this->hasOne(Member::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 檢查使用者是否為管理員
|
||||
* 使用 Spatie Permission 的 admin 角色取代舊版 is_admin 欄位
|
||||
*/
|
||||
public function isAdmin(): bool
|
||||
{
|
||||
return $this->hasRole('admin');
|
||||
}
|
||||
|
||||
public function profilePhotoUrl(): ?string
|
||||
{
|
||||
if (! $this->profile_photo_path) {
|
||||
|
||||
154
app/Services/MembershipFeeCalculator.php
Normal file
154
app/Services/MembershipFeeCalculator.php
Normal file
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Member;
|
||||
use App\Models\MembershipPayment;
|
||||
|
||||
class MembershipFeeCalculator
|
||||
{
|
||||
protected SettingsService $settings;
|
||||
|
||||
public function __construct(SettingsService $settings)
|
||||
{
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* 計算會費金額
|
||||
*
|
||||
* @param Member $member 會員
|
||||
* @param string $feeType 會費類型 (entrance_fee | annual_fee)
|
||||
* @return array{base_amount: float, discount_amount: float, final_amount: float, disability_discount: bool, fee_type: string}
|
||||
*/
|
||||
public function calculate(Member $member, string $feeType): array
|
||||
{
|
||||
$baseAmount = $this->getBaseAmount($feeType);
|
||||
$discountRate = $member->hasApprovedDisability()
|
||||
? $this->getDisabilityDiscountRate()
|
||||
: 0;
|
||||
|
||||
$discountAmount = round($baseAmount * $discountRate, 2);
|
||||
$finalAmount = round($baseAmount - $discountAmount, 2);
|
||||
|
||||
return [
|
||||
'fee_type' => $feeType,
|
||||
'base_amount' => $baseAmount,
|
||||
'discount_amount' => $discountAmount,
|
||||
'final_amount' => $finalAmount,
|
||||
'disability_discount' => $discountRate > 0,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 為會員計算下一次應繳的會費
|
||||
*
|
||||
* @param Member $member
|
||||
* @return array{base_amount: float, discount_amount: float, final_amount: float, disability_discount: bool, fee_type: string}
|
||||
*/
|
||||
public function calculateNextFee(Member $member): array
|
||||
{
|
||||
$feeType = $member->getNextFeeType();
|
||||
return $this->calculate($member, $feeType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得基本會費金額
|
||||
*
|
||||
* @param string $feeType
|
||||
* @return float
|
||||
*/
|
||||
public function getBaseAmount(string $feeType): float
|
||||
{
|
||||
return match($feeType) {
|
||||
MembershipPayment::FEE_TYPE_ENTRANCE => $this->getEntranceFee(),
|
||||
MembershipPayment::FEE_TYPE_ANNUAL => $this->getAnnualFee(),
|
||||
default => 0,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得入會會費金額
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getEntranceFee(): float
|
||||
{
|
||||
return (float) $this->settings->get('membership_fee.entrance_fee', 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得常年會費金額
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getAnnualFee(): float
|
||||
{
|
||||
return (float) $this->settings->get('membership_fee.annual_fee', 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得身心障礙折扣比例
|
||||
*
|
||||
* @return float 折扣比例 (0-1)
|
||||
*/
|
||||
public function getDisabilityDiscountRate(): float
|
||||
{
|
||||
return (float) $this->settings->get('membership_fee.disability_discount_rate', 0.5);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得身心障礙折扣百分比 (用於顯示)
|
||||
*
|
||||
* @return int 百分比 (0-100)
|
||||
*/
|
||||
public function getDisabilityDiscountPercentage(): int
|
||||
{
|
||||
return (int) ($this->getDisabilityDiscountRate() * 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* 驗證繳費金額是否正確
|
||||
*
|
||||
* @param MembershipPayment $payment
|
||||
* @return bool
|
||||
*/
|
||||
public function validatePaymentAmount(MembershipPayment $payment): bool
|
||||
{
|
||||
$member = $payment->member;
|
||||
$expected = $this->calculate($member, $payment->fee_type);
|
||||
|
||||
// 繳費金額應等於或大於應繳金額
|
||||
return $payment->amount >= $expected['final_amount'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得會費類型的標籤
|
||||
*
|
||||
* @param string $feeType
|
||||
* @return string
|
||||
*/
|
||||
public function getFeeTypeLabel(string $feeType): string
|
||||
{
|
||||
return match($feeType) {
|
||||
MembershipPayment::FEE_TYPE_ENTRANCE => '入會會費',
|
||||
MembershipPayment::FEE_TYPE_ANNUAL => '常年會費',
|
||||
default => $feeType,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得所有會費設定(用於管理界面)
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFeeSettings(): array
|
||||
{
|
||||
return [
|
||||
'entrance_fee' => $this->getEntranceFee(),
|
||||
'annual_fee' => $this->getAnnualFee(),
|
||||
'disability_discount_rate' => $this->getDisabilityDiscountRate(),
|
||||
'disability_discount_percentage' => $this->getDisabilityDiscountPercentage(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -11,12 +11,14 @@
|
||||
"laravel/framework": "^10.10",
|
||||
"laravel/sanctum": "^3.3",
|
||||
"laravel/tinker": "^2.8",
|
||||
"maatwebsite/excel": "^3.1",
|
||||
"simplesoftwareio/simple-qrcode": "^4.2",
|
||||
"spatie/laravel-permission": "^6.23"
|
||||
},
|
||||
"require-dev": {
|
||||
"fakerphp/faker": "^1.9.1",
|
||||
"laravel/breeze": "^1.29",
|
||||
"laravel/dusk": "^8.3",
|
||||
"laravel/pint": "^1.0",
|
||||
"laravel/sail": "^1.18",
|
||||
"mockery/mockery": "^1.4.4",
|
||||
|
||||
737
composer.lock
generated
737
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "823199d76778549dda38d7d7c8a1967a",
|
||||
"content-hash": "10268750f724780736201053fb4871bf",
|
||||
"packages": [
|
||||
{
|
||||
"name": "bacon/bacon-qr-code",
|
||||
@@ -266,6 +266,162 @@
|
||||
],
|
||||
"time": "2023-12-11T17:09:12+00:00"
|
||||
},
|
||||
{
|
||||
"name": "composer/pcre",
|
||||
"version": "3.3.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/composer/pcre.git",
|
||||
"reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
|
||||
"reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.4 || ^8.0"
|
||||
},
|
||||
"conflict": {
|
||||
"phpstan/phpstan": "<1.11.10"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^1.12 || ^2",
|
||||
"phpstan/phpstan-strict-rules": "^1 || ^2",
|
||||
"phpunit/phpunit": "^8 || ^9"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"phpstan": {
|
||||
"includes": [
|
||||
"extension.neon"
|
||||
]
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-main": "3.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Composer\\Pcre\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Jordi Boggiano",
|
||||
"email": "j.boggiano@seld.be",
|
||||
"homepage": "http://seld.be"
|
||||
}
|
||||
],
|
||||
"description": "PCRE wrapping library that offers type-safe preg_* replacements.",
|
||||
"keywords": [
|
||||
"PCRE",
|
||||
"preg",
|
||||
"regex",
|
||||
"regular expression"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/composer/pcre/issues",
|
||||
"source": "https://github.com/composer/pcre/tree/3.3.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://packagist.com",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/composer",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-11-12T16:29:46+00:00"
|
||||
},
|
||||
{
|
||||
"name": "composer/semver",
|
||||
"version": "3.4.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/composer/semver.git",
|
||||
"reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95",
|
||||
"reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^5.3.2 || ^7.0 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^1.11",
|
||||
"symfony/phpunit-bridge": "^3 || ^7"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "3.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Composer\\Semver\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nils Adermann",
|
||||
"email": "naderman@naderman.de",
|
||||
"homepage": "http://www.naderman.de"
|
||||
},
|
||||
{
|
||||
"name": "Jordi Boggiano",
|
||||
"email": "j.boggiano@seld.be",
|
||||
"homepage": "http://seld.be"
|
||||
},
|
||||
{
|
||||
"name": "Rob Bast",
|
||||
"email": "rob.bast@gmail.com",
|
||||
"homepage": "http://robbast.nl"
|
||||
}
|
||||
],
|
||||
"description": "Semver library that offers utilities, version constraint parsing and validation.",
|
||||
"keywords": [
|
||||
"semantic",
|
||||
"semver",
|
||||
"validation",
|
||||
"versioning"
|
||||
],
|
||||
"support": {
|
||||
"irc": "ircs://irc.libera.chat:6697/composer",
|
||||
"issues": "https://github.com/composer/semver/issues",
|
||||
"source": "https://github.com/composer/semver/tree/3.4.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://packagist.com",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/composer",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-08-20T19:15:30+00:00"
|
||||
},
|
||||
{
|
||||
"name": "dasprid/enum",
|
||||
"version": "1.0.7",
|
||||
@@ -844,6 +1000,67 @@
|
||||
],
|
||||
"time": "2025-03-06T22:45:56+00:00"
|
||||
},
|
||||
{
|
||||
"name": "ezyang/htmlpurifier",
|
||||
"version": "v4.19.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ezyang/htmlpurifier.git",
|
||||
"reference": "b287d2a16aceffbf6e0295559b39662612b77fcf"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/b287d2a16aceffbf6e0295559b39662612b77fcf",
|
||||
"reference": "b287d2a16aceffbf6e0295559b39662612b77fcf",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"cerdic/css-tidy": "^1.7 || ^2.0",
|
||||
"simpletest/simpletest": "dev-master"
|
||||
},
|
||||
"suggest": {
|
||||
"cerdic/css-tidy": "If you want to use the filter 'Filter.ExtractStyleBlocks'.",
|
||||
"ext-bcmath": "Used for unit conversion and imagecrash protection",
|
||||
"ext-iconv": "Converts text to and from non-UTF-8 encodings",
|
||||
"ext-tidy": "Used for pretty-printing HTML"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"library/HTMLPurifier.composer.php"
|
||||
],
|
||||
"psr-0": {
|
||||
"HTMLPurifier": "library/"
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/library/HTMLPurifier/Language/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"LGPL-2.1-or-later"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Edward Z. Yang",
|
||||
"email": "admin@htmlpurifier.org",
|
||||
"homepage": "http://ezyang.com"
|
||||
}
|
||||
],
|
||||
"description": "Standards compliant HTML filter written in PHP",
|
||||
"homepage": "http://htmlpurifier.org/",
|
||||
"keywords": [
|
||||
"html"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/ezyang/htmlpurifier/issues",
|
||||
"source": "https://github.com/ezyang/htmlpurifier/tree/v4.19.0"
|
||||
},
|
||||
"time": "2025-10-17T16:34:55+00:00"
|
||||
},
|
||||
{
|
||||
"name": "fruitcake/php-cors",
|
||||
"version": "v1.3.0",
|
||||
@@ -2224,6 +2441,272 @@
|
||||
],
|
||||
"time": "2024-09-21T08:32:55+00:00"
|
||||
},
|
||||
{
|
||||
"name": "maatwebsite/excel",
|
||||
"version": "3.1.67",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/SpartnerNL/Laravel-Excel.git",
|
||||
"reference": "e508e34a502a3acc3329b464dad257378a7edb4d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/SpartnerNL/Laravel-Excel/zipball/e508e34a502a3acc3329b464dad257378a7edb4d",
|
||||
"reference": "e508e34a502a3acc3329b464dad257378a7edb4d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"composer/semver": "^3.3",
|
||||
"ext-json": "*",
|
||||
"illuminate/support": "5.8.*||^6.0||^7.0||^8.0||^9.0||^10.0||^11.0||^12.0",
|
||||
"php": "^7.0||^8.0",
|
||||
"phpoffice/phpspreadsheet": "^1.30.0",
|
||||
"psr/simple-cache": "^1.0||^2.0||^3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/scout": "^7.0||^8.0||^9.0||^10.0",
|
||||
"orchestra/testbench": "^6.0||^7.0||^8.0||^9.0||^10.0",
|
||||
"predis/predis": "^1.1"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"aliases": {
|
||||
"Excel": "Maatwebsite\\Excel\\Facades\\Excel"
|
||||
},
|
||||
"providers": [
|
||||
"Maatwebsite\\Excel\\ExcelServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Maatwebsite\\Excel\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Patrick Brouwers",
|
||||
"email": "patrick@spartner.nl"
|
||||
}
|
||||
],
|
||||
"description": "Supercharged Excel exports and imports in Laravel",
|
||||
"keywords": [
|
||||
"PHPExcel",
|
||||
"batch",
|
||||
"csv",
|
||||
"excel",
|
||||
"export",
|
||||
"import",
|
||||
"laravel",
|
||||
"php",
|
||||
"phpspreadsheet"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/SpartnerNL/Laravel-Excel/issues",
|
||||
"source": "https://github.com/SpartnerNL/Laravel-Excel/tree/3.1.67"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://laravel-excel.com/commercial-support",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/patrickbrouwers",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-08-26T09:13:16+00:00"
|
||||
},
|
||||
{
|
||||
"name": "maennchen/zipstream-php",
|
||||
"version": "3.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/maennchen/ZipStream-PHP.git",
|
||||
"reference": "9712d8fa4cdf9240380b01eb4be55ad8dcf71416"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/9712d8fa4cdf9240380b01eb4be55ad8dcf71416",
|
||||
"reference": "9712d8fa4cdf9240380b01eb4be55ad8dcf71416",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-mbstring": "*",
|
||||
"ext-zlib": "*",
|
||||
"php-64bit": "^8.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"brianium/paratest": "^7.7",
|
||||
"ext-zip": "*",
|
||||
"friendsofphp/php-cs-fixer": "^3.16",
|
||||
"guzzlehttp/guzzle": "^7.5",
|
||||
"mikey179/vfsstream": "^1.6",
|
||||
"php-coveralls/php-coveralls": "^2.5",
|
||||
"phpunit/phpunit": "^12.0",
|
||||
"vimeo/psalm": "^6.0"
|
||||
},
|
||||
"suggest": {
|
||||
"guzzlehttp/psr7": "^2.4",
|
||||
"psr/http-message": "^2.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"ZipStream\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Paul Duncan",
|
||||
"email": "pabs@pablotron.org"
|
||||
},
|
||||
{
|
||||
"name": "Jonatan Männchen",
|
||||
"email": "jonatan@maennchen.ch"
|
||||
},
|
||||
{
|
||||
"name": "Jesse Donat",
|
||||
"email": "donatj@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "András Kolesár",
|
||||
"email": "kolesar@kolesar.hu"
|
||||
}
|
||||
],
|
||||
"description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.",
|
||||
"keywords": [
|
||||
"stream",
|
||||
"zip"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/maennchen/ZipStream-PHP/issues",
|
||||
"source": "https://github.com/maennchen/ZipStream-PHP/tree/3.2.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/maennchen",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-07-17T11:15:13+00:00"
|
||||
},
|
||||
{
|
||||
"name": "markbaker/complex",
|
||||
"version": "3.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/MarkBaker/PHPComplex.git",
|
||||
"reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/95c56caa1cf5c766ad6d65b6344b807c1e8405b9",
|
||||
"reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"dealerdirect/phpcodesniffer-composer-installer": "dev-master",
|
||||
"phpcompatibility/php-compatibility": "^9.3",
|
||||
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
|
||||
"squizlabs/php_codesniffer": "^3.7"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Complex\\": "classes/src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Mark Baker",
|
||||
"email": "mark@lange.demon.co.uk"
|
||||
}
|
||||
],
|
||||
"description": "PHP Class for working with complex numbers",
|
||||
"homepage": "https://github.com/MarkBaker/PHPComplex",
|
||||
"keywords": [
|
||||
"complex",
|
||||
"mathematics"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/MarkBaker/PHPComplex/issues",
|
||||
"source": "https://github.com/MarkBaker/PHPComplex/tree/3.0.2"
|
||||
},
|
||||
"time": "2022-12-06T16:21:08+00:00"
|
||||
},
|
||||
{
|
||||
"name": "markbaker/matrix",
|
||||
"version": "3.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/MarkBaker/PHPMatrix.git",
|
||||
"reference": "728434227fe21be27ff6d86621a1b13107a2562c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/728434227fe21be27ff6d86621a1b13107a2562c",
|
||||
"reference": "728434227fe21be27ff6d86621a1b13107a2562c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.1 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"dealerdirect/phpcodesniffer-composer-installer": "dev-master",
|
||||
"phpcompatibility/php-compatibility": "^9.3",
|
||||
"phpdocumentor/phpdocumentor": "2.*",
|
||||
"phploc/phploc": "^4.0",
|
||||
"phpmd/phpmd": "2.*",
|
||||
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
|
||||
"sebastian/phpcpd": "^4.0",
|
||||
"squizlabs/php_codesniffer": "^3.7"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Matrix\\": "classes/src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Mark Baker",
|
||||
"email": "mark@demon-angel.eu"
|
||||
}
|
||||
],
|
||||
"description": "PHP Class for working with matrices",
|
||||
"homepage": "https://github.com/MarkBaker/PHPMatrix",
|
||||
"keywords": [
|
||||
"mathematics",
|
||||
"matrix",
|
||||
"vector"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/MarkBaker/PHPMatrix/issues",
|
||||
"source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.1"
|
||||
},
|
||||
"time": "2022-12-02T22:17:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "masterminds/html5",
|
||||
"version": "2.10.0",
|
||||
@@ -2798,6 +3281,112 @@
|
||||
],
|
||||
"time": "2024-11-21T10:36:35+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpoffice/phpspreadsheet",
|
||||
"version": "1.30.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PHPOffice/PhpSpreadsheet.git",
|
||||
"reference": "fa8257a579ec623473eabfe49731de5967306c4c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/fa8257a579ec623473eabfe49731de5967306c4c",
|
||||
"reference": "fa8257a579ec623473eabfe49731de5967306c4c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"composer/pcre": "^1||^2||^3",
|
||||
"ext-ctype": "*",
|
||||
"ext-dom": "*",
|
||||
"ext-fileinfo": "*",
|
||||
"ext-gd": "*",
|
||||
"ext-iconv": "*",
|
||||
"ext-libxml": "*",
|
||||
"ext-mbstring": "*",
|
||||
"ext-simplexml": "*",
|
||||
"ext-xml": "*",
|
||||
"ext-xmlreader": "*",
|
||||
"ext-xmlwriter": "*",
|
||||
"ext-zip": "*",
|
||||
"ext-zlib": "*",
|
||||
"ezyang/htmlpurifier": "^4.15",
|
||||
"maennchen/zipstream-php": "^2.1 || ^3.0",
|
||||
"markbaker/complex": "^3.0",
|
||||
"markbaker/matrix": "^3.0",
|
||||
"php": ">=7.4.0 <8.5.0",
|
||||
"psr/http-client": "^1.0",
|
||||
"psr/http-factory": "^1.0",
|
||||
"psr/simple-cache": "^1.0 || ^2.0 || ^3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"dealerdirect/phpcodesniffer-composer-installer": "dev-main",
|
||||
"dompdf/dompdf": "^1.0 || ^2.0 || ^3.0",
|
||||
"friendsofphp/php-cs-fixer": "^3.2",
|
||||
"mitoteam/jpgraph": "^10.3",
|
||||
"mpdf/mpdf": "^8.1.1",
|
||||
"phpcompatibility/php-compatibility": "^9.3",
|
||||
"phpstan/phpstan": "^1.1",
|
||||
"phpstan/phpstan-phpunit": "^1.0",
|
||||
"phpunit/phpunit": "^8.5 || ^9.0",
|
||||
"squizlabs/php_codesniffer": "^3.7",
|
||||
"tecnickcom/tcpdf": "^6.5"
|
||||
},
|
||||
"suggest": {
|
||||
"dompdf/dompdf": "Option for rendering PDF with PDF Writer",
|
||||
"ext-intl": "PHP Internationalization Functions",
|
||||
"mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers",
|
||||
"mpdf/mpdf": "Option for rendering PDF with PDF Writer",
|
||||
"tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Maarten Balliauw",
|
||||
"homepage": "https://blog.maartenballiauw.be"
|
||||
},
|
||||
{
|
||||
"name": "Mark Baker",
|
||||
"homepage": "https://markbakeruk.net"
|
||||
},
|
||||
{
|
||||
"name": "Franck Lefevre",
|
||||
"homepage": "https://rootslabs.net"
|
||||
},
|
||||
{
|
||||
"name": "Erik Tilt"
|
||||
},
|
||||
{
|
||||
"name": "Adrien Crivelli"
|
||||
}
|
||||
],
|
||||
"description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine",
|
||||
"homepage": "https://github.com/PHPOffice/PhpSpreadsheet",
|
||||
"keywords": [
|
||||
"OpenXML",
|
||||
"excel",
|
||||
"gnumeric",
|
||||
"ods",
|
||||
"php",
|
||||
"spreadsheet",
|
||||
"xls",
|
||||
"xlsx"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues",
|
||||
"source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.30.1"
|
||||
},
|
||||
"time": "2025-10-26T16:01:04+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpoption/phpoption",
|
||||
"version": "1.9.4",
|
||||
@@ -6499,6 +7088,80 @@
|
||||
},
|
||||
"time": "2024-03-04T14:35:21+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/dusk",
|
||||
"version": "v8.3.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/dusk.git",
|
||||
"reference": "33a4211c7b63ffe430bf30ec3c014012dcb6dfa6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/dusk/zipball/33a4211c7b63ffe430bf30ec3c014012dcb6dfa6",
|
||||
"reference": "33a4211c7b63ffe430bf30ec3c014012dcb6dfa6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"ext-zip": "*",
|
||||
"guzzlehttp/guzzle": "^7.5",
|
||||
"illuminate/console": "^10.0|^11.0|^12.0",
|
||||
"illuminate/support": "^10.0|^11.0|^12.0",
|
||||
"php": "^8.1",
|
||||
"php-webdriver/webdriver": "^1.15.2",
|
||||
"symfony/console": "^6.2|^7.0",
|
||||
"symfony/finder": "^6.2|^7.0",
|
||||
"symfony/process": "^6.2|^7.0",
|
||||
"vlucas/phpdotenv": "^5.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/framework": "^10.0|^11.0|^12.0",
|
||||
"mockery/mockery": "^1.6",
|
||||
"orchestra/testbench-core": "^8.19|^9.17|^10.8",
|
||||
"phpstan/phpstan": "^1.10",
|
||||
"phpunit/phpunit": "^10.1|^11.0|^12.0.1",
|
||||
"psy/psysh": "^0.11.12|^0.12",
|
||||
"symfony/yaml": "^6.2|^7.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-pcntl": "Used to gracefully terminate Dusk when tests are running."
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Laravel\\Dusk\\DuskServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Laravel\\Dusk\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Taylor Otwell",
|
||||
"email": "taylor@laravel.com"
|
||||
}
|
||||
],
|
||||
"description": "Laravel Dusk provides simple end-to-end testing and browser automation.",
|
||||
"keywords": [
|
||||
"laravel",
|
||||
"testing",
|
||||
"webdriver"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/laravel/dusk/issues",
|
||||
"source": "https://github.com/laravel/dusk/tree/v8.3.4"
|
||||
},
|
||||
"time": "2025-11-20T16:26:16+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/pint",
|
||||
"version": "v1.25.1",
|
||||
@@ -6985,6 +7648,72 @@
|
||||
},
|
||||
"time": "2022-02-21T01:04:05+00:00"
|
||||
},
|
||||
{
|
||||
"name": "php-webdriver/webdriver",
|
||||
"version": "1.15.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-webdriver/php-webdriver.git",
|
||||
"reference": "998e499b786805568deaf8cbf06f4044f05d91bf"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/998e499b786805568deaf8cbf06f4044f05d91bf",
|
||||
"reference": "998e499b786805568deaf8cbf06f4044f05d91bf",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-curl": "*",
|
||||
"ext-json": "*",
|
||||
"ext-zip": "*",
|
||||
"php": "^7.3 || ^8.0",
|
||||
"symfony/polyfill-mbstring": "^1.12",
|
||||
"symfony/process": "^5.0 || ^6.0 || ^7.0"
|
||||
},
|
||||
"replace": {
|
||||
"facebook/webdriver": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"ergebnis/composer-normalize": "^2.20.0",
|
||||
"ondram/ci-detector": "^4.0",
|
||||
"php-coveralls/php-coveralls": "^2.4",
|
||||
"php-mock/php-mock-phpunit": "^2.0",
|
||||
"php-parallel-lint/php-parallel-lint": "^1.2",
|
||||
"phpunit/phpunit": "^9.3",
|
||||
"squizlabs/php_codesniffer": "^3.5",
|
||||
"symfony/var-dumper": "^5.0 || ^6.0 || ^7.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-SimpleXML": "For Firefox profile creation"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"lib/Exception/TimeoutException.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Facebook\\WebDriver\\": "lib/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "A PHP client for Selenium WebDriver. Previously facebook/webdriver.",
|
||||
"homepage": "https://github.com/php-webdriver/php-webdriver",
|
||||
"keywords": [
|
||||
"Chromedriver",
|
||||
"geckodriver",
|
||||
"php",
|
||||
"selenium",
|
||||
"webdriver"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/php-webdriver/php-webdriver/issues",
|
||||
"source": "https://github.com/php-webdriver/php-webdriver/tree/1.15.2"
|
||||
},
|
||||
"time": "2024-11-21T15:12:59+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
"version": "10.1.16",
|
||||
@@ -8878,12 +9607,12 @@
|
||||
],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": {},
|
||||
"stability-flags": [],
|
||||
"prefer-stable": true,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
"php": "^8.1"
|
||||
},
|
||||
"platform-dev": {},
|
||||
"plugin-api-version": "2.9.0"
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "2.6.0"
|
||||
}
|
||||
|
||||
161
config/accounting_mapping.php
Normal file
161
config/accounting_mapping.php
Normal file
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Excel to System Account Code Mapping
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This configuration maps Excel accounting codes to system account codes
|
||||
| for importing accounting data from Excel files.
|
||||
|
|
||||
*/
|
||||
|
||||
'excel_to_system' => [
|
||||
// Assets (1000 series)
|
||||
'1000' => '1101', // 現金及約當現金 → 現金
|
||||
'1100' => '1102', // 庫存現金 → 零用金
|
||||
'1101' => '1201', // 銀行存款 → 銀行存款
|
||||
'1107' => '1201', // 其他現金等價物 → 銀行存款
|
||||
|
||||
// Income (4000 series)
|
||||
'4100' => '4201', // 一般捐款收入 → 捐贈收入
|
||||
'4310' => '4102', // 入會費 → 入會費收入
|
||||
'4101' => '4101', // 會費收入 → 會費收入 (direct match)
|
||||
|
||||
// Expenses (5000 series)
|
||||
// 5100 業務費用需要根據描述細分,見下方 expense_keywords
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Expense Classification Keywords
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Excel 中的 5100 業務費用需要根據描述關鍵字分類到具體科目
|
||||
|
|
||||
*/
|
||||
|
||||
'expense_keywords' => [
|
||||
// 5206 - 旅運費
|
||||
[
|
||||
'keywords' => ['交通', '車費', '計程車', '高鐵', '台鐵', '客運', '機票', '油資', '停車'],
|
||||
'account_code' => '5206',
|
||||
'account_name' => '旅運費',
|
||||
],
|
||||
|
||||
// 5209 - 會議費
|
||||
[
|
||||
'keywords' => ['會場', '場地', '清潔', '點心', '餐', '便當', '茶水', '會議'],
|
||||
'account_code' => '5209',
|
||||
'account_name' => '會議費',
|
||||
],
|
||||
|
||||
// 5203 - 郵電費
|
||||
[
|
||||
'keywords' => ['郵寄', '郵資', '郵票', '快遞', '宅配', '電話', '網路', '通訊', '電信'],
|
||||
'account_code' => '5203',
|
||||
'account_name' => '郵電費',
|
||||
],
|
||||
|
||||
// 5205 - 印刷費
|
||||
[
|
||||
'keywords' => ['影印', '列印', '印刷', '裝訂', '海報', '傳單', '文宣'],
|
||||
'account_code' => '5205',
|
||||
'account_name' => '印刷費',
|
||||
],
|
||||
|
||||
// 5204 - 文具用品
|
||||
[
|
||||
'keywords' => ['文具', '用品', '紙張', '筆', '資料夾', '辦公用品'],
|
||||
'account_code' => '5204',
|
||||
'account_name' => '文具用品',
|
||||
],
|
||||
|
||||
// 5202 - 水電費
|
||||
[
|
||||
'keywords' => ['水費', '電費', '水電'],
|
||||
'account_code' => '5202',
|
||||
'account_name' => '水電費',
|
||||
],
|
||||
|
||||
// 5201 - 租金支出
|
||||
[
|
||||
'keywords' => ['租金', '房租', '場租'],
|
||||
'account_code' => '5201',
|
||||
'account_name' => '租金支出',
|
||||
],
|
||||
|
||||
// 5208 - 修繕費
|
||||
[
|
||||
'keywords' => ['修繕', '維修', '修理', '保養'],
|
||||
'account_code' => '5208',
|
||||
'account_name' => '修繕費',
|
||||
],
|
||||
|
||||
// 5212 - 廣告宣傳費
|
||||
[
|
||||
'keywords' => ['廣告', '宣傳', '行銷', '推廣'],
|
||||
'account_code' => '5212',
|
||||
'account_name' => '廣告宣傳費',
|
||||
],
|
||||
|
||||
// 5213 - 專案活動費
|
||||
[
|
||||
'keywords' => ['活動', '專案', '講座', '課程', '研習', '訓練'],
|
||||
'account_code' => '5213',
|
||||
'account_name' => '專案活動費',
|
||||
],
|
||||
|
||||
// 5211 - 交際費
|
||||
[
|
||||
'keywords' => ['交際', '禮品', '贈品', '紀念品'],
|
||||
'account_code' => '5211',
|
||||
'account_name' => '交際費',
|
||||
],
|
||||
|
||||
// 5304 - 銀行手續費
|
||||
[
|
||||
'keywords' => ['手續費', '匯費', '轉帳費'],
|
||||
'account_code' => '5304',
|
||||
'account_name' => '銀行手續費',
|
||||
],
|
||||
|
||||
// 5308 - 資訊系統費
|
||||
[
|
||||
'keywords' => ['軟體', '系統', '網站', 'domain', '主機', '雲端'],
|
||||
'account_code' => '5308',
|
||||
'account_name' => '資訊系統費',
|
||||
],
|
||||
|
||||
// Default: 5901 雜項支出 (if no keywords match)
|
||||
[
|
||||
'keywords' => [],
|
||||
'account_code' => '5901',
|
||||
'account_name' => '雜項支出',
|
||||
'is_default' => true,
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Cash Account
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Default cash account for generating journal entries
|
||||
|
|
||||
*/
|
||||
|
||||
'cash_account_code' => '1101', // 現金
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Bank Account
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Default bank account
|
||||
|
|
||||
*/
|
||||
|
||||
'bank_account_code' => '1201', // 銀行存款
|
||||
];
|
||||
40
database/factories/AuditLogFactory.php
Normal file
40
database/factories/AuditLogFactory.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\AuditLog;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
class AuditLogFactory extends Factory
|
||||
{
|
||||
protected $model = AuditLog::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'user_id' => User::factory(),
|
||||
'action' => $this->faker->randomElement([
|
||||
'member_created',
|
||||
'member_status_changed',
|
||||
'payment_approved',
|
||||
'payment_rejected',
|
||||
'finance_document_created',
|
||||
'finance_document_approved',
|
||||
'user_login',
|
||||
'role_assigned',
|
||||
]),
|
||||
'auditable_type' => $this->faker->randomElement([
|
||||
'App\Models\Member',
|
||||
'App\Models\MembershipPayment',
|
||||
'App\Models\FinanceDocument',
|
||||
'App\Models\User',
|
||||
]),
|
||||
'auditable_id' => $this->faker->numberBetween(1, 100),
|
||||
'metadata' => [
|
||||
'ip_address' => $this->faker->ipv4(),
|
||||
'user_agent' => $this->faker->userAgent(),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
32
database/factories/BankReconciliationFactory.php
Normal file
32
database/factories/BankReconciliationFactory.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\BankReconciliation;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
class BankReconciliationFactory extends Factory
|
||||
{
|
||||
protected $model = BankReconciliation::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
$bankBalance = $this->faker->numberBetween(50000, 500000);
|
||||
$bookBalance = $bankBalance + $this->faker->numberBetween(-5000, 5000);
|
||||
|
||||
return [
|
||||
'reconciliation_month' => now()->startOfMonth(),
|
||||
'bank_statement_date' => now(),
|
||||
'bank_statement_balance' => $bankBalance,
|
||||
'system_book_balance' => $bookBalance,
|
||||
'outstanding_checks' => [],
|
||||
'deposits_in_transit' => [],
|
||||
'bank_charges' => [],
|
||||
'prepared_by_cashier_id' => User::factory(),
|
||||
'prepared_at' => now(),
|
||||
'reconciliation_status' => 'pending',
|
||||
'discrepancy_amount' => abs($bankBalance - $bookBalance),
|
||||
];
|
||||
}
|
||||
}
|
||||
16
database/factories/BudgetCategoryFactory.php
Normal file
16
database/factories/BudgetCategoryFactory.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
class BudgetCategoryFactory extends Factory
|
||||
{
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'name' => $this->faker->unique()->words(2, true),
|
||||
'description' => $this->faker->sentence(),
|
||||
];
|
||||
}
|
||||
}
|
||||
22
database/factories/ChartOfAccountFactory.php
Normal file
22
database/factories/ChartOfAccountFactory.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\ChartOfAccount;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
class ChartOfAccountFactory extends Factory
|
||||
{
|
||||
protected $model = ChartOfAccount::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'code' => $this->faker->unique()->numerify('####'),
|
||||
'name' => $this->faker->words(3, true),
|
||||
'type' => $this->faker->randomElement(['asset', 'liability', 'equity', 'revenue', 'expense']),
|
||||
'description' => $this->faker->sentence(),
|
||||
'is_active' => true,
|
||||
];
|
||||
}
|
||||
}
|
||||
23
database/factories/DocumentCategoryFactory.php
Normal file
23
database/factories/DocumentCategoryFactory.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\DocumentCategory;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
class DocumentCategoryFactory extends Factory
|
||||
{
|
||||
protected $model = DocumentCategory::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'name' => $this->faker->unique()->words(2, true),
|
||||
'slug' => $this->faker->unique()->slug(2),
|
||||
'description' => $this->faker->sentence(),
|
||||
'icon' => $this->faker->randomElement(['📄', '📁', '📋', '📊', '📈']),
|
||||
'sort_order' => $this->faker->numberBetween(1, 100),
|
||||
'default_access_level' => $this->faker->randomElement(['public', 'members', 'admin', 'board']),
|
||||
];
|
||||
}
|
||||
}
|
||||
31
database/factories/DocumentFactory.php
Normal file
31
database/factories/DocumentFactory.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\Document;
|
||||
use App\Models\DocumentCategory;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class DocumentFactory extends Factory
|
||||
{
|
||||
protected $model = Document::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'document_category_id' => DocumentCategory::factory(),
|
||||
'title' => $this->faker->sentence(3),
|
||||
'document_number' => 'DOC-'.now()->format('Y').'-'.str_pad(rand(1, 9999), 4, '0', STR_PAD_LEFT),
|
||||
'description' => $this->faker->paragraph(),
|
||||
'public_uuid' => (string) Str::uuid(),
|
||||
'access_level' => $this->faker->randomElement(['public', 'members', 'admin']),
|
||||
'status' => 'active',
|
||||
'created_by_user_id' => User::factory(),
|
||||
'view_count' => $this->faker->numberBetween(0, 100),
|
||||
'download_count' => $this->faker->numberBetween(0, 50),
|
||||
'version_count' => 1,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* 移除 is_admin 欄位,統一使用 Spatie Permission 的 admin 角色進行權限管理
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn('is_admin');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->boolean('is_admin')->default(false)->after('email');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
if (Schema::hasTable('cashier_ledger_entries')) {
|
||||
return;
|
||||
}
|
||||
|
||||
Schema::create('cashier_ledger_entries', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('finance_document_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->date('entry_date');
|
||||
$table->string('entry_type'); // receipt, payment
|
||||
$table->string('payment_method'); // bank_transfer, check, cash
|
||||
$table->string('bank_account')->nullable();
|
||||
$table->decimal('amount', 12, 2);
|
||||
$table->decimal('balance_before', 12, 2)->default(0);
|
||||
$table->decimal('balance_after', 12, 2)->default(0);
|
||||
$table->string('receipt_number')->nullable();
|
||||
$table->string('transaction_reference')->nullable();
|
||||
$table->foreignId('recorded_by_cashier_id')->constrained('users');
|
||||
$table->timestamp('recorded_at');
|
||||
$table->text('notes')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->index('entry_date');
|
||||
$table->index('entry_type');
|
||||
$table->index('bank_account');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('cashier_ledger_entries');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('announcements', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
// 基本資訊
|
||||
$table->string('title');
|
||||
$table->text('content');
|
||||
|
||||
// 狀態管理
|
||||
$table->enum('status', ['draft', 'published', 'archived'])->default('draft');
|
||||
|
||||
// 顯示控制
|
||||
$table->boolean('is_pinned')->default(false);
|
||||
$table->integer('display_order')->default(0);
|
||||
|
||||
// 訪問控制
|
||||
$table->enum('access_level', ['public', 'members', 'board', 'admin'])->default('members');
|
||||
|
||||
// 時間控制
|
||||
$table->timestamp('published_at')->nullable();
|
||||
$table->timestamp('expires_at')->nullable();
|
||||
$table->timestamp('archived_at')->nullable();
|
||||
|
||||
// 統計
|
||||
$table->integer('view_count')->default(0);
|
||||
|
||||
// 用戶關聯
|
||||
$table->foreignId('created_by_user_id')->constrained('users')->onDelete('cascade');
|
||||
$table->foreignId('last_updated_by_user_id')->nullable()->constrained('users')->onDelete('set null');
|
||||
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
|
||||
// 索引
|
||||
$table->index('status');
|
||||
$table->index('access_level');
|
||||
$table->index('published_at');
|
||||
$table->index('expires_at');
|
||||
$table->index(['is_pinned', 'display_order']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('announcements');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('accounting_entries', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('finance_document_id')->constrained()->onDelete('cascade');
|
||||
$table->foreignId('chart_of_account_id')->constrained();
|
||||
$table->enum('entry_type', ['debit', 'credit']);
|
||||
$table->decimal('amount', 15, 2);
|
||||
$table->date('entry_date');
|
||||
$table->text('description')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
// Indexes for performance
|
||||
$table->index(['finance_document_id', 'entry_type']);
|
||||
$table->index(['chart_of_account_id', 'entry_date']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('accounting_entries');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('board_meetings', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->date('meeting_date');
|
||||
$table->string('title');
|
||||
$table->text('notes')->nullable();
|
||||
$table->enum('status', ['scheduled', 'completed', 'cancelled'])->default('scheduled');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('board_meetings');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('finance_documents', function (Blueprint $table) {
|
||||
// 只新增尚未存在的欄位
|
||||
if (!Schema::hasColumn('finance_documents', 'accountant_recorded_by_id')) {
|
||||
$table->unsignedBigInteger('accountant_recorded_by_id')->nullable();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('finance_documents', function (Blueprint $table) {
|
||||
if (Schema::hasColumn('finance_documents', 'accountant_recorded_by_id')) {
|
||||
$table->dropColumn('accountant_recorded_by_id');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('finance_documents', function (Blueprint $table) {
|
||||
if (Schema::hasColumn('finance_documents', 'request_type')) {
|
||||
$table->dropColumn('request_type');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('finance_documents', function (Blueprint $table) {
|
||||
if (!Schema::hasColumn('finance_documents', 'request_type')) {
|
||||
$table->string('request_type')->nullable()->after('amount');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('incomes', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
// 基本資訊
|
||||
$table->string('income_number')->unique(); // 收入編號:INC-2025-0001
|
||||
$table->string('title'); // 收入標題
|
||||
$table->text('description')->nullable(); // 說明
|
||||
$table->date('income_date'); // 收入日期
|
||||
$table->decimal('amount', 12, 2); // 金額
|
||||
|
||||
// 收入分類
|
||||
$table->string('income_type'); // 收入類型
|
||||
$table->foreignId('chart_of_account_id') // 會計科目
|
||||
->constrained('chart_of_accounts');
|
||||
|
||||
// 付款資訊
|
||||
$table->string('payment_method'); // 付款方式
|
||||
$table->string('bank_account')->nullable(); // 銀行帳戶
|
||||
$table->string('payer_name')->nullable(); // 付款人姓名
|
||||
$table->string('receipt_number')->nullable(); // 收據編號
|
||||
$table->string('transaction_reference')->nullable(); // 銀行交易參考號
|
||||
$table->string('attachment_path')->nullable(); // 附件路徑
|
||||
|
||||
// 會員關聯
|
||||
$table->foreignId('member_id')->nullable()
|
||||
->constrained()->nullOnDelete();
|
||||
|
||||
// 審核流程
|
||||
$table->string('status')->default('pending'); // pending, confirmed, cancelled
|
||||
|
||||
// 出納記錄
|
||||
$table->foreignId('recorded_by_cashier_id')
|
||||
->constrained('users');
|
||||
$table->timestamp('recorded_at');
|
||||
|
||||
// 會計確認
|
||||
$table->foreignId('confirmed_by_accountant_id')->nullable()
|
||||
->constrained('users');
|
||||
$table->timestamp('confirmed_at')->nullable();
|
||||
|
||||
// 關聯出納日記帳
|
||||
$table->foreignId('cashier_ledger_entry_id')->nullable()
|
||||
->constrained('cashier_ledger_entries')->nullOnDelete();
|
||||
|
||||
$table->text('notes')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
// 索引
|
||||
$table->index('income_date');
|
||||
$table->index('income_type');
|
||||
$table->index('status');
|
||||
$table->index(['member_id', 'income_type']);
|
||||
});
|
||||
|
||||
// 在 accounting_entries 表新增 income_id 欄位
|
||||
Schema::table('accounting_entries', function (Blueprint $table) {
|
||||
if (!Schema::hasColumn('accounting_entries', 'income_id')) {
|
||||
$table->foreignId('income_id')->nullable()
|
||||
->after('finance_document_id')
|
||||
->constrained('incomes')->nullOnDelete();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('accounting_entries', function (Blueprint $table) {
|
||||
if (Schema::hasColumn('accounting_entries', 'income_id')) {
|
||||
$table->dropForeign(['income_id']);
|
||||
$table->dropColumn('income_id');
|
||||
}
|
||||
});
|
||||
|
||||
Schema::dropIfExists('incomes');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('members', function (Blueprint $table) {
|
||||
// 身心障礙手冊相關欄位
|
||||
$table->string('disability_certificate_path')->nullable()->after('membership_type');
|
||||
$table->string('disability_certificate_status')->nullable()->after('disability_certificate_path');
|
||||
$table->foreignId('disability_verified_by')->nullable()->after('disability_certificate_status')
|
||||
->constrained('users')->nullOnDelete();
|
||||
$table->timestamp('disability_verified_at')->nullable()->after('disability_verified_by');
|
||||
$table->text('disability_rejection_reason')->nullable()->after('disability_verified_at');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('members', function (Blueprint $table) {
|
||||
$table->dropForeign(['disability_verified_by']);
|
||||
$table->dropColumn([
|
||||
'disability_certificate_path',
|
||||
'disability_certificate_status',
|
||||
'disability_verified_by',
|
||||
'disability_verified_at',
|
||||
'disability_rejection_reason',
|
||||
]);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('membership_payments', function (Blueprint $table) {
|
||||
// 會費類型欄位
|
||||
$table->string('fee_type')->default('entrance_fee')->after('member_id');
|
||||
$table->decimal('base_amount', 10, 2)->nullable()->after('amount');
|
||||
$table->decimal('discount_amount', 10, 2)->default(0)->after('base_amount');
|
||||
$table->decimal('final_amount', 10, 2)->nullable()->after('discount_amount');
|
||||
$table->boolean('disability_discount')->default(false)->after('final_amount');
|
||||
});
|
||||
|
||||
// 為現有記錄設定預設值
|
||||
\DB::statement("UPDATE membership_payments SET base_amount = amount, final_amount = amount WHERE base_amount IS NULL");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('membership_payments', function (Blueprint $table) {
|
||||
$table->dropColumn([
|
||||
'fee_type',
|
||||
'base_amount',
|
||||
'discount_amount',
|
||||
'final_amount',
|
||||
'disability_discount',
|
||||
]);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -484,7 +484,10 @@ class ChartOfAccountSeeder extends Seeder
|
||||
];
|
||||
|
||||
foreach ($accounts as $account) {
|
||||
ChartOfAccount::create($account);
|
||||
ChartOfAccount::firstOrCreate(
|
||||
['account_code' => $account['account_code']],
|
||||
$account
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,48 +11,85 @@ class FinancialWorkflowPermissionsSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*
|
||||
* 此 Seeder 建立統一的角色與權限系統,整合:
|
||||
* - 財務工作流程權限
|
||||
* - 會員繳費審核權限(原 PaymentVerificationRolesSeeder)
|
||||
* - 基礎角色(原 RoleSeeder)
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
// Create permissions for financial workflow
|
||||
$permissions = [
|
||||
// Approval Stage Permissions
|
||||
'approve_finance_cashier' => '出納審核財務申請單(第一階段)',
|
||||
'approve_finance_accountant' => '會計審核財務申請單(第二階段)',
|
||||
'approve_finance_chair' => '理事長審核財務申請單(第三階段)',
|
||||
'approve_finance_board' => '理事會審核大額財務申請(大於50,000)',
|
||||
// ===== 會員繳費審核權限(原 PaymentVerificationRolesSeeder) =====
|
||||
'verify_payments_cashier' => '出納審核會員繳費(第一階段)',
|
||||
'verify_payments_accountant' => '會計審核會員繳費(第二階段)',
|
||||
'verify_payments_chair' => '理事長審核會員繳費(第三階段)',
|
||||
'activate_memberships' => '啟用會員帳號',
|
||||
'view_payment_verifications' => '查看繳費審核儀表板',
|
||||
|
||||
// Payment Stage Permissions
|
||||
// ===== 財務申請單審核權限(新工作流程) =====
|
||||
'approve_finance_secretary' => '秘書長審核財務申請單(第一階段)',
|
||||
'approve_finance_chair' => '理事長審核財務申請單(第二階段:中額以上)',
|
||||
'approve_finance_board' => '董理事會審核財務申請單(第三階段:大額)',
|
||||
// Legacy permissions
|
||||
'approve_finance_cashier' => '出納審核財務申請單(舊流程)',
|
||||
'approve_finance_accountant' => '會計審核財務申請單(舊流程)',
|
||||
|
||||
// ===== 出帳確認權限 =====
|
||||
'confirm_disbursement_requester' => '申請人確認領款',
|
||||
'confirm_disbursement_cashier' => '出納確認出帳',
|
||||
|
||||
// ===== 入帳確認權限 =====
|
||||
'confirm_recording_accountant' => '會計確認入帳',
|
||||
|
||||
// ===== 收入管理權限 =====
|
||||
'view_incomes' => '查看收入記錄',
|
||||
'record_income' => '記錄收入(出納)',
|
||||
'confirm_income' => '確認收入(會計)',
|
||||
'cancel_income' => '取消收入',
|
||||
'export_incomes' => '匯出收入報表',
|
||||
'view_income_statistics' => '查看收入統計',
|
||||
|
||||
// ===== 付款階段權限 =====
|
||||
'create_payment_order' => '會計製作付款單',
|
||||
'verify_payment_order' => '出納覆核付款單',
|
||||
'execute_payment' => '出納執行付款',
|
||||
'upload_payment_receipt' => '上傳付款憑證',
|
||||
|
||||
// Recording Stage Permissions
|
||||
// ===== 記錄階段權限 =====
|
||||
'record_cashier_ledger' => '出納記錄現金簿',
|
||||
'record_accounting_transaction' => '會計記錄會計分錄',
|
||||
'view_cashier_ledger' => '查看出納現金簿',
|
||||
'view_accounting_transactions' => '查看會計分錄',
|
||||
|
||||
// Reconciliation Permissions
|
||||
// ===== 銀行調節權限 =====
|
||||
'prepare_bank_reconciliation' => '出納製作銀行調節表',
|
||||
'review_bank_reconciliation' => '會計覆核銀行調節表',
|
||||
'approve_bank_reconciliation' => '主管核准銀行調節表',
|
||||
|
||||
// General Finance Document Permissions
|
||||
// ===== 財務文件權限 =====
|
||||
'view_finance_documents' => '查看財務申請單',
|
||||
'create_finance_documents' => '建立財務申請單',
|
||||
'edit_finance_documents' => '編輯財務申請單',
|
||||
'delete_finance_documents' => '刪除財務申請單',
|
||||
|
||||
// Chart of Accounts & Budget Permissions
|
||||
// ===== 會計科目與預算權限 =====
|
||||
'assign_chart_of_account' => '指定會計科目',
|
||||
'assign_budget_item' => '指定預算項目',
|
||||
|
||||
// Dashboard & Reports Permissions
|
||||
// ===== 儀表板與報表權限 =====
|
||||
'view_finance_dashboard' => '查看財務儀表板',
|
||||
'view_finance_reports' => '查看財務報表',
|
||||
'export_finance_reports' => '匯出財務報表',
|
||||
|
||||
// ===== 公告系統權限 =====
|
||||
'view_announcements' => '查看公告',
|
||||
'create_announcements' => '建立公告',
|
||||
'edit_announcements' => '編輯公告',
|
||||
'delete_announcements' => '刪除公告',
|
||||
'publish_announcements' => '發布公告',
|
||||
'manage_all_announcements' => '管理所有公告',
|
||||
];
|
||||
|
||||
foreach ($permissions as $name => $description) {
|
||||
@@ -63,81 +100,175 @@ class FinancialWorkflowPermissionsSeeder extends Seeder
|
||||
$this->command->info("Permission created: {$name}");
|
||||
}
|
||||
|
||||
// Create roles for financial workflow
|
||||
// ===== 建立基礎角色(原 RoleSeeder) =====
|
||||
$baseRoles = [
|
||||
'admin' => '系統管理員 - 擁有系統所有權限,負責使用者管理、系統設定與維護',
|
||||
'staff' => '工作人員 - 一般協會工作人員,可檢視文件與協助行政事務',
|
||||
];
|
||||
|
||||
foreach ($baseRoles as $roleName => $description) {
|
||||
Role::updateOrCreate(
|
||||
['name' => $roleName, 'guard_name' => 'web'],
|
||||
['description' => $description]
|
||||
);
|
||||
$this->command->info("Base role created: {$roleName}");
|
||||
}
|
||||
|
||||
// ===== 建立財務與會員管理角色 =====
|
||||
$roles = [
|
||||
'secretary_general' => [
|
||||
'permissions' => [
|
||||
// 財務申請單審核(新工作流程第一階段)
|
||||
'approve_finance_secretary',
|
||||
// 一般
|
||||
'view_finance_documents',
|
||||
'view_finance_dashboard',
|
||||
'view_finance_reports',
|
||||
// 公告系統
|
||||
'view_announcements',
|
||||
'create_announcements',
|
||||
'edit_announcements',
|
||||
'delete_announcements',
|
||||
'publish_announcements',
|
||||
'manage_all_announcements',
|
||||
],
|
||||
'description' => '秘書長 - 協會行政負責人,負責初審所有財務申請',
|
||||
],
|
||||
'finance_cashier' => [
|
||||
'permissions' => [
|
||||
// Approval stage
|
||||
// 會員繳費審核(原 payment_cashier)
|
||||
'verify_payments_cashier',
|
||||
'view_payment_verifications',
|
||||
// 財務申請單審核(舊流程,保留)
|
||||
'approve_finance_cashier',
|
||||
// Payment stage
|
||||
// 出帳確認(新工作流程)
|
||||
'confirm_disbursement_cashier',
|
||||
// 收入管理
|
||||
'view_incomes',
|
||||
'record_income',
|
||||
// 付款階段
|
||||
'verify_payment_order',
|
||||
'execute_payment',
|
||||
'upload_payment_receipt',
|
||||
// Recording stage
|
||||
// 記錄階段
|
||||
'record_cashier_ledger',
|
||||
'view_cashier_ledger',
|
||||
// Reconciliation
|
||||
// 銀行調節
|
||||
'prepare_bank_reconciliation',
|
||||
// General
|
||||
// 一般
|
||||
'view_finance_documents',
|
||||
'view_finance_dashboard',
|
||||
// 公告系統
|
||||
'view_announcements',
|
||||
'create_announcements',
|
||||
'edit_announcements',
|
||||
'delete_announcements',
|
||||
'publish_announcements',
|
||||
],
|
||||
'description' => '出納 - 管錢(覆核付款單、執行付款、記錄現金簿、製作銀行調節表)',
|
||||
'description' => '出納 - 負責現金收付、銀行調節表製作、出帳確認、記錄收入',
|
||||
],
|
||||
'finance_accountant' => [
|
||||
'permissions' => [
|
||||
// Approval stage
|
||||
// 會員繳費審核(原 payment_accountant)
|
||||
'verify_payments_accountant',
|
||||
'view_payment_verifications',
|
||||
// 財務申請單審核(舊流程,保留)
|
||||
'approve_finance_accountant',
|
||||
// Payment stage
|
||||
// 入帳確認(新工作流程)
|
||||
'confirm_recording_accountant',
|
||||
// 收入管理
|
||||
'view_incomes',
|
||||
'confirm_income',
|
||||
'cancel_income',
|
||||
'export_incomes',
|
||||
'view_income_statistics',
|
||||
// 付款階段
|
||||
'create_payment_order',
|
||||
// Recording stage
|
||||
// 記錄階段
|
||||
'record_accounting_transaction',
|
||||
'view_accounting_transactions',
|
||||
// Reconciliation
|
||||
// 銀行調節
|
||||
'review_bank_reconciliation',
|
||||
// Chart of accounts & budget
|
||||
// 會計科目與預算
|
||||
'assign_chart_of_account',
|
||||
'assign_budget_item',
|
||||
// General
|
||||
// 一般
|
||||
'view_finance_documents',
|
||||
'view_finance_dashboard',
|
||||
'view_finance_reports',
|
||||
'export_finance_reports',
|
||||
// 公告系統
|
||||
'view_announcements',
|
||||
'create_announcements',
|
||||
'edit_announcements',
|
||||
'delete_announcements',
|
||||
'publish_announcements',
|
||||
],
|
||||
'description' => '會計 - 管帳(製作付款單、記錄會計分錄、覆核銀行調節表、指定會計科目)',
|
||||
'description' => '會計 - 負責會計傳票製作、財務報表編製、入帳確認、確認收入',
|
||||
],
|
||||
'finance_chair' => [
|
||||
'permissions' => [
|
||||
// Approval stage
|
||||
// 會員繳費審核(原 payment_chair)
|
||||
'verify_payments_chair',
|
||||
'view_payment_verifications',
|
||||
// 財務申請單審核
|
||||
'approve_finance_chair',
|
||||
// Reconciliation
|
||||
// 銀行調節
|
||||
'approve_bank_reconciliation',
|
||||
// General
|
||||
// 一般
|
||||
'view_finance_documents',
|
||||
'view_finance_dashboard',
|
||||
'view_finance_reports',
|
||||
'export_finance_reports',
|
||||
// 公告系統
|
||||
'view_announcements',
|
||||
'create_announcements',
|
||||
'edit_announcements',
|
||||
'delete_announcements',
|
||||
'publish_announcements',
|
||||
'manage_all_announcements',
|
||||
],
|
||||
'description' => '理事長 - 審核中大額財務申請、核准銀行調節表',
|
||||
'description' => '理事長 - 協會負責人,負責核決重大財務支出與會員繳費最終審核',
|
||||
],
|
||||
'finance_board_member' => [
|
||||
'permissions' => [
|
||||
// Approval stage (for large amounts)
|
||||
// 大額審核
|
||||
'approve_finance_board',
|
||||
// General
|
||||
// 一般
|
||||
'view_finance_documents',
|
||||
'view_finance_dashboard',
|
||||
'view_finance_reports',
|
||||
// 公告系統
|
||||
'view_announcements',
|
||||
'create_announcements',
|
||||
'edit_announcements',
|
||||
'delete_announcements',
|
||||
'publish_announcements',
|
||||
],
|
||||
'description' => '理事 - 審核大額財務申請(大於50,000)',
|
||||
'description' => '理事 - 理事會成員,協助監督協會運作與審核特定議案',
|
||||
],
|
||||
'finance_requester' => [
|
||||
'permissions' => [
|
||||
'view_finance_documents',
|
||||
'create_finance_documents',
|
||||
'edit_finance_documents',
|
||||
// 出帳確認(新工作流程)
|
||||
'confirm_disbursement_requester',
|
||||
],
|
||||
'description' => '財務申請人 - 可建立和編輯自己的財務申請單',
|
||||
'description' => '財務申請人 - 一般有權申請款項之人員(如活動負責人),可確認領款',
|
||||
],
|
||||
'membership_manager' => [
|
||||
'permissions' => [
|
||||
'activate_memberships',
|
||||
'view_payment_verifications',
|
||||
// 公告系統
|
||||
'view_announcements',
|
||||
'create_announcements',
|
||||
'edit_announcements',
|
||||
'delete_announcements',
|
||||
'publish_announcements',
|
||||
],
|
||||
'description' => '會員管理員 - 專責處理會員入會審核、資料維護與會籍管理',
|
||||
],
|
||||
];
|
||||
|
||||
@@ -153,24 +284,32 @@ class FinancialWorkflowPermissionsSeeder extends Seeder
|
||||
$this->command->info("Role created: {$roleName} with permissions: " . implode(', ', $roleData['permissions']));
|
||||
}
|
||||
|
||||
// Assign all financial workflow permissions to admin role (if exists)
|
||||
// Assign all permissions to admin role
|
||||
$adminRole = Role::where('name', 'admin')->first();
|
||||
if ($adminRole) {
|
||||
$adminRole->givePermissionTo(array_keys($permissions));
|
||||
$this->command->info("Admin role updated with all financial workflow permissions");
|
||||
$this->command->info("Admin role updated with all permissions");
|
||||
}
|
||||
|
||||
$this->command->info("\n=== Financial Workflow Roles & Permissions Created ===");
|
||||
$this->command->info("Roles created:");
|
||||
$this->command->info("1. finance_cashier - 出納(管錢)");
|
||||
$this->command->info("2. finance_accountant - 會計(管帳)");
|
||||
$this->command->info("3. finance_chair - 理事長");
|
||||
$this->command->info("4. finance_board_member - 理事");
|
||||
$this->command->info("5. finance_requester - 財務申請人");
|
||||
$this->command->info("\nWorkflow stages:");
|
||||
$this->command->info("1. Approval Stage: Cashier → Accountant → Chair (→ Board for large amounts)");
|
||||
$this->command->info("2. Payment Stage: Accountant creates order → Cashier verifies → Cashier executes");
|
||||
$this->command->info("3. Recording Stage: Cashier records ledger + Accountant records transactions");
|
||||
$this->command->info("4. Reconciliation: Cashier prepares → Accountant reviews → Chair approves");
|
||||
$this->command->info("\n=== 統一角色系統建立完成 ===");
|
||||
$this->command->info("基礎角色:");
|
||||
$this->command->info(" - admin - 系統管理員");
|
||||
$this->command->info(" - staff - 工作人員");
|
||||
$this->command->info("\n財務角色:");
|
||||
$this->command->info(" - secretary_general - 秘書長(新增:財務申請初審)");
|
||||
$this->command->info(" - finance_cashier - 出納(出帳確認)");
|
||||
$this->command->info(" - finance_accountant - 會計(入帳確認)");
|
||||
$this->command->info(" - finance_chair - 理事長(中額以上審核)");
|
||||
$this->command->info(" - finance_board_member - 理事(大額審核)");
|
||||
$this->command->info(" - finance_requester - 財務申請人(可確認領款)");
|
||||
$this->command->info("\n會員管理角色:");
|
||||
$this->command->info(" - membership_manager - 會員管理員");
|
||||
$this->command->info("\n新財務申請審核工作流程:");
|
||||
$this->command->info(" 審核階段:");
|
||||
$this->command->info(" 小額 (< 5,000): secretary_general");
|
||||
$this->command->info(" 中額 (5,000-50,000): secretary_general → finance_chair");
|
||||
$this->command->info(" 大額 (> 50,000): secretary_general → finance_chair → finance_board_member");
|
||||
$this->command->info(" 出帳階段: finance_requester(申請人確認) + finance_cashier(出納確認)");
|
||||
$this->command->info(" 入帳階段: finance_accountant(會計入帳)");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
class PaymentVerificationRolesSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
// Create permissions for payment verification workflow
|
||||
$permissions = [
|
||||
'verify_payments_cashier' => 'Verify membership payments as cashier (Tier 1)',
|
||||
'verify_payments_accountant' => 'Verify membership payments as accountant (Tier 2)',
|
||||
'verify_payments_chair' => 'Verify membership payments as chair (Tier 3)',
|
||||
'activate_memberships' => 'Activate member accounts after payment approval',
|
||||
'view_payment_verifications' => 'View payment verification dashboard',
|
||||
];
|
||||
|
||||
foreach ($permissions as $name => $description) {
|
||||
Permission::firstOrCreate(
|
||||
['name' => $name],
|
||||
['guard_name' => 'web']
|
||||
);
|
||||
$this->command->info("Permission created: {$name}");
|
||||
}
|
||||
|
||||
// Create roles for payment verification
|
||||
$roles = [
|
||||
'payment_cashier' => [
|
||||
'permissions' => ['verify_payments_cashier', 'view_payment_verifications'],
|
||||
'description' => 'Cashier - First tier payment verification',
|
||||
],
|
||||
'payment_accountant' => [
|
||||
'permissions' => ['verify_payments_accountant', 'view_payment_verifications'],
|
||||
'description' => 'Accountant - Second tier payment verification',
|
||||
],
|
||||
'payment_chair' => [
|
||||
'permissions' => ['verify_payments_chair', 'view_payment_verifications'],
|
||||
'description' => 'Chair - Final tier payment verification',
|
||||
],
|
||||
'membership_manager' => [
|
||||
'permissions' => ['activate_memberships', 'view_payment_verifications'],
|
||||
'description' => 'Membership Manager - Can activate memberships after approval',
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($roles as $roleName => $roleData) {
|
||||
$role = Role::firstOrCreate(
|
||||
['name' => $roleName],
|
||||
['guard_name' => 'web']
|
||||
);
|
||||
|
||||
// Assign permissions to role
|
||||
$role->syncPermissions($roleData['permissions']);
|
||||
|
||||
$this->command->info("Role created: {$roleName} with permissions: " . implode(', ', $roleData['permissions']));
|
||||
}
|
||||
|
||||
// Assign all payment verification permissions to admin role (if exists)
|
||||
$adminRole = Role::where('name', 'admin')->first();
|
||||
if ($adminRole) {
|
||||
$adminRole->givePermissionTo([
|
||||
'verify_payments_cashier',
|
||||
'verify_payments_accountant',
|
||||
'verify_payments_chair',
|
||||
'activate_memberships',
|
||||
'view_payment_verifications',
|
||||
]);
|
||||
$this->command->info("Admin role updated with all payment verification permissions");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
class RoleSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
$roles = [
|
||||
'admin' => 'Full system administrator',
|
||||
'staff' => 'General staff with access to internal tools',
|
||||
'cashier' => 'Handles payment recording and finance intake',
|
||||
'accountant' => 'Reviews finance docs and approvals',
|
||||
'chair' => 'Board chairperson for final approvals',
|
||||
];
|
||||
|
||||
collect($roles)->each(function ($description, $role) {
|
||||
Role::updateOrCreate(
|
||||
['name' => $role, 'guard_name' => 'web'],
|
||||
['description' => $description]
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -38,8 +38,7 @@ class TestDataSeeder extends Seeder
|
||||
|
||||
// Ensure required seeders have run
|
||||
$this->call([
|
||||
RoleSeeder::class,
|
||||
PaymentVerificationRolesSeeder::class,
|
||||
FinancialWorkflowPermissionsSeeder::class,
|
||||
ChartOfAccountSeeder::class,
|
||||
IssueLabelSeeder::class,
|
||||
]);
|
||||
@@ -86,62 +85,68 @@ class TestDataSeeder extends Seeder
|
||||
$users = [];
|
||||
|
||||
// 1. Super Admin
|
||||
$admin = User::create([
|
||||
'name' => 'Admin User',
|
||||
'email' => 'admin@test.com',
|
||||
'password' => Hash::make('password'),
|
||||
'is_admin' => true,
|
||||
]);
|
||||
$admin = User::firstOrCreate(
|
||||
['email' => 'admin@test.com'],
|
||||
[
|
||||
'name' => 'Admin User',
|
||||
'password' => Hash::make('password'),
|
||||
]
|
||||
);
|
||||
$admin->assignRole('admin');
|
||||
$users['admin'] = $admin;
|
||||
|
||||
// 2. Payment Cashier
|
||||
$cashier = User::create([
|
||||
'name' => 'Cashier User',
|
||||
'email' => 'cashier@test.com',
|
||||
'password' => Hash::make('password'),
|
||||
'is_admin' => true,
|
||||
]);
|
||||
$cashier->assignRole('payment_cashier');
|
||||
// 2. Finance Cashier (整合原 payment_cashier)
|
||||
$cashier = User::firstOrCreate(
|
||||
['email' => 'cashier@test.com'],
|
||||
[
|
||||
'name' => 'Cashier User',
|
||||
'password' => Hash::make('password'),
|
||||
]
|
||||
);
|
||||
$cashier->assignRole('finance_cashier');
|
||||
$users['cashier'] = $cashier;
|
||||
|
||||
// 3. Payment Accountant
|
||||
$accountant = User::create([
|
||||
'name' => 'Accountant User',
|
||||
'email' => 'accountant@test.com',
|
||||
'password' => Hash::make('password'),
|
||||
'is_admin' => true,
|
||||
]);
|
||||
$accountant->assignRole('payment_accountant');
|
||||
// 3. Finance Accountant (整合原 payment_accountant)
|
||||
$accountant = User::firstOrCreate(
|
||||
['email' => 'accountant@test.com'],
|
||||
[
|
||||
'name' => 'Accountant User',
|
||||
'password' => Hash::make('password'),
|
||||
]
|
||||
);
|
||||
$accountant->assignRole('finance_accountant');
|
||||
$users['accountant'] = $accountant;
|
||||
|
||||
// 4. Payment Chair
|
||||
$chair = User::create([
|
||||
'name' => 'Chair User',
|
||||
'email' => 'chair@test.com',
|
||||
'password' => Hash::make('password'),
|
||||
'is_admin' => true,
|
||||
]);
|
||||
$chair->assignRole('payment_chair');
|
||||
// 4. Finance Chair (整合原 payment_chair)
|
||||
$chair = User::firstOrCreate(
|
||||
['email' => 'chair@test.com'],
|
||||
[
|
||||
'name' => 'Chair User',
|
||||
'password' => Hash::make('password'),
|
||||
]
|
||||
);
|
||||
$chair->assignRole('finance_chair');
|
||||
$users['chair'] = $chair;
|
||||
|
||||
// 5. Membership Manager
|
||||
$manager = User::create([
|
||||
'name' => 'Membership Manager',
|
||||
'email' => 'manager@test.com',
|
||||
'password' => Hash::make('password'),
|
||||
'is_admin' => true,
|
||||
]);
|
||||
$manager = User::firstOrCreate(
|
||||
['email' => 'manager@test.com'],
|
||||
[
|
||||
'name' => 'Membership Manager',
|
||||
'password' => Hash::make('password'),
|
||||
]
|
||||
);
|
||||
$manager->assignRole('membership_manager');
|
||||
$users['manager'] = $manager;
|
||||
|
||||
// 6. Regular Member User
|
||||
$member = User::create([
|
||||
'name' => 'Regular Member',
|
||||
'email' => 'member@test.com',
|
||||
'password' => Hash::make('password'),
|
||||
'is_admin' => false,
|
||||
]);
|
||||
$member = User::firstOrCreate(
|
||||
['email' => 'member@test.com'],
|
||||
[
|
||||
'name' => 'Regular Member',
|
||||
'password' => Hash::make('password'),
|
||||
]
|
||||
);
|
||||
$users['member'] = $member;
|
||||
|
||||
return $users;
|
||||
@@ -158,97 +163,107 @@ class TestDataSeeder extends Seeder
|
||||
|
||||
// 5 Pending Members
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
$members[] = Member::create([
|
||||
'user_id' => $i === 0 ? $users['member']->id : null,
|
||||
'full_name' => "待審核會員 {$counter}",
|
||||
'email' => "pending{$counter}@test.com",
|
||||
'phone' => '09' . str_pad($counter, 8, '0', STR_PAD_LEFT),
|
||||
'address_line_1' => "測試地址 {$counter} 號",
|
||||
'city' => $taiwanCities[array_rand($taiwanCities)],
|
||||
'postal_code' => '100',
|
||||
'membership_status' => Member::STATUS_PENDING,
|
||||
'membership_type' => Member::TYPE_REGULAR,
|
||||
'national_id_encrypted' => Crypt::encryptString('A' . str_pad($counter, 9, '0', STR_PAD_LEFT)),
|
||||
'national_id_hash' => hash('sha256', 'A' . str_pad($counter, 9, '0', STR_PAD_LEFT)),
|
||||
]);
|
||||
$members[] = Member::firstOrCreate(
|
||||
['email' => "pending{$counter}@test.com"],
|
||||
[
|
||||
'user_id' => $i === 0 ? $users['member']->id : null,
|
||||
'full_name' => "待審核會員 {$counter}",
|
||||
'phone' => '09' . str_pad($counter, 8, '0', STR_PAD_LEFT),
|
||||
'address_line_1' => "測試地址 {$counter} 號",
|
||||
'city' => $taiwanCities[array_rand($taiwanCities)],
|
||||
'postal_code' => '100',
|
||||
'membership_status' => Member::STATUS_PENDING,
|
||||
'membership_type' => Member::TYPE_REGULAR,
|
||||
'national_id_encrypted' => Crypt::encryptString('A' . str_pad($counter, 9, '0', STR_PAD_LEFT)),
|
||||
'national_id_hash' => hash('sha256', 'A' . str_pad($counter, 9, '0', STR_PAD_LEFT)),
|
||||
]
|
||||
);
|
||||
$counter++;
|
||||
}
|
||||
|
||||
// 8 Active Members
|
||||
for ($i = 0; $i < 8; $i++) {
|
||||
$startDate = now()->subMonths(rand(1, 6));
|
||||
$members[] = Member::create([
|
||||
'user_id' => null,
|
||||
'full_name' => "活躍會員 {$counter}",
|
||||
'email' => "active{$counter}@test.com",
|
||||
'phone' => '09' . str_pad($counter, 8, '0', STR_PAD_LEFT),
|
||||
'address_line_1' => "測試地址 {$counter} 號",
|
||||
'city' => $taiwanCities[array_rand($taiwanCities)],
|
||||
'postal_code' => '100',
|
||||
'membership_status' => Member::STATUS_ACTIVE,
|
||||
'membership_type' => $i < 6 ? Member::TYPE_REGULAR : ($i === 6 ? Member::TYPE_HONORARY : Member::TYPE_STUDENT),
|
||||
'membership_started_at' => $startDate,
|
||||
'membership_expires_at' => $startDate->copy()->addYear(),
|
||||
'emergency_contact_name' => "緊急聯絡人 {$counter}",
|
||||
'emergency_contact_phone' => '02-12345678',
|
||||
'national_id_encrypted' => Crypt::encryptString('A' . str_pad($counter, 9, '0', STR_PAD_LEFT)),
|
||||
'national_id_hash' => hash('sha256', 'A' . str_pad($counter, 9, '0', STR_PAD_LEFT)),
|
||||
]);
|
||||
$members[] = Member::firstOrCreate(
|
||||
['email' => "active{$counter}@test.com"],
|
||||
[
|
||||
'user_id' => null,
|
||||
'full_name' => "活躍會員 {$counter}",
|
||||
'phone' => '09' . str_pad($counter, 8, '0', STR_PAD_LEFT),
|
||||
'address_line_1' => "測試地址 {$counter} 號",
|
||||
'city' => $taiwanCities[array_rand($taiwanCities)],
|
||||
'postal_code' => '100',
|
||||
'membership_status' => Member::STATUS_ACTIVE,
|
||||
'membership_type' => $i < 6 ? Member::TYPE_REGULAR : ($i === 6 ? Member::TYPE_HONORARY : Member::TYPE_STUDENT),
|
||||
'membership_started_at' => $startDate,
|
||||
'membership_expires_at' => $startDate->copy()->addYear(),
|
||||
'emergency_contact_name' => "緊急聯絡人 {$counter}",
|
||||
'emergency_contact_phone' => '02-12345678',
|
||||
'national_id_encrypted' => Crypt::encryptString('A' . str_pad($counter, 9, '0', STR_PAD_LEFT)),
|
||||
'national_id_hash' => hash('sha256', 'A' . str_pad($counter, 9, '0', STR_PAD_LEFT)),
|
||||
]
|
||||
);
|
||||
$counter++;
|
||||
}
|
||||
|
||||
// 3 Expired Members
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
$startDate = now()->subYears(2);
|
||||
$members[] = Member::create([
|
||||
'user_id' => null,
|
||||
'full_name' => "過期會員 {$counter}",
|
||||
'email' => "expired{$counter}@test.com",
|
||||
'phone' => '09' . str_pad($counter, 8, '0', STR_PAD_LEFT),
|
||||
'address_line_1' => "測試地址 {$counter} 號",
|
||||
'city' => $taiwanCities[array_rand($taiwanCities)],
|
||||
'postal_code' => '100',
|
||||
'membership_status' => Member::STATUS_EXPIRED,
|
||||
'membership_type' => Member::TYPE_REGULAR,
|
||||
'membership_started_at' => $startDate,
|
||||
'membership_expires_at' => $startDate->copy()->addYear(),
|
||||
'national_id_encrypted' => Crypt::encryptString('A' . str_pad($counter, 9, '0', STR_PAD_LEFT)),
|
||||
'national_id_hash' => hash('sha256', 'A' . str_pad($counter, 9, '0', STR_PAD_LEFT)),
|
||||
]);
|
||||
$members[] = Member::firstOrCreate(
|
||||
['email' => "expired{$counter}@test.com"],
|
||||
[
|
||||
'user_id' => null,
|
||||
'full_name' => "過期會員 {$counter}",
|
||||
'phone' => '09' . str_pad($counter, 8, '0', STR_PAD_LEFT),
|
||||
'address_line_1' => "測試地址 {$counter} 號",
|
||||
'city' => $taiwanCities[array_rand($taiwanCities)],
|
||||
'postal_code' => '100',
|
||||
'membership_status' => Member::STATUS_EXPIRED,
|
||||
'membership_type' => Member::TYPE_REGULAR,
|
||||
'membership_started_at' => $startDate,
|
||||
'membership_expires_at' => $startDate->copy()->addYear(),
|
||||
'national_id_encrypted' => Crypt::encryptString('A' . str_pad($counter, 9, '0', STR_PAD_LEFT)),
|
||||
'national_id_hash' => hash('sha256', 'A' . str_pad($counter, 9, '0', STR_PAD_LEFT)),
|
||||
]
|
||||
);
|
||||
$counter++;
|
||||
}
|
||||
|
||||
// 2 Suspended Members
|
||||
for ($i = 0; $i < 2; $i++) {
|
||||
$members[] = Member::create([
|
||||
'user_id' => null,
|
||||
'full_name' => "停權會員 {$counter}",
|
||||
'email' => "suspended{$counter}@test.com",
|
||||
'phone' => '09' . str_pad($counter, 8, '0', STR_PAD_LEFT),
|
||||
'address_line_1' => "測試地址 {$counter} 號",
|
||||
'city' => $taiwanCities[array_rand($taiwanCities)],
|
||||
'postal_code' => '100',
|
||||
'membership_status' => Member::STATUS_SUSPENDED,
|
||||
'membership_type' => Member::TYPE_REGULAR,
|
||||
'national_id_encrypted' => Crypt::encryptString('A' . str_pad($counter, 9, '0', STR_PAD_LEFT)),
|
||||
'national_id_hash' => hash('sha256', 'A' . str_pad($counter, 9, '0', STR_PAD_LEFT)),
|
||||
]);
|
||||
$members[] = Member::firstOrCreate(
|
||||
['email' => "suspended{$counter}@test.com"],
|
||||
[
|
||||
'user_id' => null,
|
||||
'full_name' => "停權會員 {$counter}",
|
||||
'phone' => '09' . str_pad($counter, 8, '0', STR_PAD_LEFT),
|
||||
'address_line_1' => "測試地址 {$counter} 號",
|
||||
'city' => $taiwanCities[array_rand($taiwanCities)],
|
||||
'postal_code' => '100',
|
||||
'membership_status' => Member::STATUS_SUSPENDED,
|
||||
'membership_type' => Member::TYPE_REGULAR,
|
||||
'national_id_encrypted' => Crypt::encryptString('A' . str_pad($counter, 9, '0', STR_PAD_LEFT)),
|
||||
'national_id_hash' => hash('sha256', 'A' . str_pad($counter, 9, '0', STR_PAD_LEFT)),
|
||||
]
|
||||
);
|
||||
$counter++;
|
||||
}
|
||||
|
||||
// 2 Additional Pending Members (total 20)
|
||||
for ($i = 0; $i < 2; $i++) {
|
||||
$members[] = Member::create([
|
||||
'user_id' => null,
|
||||
'full_name' => "新申請會員 {$counter}",
|
||||
'email' => "newmember{$counter}@test.com",
|
||||
'phone' => '09' . str_pad($counter, 8, '0', STR_PAD_LEFT),
|
||||
'address_line_1' => "測試地址 {$counter} 號",
|
||||
'city' => $taiwanCities[array_rand($taiwanCities)],
|
||||
'postal_code' => '100',
|
||||
'membership_status' => Member::STATUS_PENDING,
|
||||
'membership_type' => Member::TYPE_REGULAR,
|
||||
]);
|
||||
$members[] = Member::firstOrCreate(
|
||||
['email' => "newmember{$counter}@test.com"],
|
||||
[
|
||||
'user_id' => null,
|
||||
'full_name' => "新申請會員 {$counter}",
|
||||
'phone' => '09' . str_pad($counter, 8, '0', STR_PAD_LEFT),
|
||||
'address_line_1' => "測試地址 {$counter} 號",
|
||||
'city' => $taiwanCities[array_rand($taiwanCities)],
|
||||
'postal_code' => '100',
|
||||
'membership_status' => Member::STATUS_PENDING,
|
||||
'membership_type' => Member::TYPE_REGULAR,
|
||||
]
|
||||
);
|
||||
$counter++;
|
||||
}
|
||||
|
||||
@@ -264,38 +279,41 @@ class TestDataSeeder extends Seeder
|
||||
$paymentMethods = [
|
||||
MembershipPayment::METHOD_BANK_TRANSFER,
|
||||
MembershipPayment::METHOD_CASH,
|
||||
MembershipPayment::METHOD_CHECK,
|
||||
];
|
||||
|
||||
// 10 Pending Payments
|
||||
for ($i = 0; $i < 10; $i++) {
|
||||
$payments[] = MembershipPayment::create([
|
||||
'member_id' => $members[$i]->id,
|
||||
'amount' => 1000,
|
||||
'paid_at' => now()->subDays(rand(1, 10)),
|
||||
'payment_method' => $paymentMethods[array_rand($paymentMethods)],
|
||||
'reference' => 'REF-' . str_pad($i + 1, 5, '0', STR_PAD_LEFT),
|
||||
'receipt_path' => 'receipts/test-receipt-' . ($i + 1) . '.pdf',
|
||||
'status' => MembershipPayment::STATUS_PENDING,
|
||||
'notes' => '待審核的繳費記錄',
|
||||
]);
|
||||
$payments[] = MembershipPayment::firstOrCreate(
|
||||
['reference' => 'REF-' . str_pad($i + 1, 5, '0', STR_PAD_LEFT)],
|
||||
[
|
||||
'member_id' => $members[$i]->id,
|
||||
'amount' => 1000,
|
||||
'paid_at' => now()->subDays(rand(1, 10)),
|
||||
'payment_method' => $paymentMethods[array_rand($paymentMethods)],
|
||||
'receipt_path' => 'receipts/test-receipt-' . ($i + 1) . '.pdf',
|
||||
'status' => MembershipPayment::STATUS_PENDING,
|
||||
'notes' => '待審核的繳費記錄',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// 8 Approved by Cashier
|
||||
for ($i = 10; $i < 18; $i++) {
|
||||
$payments[] = MembershipPayment::create([
|
||||
'member_id' => $members[$i % count($members)]->id,
|
||||
'amount' => 1000,
|
||||
'paid_at' => now()->subDays(rand(5, 15)),
|
||||
'payment_method' => $paymentMethods[array_rand($paymentMethods)],
|
||||
'reference' => 'REF-' . str_pad($i + 1, 5, '0', STR_PAD_LEFT),
|
||||
'receipt_path' => 'receipts/test-receipt-' . ($i + 1) . '.pdf',
|
||||
'status' => MembershipPayment::STATUS_APPROVED_CASHIER,
|
||||
'verified_by_cashier_id' => $users['cashier']->id,
|
||||
'cashier_verified_at' => now()->subDays(rand(3, 12)),
|
||||
'cashier_notes' => '收據已核對,金額無誤',
|
||||
'notes' => '已通過出納審核',
|
||||
]);
|
||||
$payments[] = MembershipPayment::firstOrCreate(
|
||||
['reference' => 'REF-' . str_pad($i + 1, 5, '0', STR_PAD_LEFT)],
|
||||
[
|
||||
'member_id' => $members[$i % count($members)]->id,
|
||||
'amount' => 1000,
|
||||
'paid_at' => now()->subDays(rand(5, 15)),
|
||||
'payment_method' => $paymentMethods[array_rand($paymentMethods)],
|
||||
'receipt_path' => 'receipts/test-receipt-' . ($i + 1) . '.pdf',
|
||||
'status' => MembershipPayment::STATUS_APPROVED_CASHIER,
|
||||
'verified_by_cashier_id' => $users['cashier']->id,
|
||||
'cashier_verified_at' => now()->subDays(rand(3, 12)),
|
||||
'cashier_notes' => '收據已核對,金額無誤',
|
||||
'notes' => '已通過出納審核',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// 6 Approved by Accountant
|
||||
@@ -303,22 +321,24 @@ class TestDataSeeder extends Seeder
|
||||
$cashierVerifiedAt = now()->subDays(rand(10, 20));
|
||||
$accountantVerifiedAt = $cashierVerifiedAt->copy()->addDays(rand(1, 3));
|
||||
|
||||
$payments[] = MembershipPayment::create([
|
||||
'member_id' => $members[$i % count($members)]->id,
|
||||
'amount' => 1000,
|
||||
'paid_at' => now()->subDays(rand(15, 25)),
|
||||
'payment_method' => $paymentMethods[array_rand($paymentMethods)],
|
||||
'reference' => 'REF-' . str_pad($i + 1, 5, '0', STR_PAD_LEFT),
|
||||
'receipt_path' => 'receipts/test-receipt-' . ($i + 1) . '.pdf',
|
||||
'status' => MembershipPayment::STATUS_APPROVED_ACCOUNTANT,
|
||||
'verified_by_cashier_id' => $users['cashier']->id,
|
||||
'cashier_verified_at' => $cashierVerifiedAt,
|
||||
'cashier_notes' => '收據已核對,金額無誤',
|
||||
'verified_by_accountant_id' => $users['accountant']->id,
|
||||
'accountant_verified_at' => $accountantVerifiedAt,
|
||||
'accountant_notes' => '帳務核對完成',
|
||||
'notes' => '已通過會計審核',
|
||||
]);
|
||||
$payments[] = MembershipPayment::firstOrCreate(
|
||||
['reference' => 'REF-' . str_pad($i + 1, 5, '0', STR_PAD_LEFT)],
|
||||
[
|
||||
'member_id' => $members[$i % count($members)]->id,
|
||||
'amount' => 1000,
|
||||
'paid_at' => now()->subDays(rand(15, 25)),
|
||||
'payment_method' => $paymentMethods[array_rand($paymentMethods)],
|
||||
'receipt_path' => 'receipts/test-receipt-' . ($i + 1) . '.pdf',
|
||||
'status' => MembershipPayment::STATUS_APPROVED_ACCOUNTANT,
|
||||
'verified_by_cashier_id' => $users['cashier']->id,
|
||||
'cashier_verified_at' => $cashierVerifiedAt,
|
||||
'cashier_notes' => '收據已核對,金額無誤',
|
||||
'verified_by_accountant_id' => $users['accountant']->id,
|
||||
'accountant_verified_at' => $accountantVerifiedAt,
|
||||
'accountant_notes' => '帳務核對完成',
|
||||
'notes' => '已通過會計審核',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// 4 Fully Approved (Chair approved - member activated)
|
||||
@@ -327,42 +347,46 @@ class TestDataSeeder extends Seeder
|
||||
$accountantVerifiedAt = $cashierVerifiedAt->copy()->addDays(rand(1, 3));
|
||||
$chairVerifiedAt = $accountantVerifiedAt->copy()->addDays(rand(1, 2));
|
||||
|
||||
$payments[] = MembershipPayment::create([
|
||||
'member_id' => $members[$i % count($members)]->id,
|
||||
'amount' => 1000,
|
||||
'paid_at' => now()->subDays(rand(25, 35)),
|
||||
'payment_method' => $paymentMethods[array_rand($paymentMethods)],
|
||||
'reference' => 'REF-' . str_pad($i + 1, 5, '0', STR_PAD_LEFT),
|
||||
'receipt_path' => 'receipts/test-receipt-' . ($i + 1) . '.pdf',
|
||||
'status' => MembershipPayment::STATUS_APPROVED_CHAIR,
|
||||
'verified_by_cashier_id' => $users['cashier']->id,
|
||||
'cashier_verified_at' => $cashierVerifiedAt,
|
||||
'cashier_notes' => '收據已核對,金額無誤',
|
||||
'verified_by_accountant_id' => $users['accountant']->id,
|
||||
'accountant_verified_at' => $accountantVerifiedAt,
|
||||
'accountant_notes' => '帳務核對完成',
|
||||
'verified_by_chair_id' => $users['chair']->id,
|
||||
'chair_verified_at' => $chairVerifiedAt,
|
||||
'chair_notes' => '最終批准',
|
||||
'notes' => '已完成三階段審核',
|
||||
]);
|
||||
$payments[] = MembershipPayment::firstOrCreate(
|
||||
['reference' => 'REF-' . str_pad($i + 1, 5, '0', STR_PAD_LEFT)],
|
||||
[
|
||||
'member_id' => $members[$i % count($members)]->id,
|
||||
'amount' => 1000,
|
||||
'paid_at' => now()->subDays(rand(25, 35)),
|
||||
'payment_method' => $paymentMethods[array_rand($paymentMethods)],
|
||||
'receipt_path' => 'receipts/test-receipt-' . ($i + 1) . '.pdf',
|
||||
'status' => MembershipPayment::STATUS_APPROVED_CHAIR,
|
||||
'verified_by_cashier_id' => $users['cashier']->id,
|
||||
'cashier_verified_at' => $cashierVerifiedAt,
|
||||
'cashier_notes' => '收據已核對,金額無誤',
|
||||
'verified_by_accountant_id' => $users['accountant']->id,
|
||||
'accountant_verified_at' => $accountantVerifiedAt,
|
||||
'accountant_notes' => '帳務核對完成',
|
||||
'verified_by_chair_id' => $users['chair']->id,
|
||||
'chair_verified_at' => $chairVerifiedAt,
|
||||
'chair_notes' => '最終批准',
|
||||
'notes' => '已完成三階段審核',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// 2 Rejected Payments
|
||||
for ($i = 28; $i < 30; $i++) {
|
||||
$payments[] = MembershipPayment::create([
|
||||
'member_id' => $members[$i % count($members)]->id,
|
||||
'amount' => 1000,
|
||||
'paid_at' => now()->subDays(rand(5, 10)),
|
||||
'payment_method' => $paymentMethods[array_rand($paymentMethods)],
|
||||
'reference' => 'REF-' . str_pad($i + 1, 5, '0', STR_PAD_LEFT),
|
||||
'receipt_path' => 'receipts/test-receipt-' . ($i + 1) . '.pdf',
|
||||
'status' => MembershipPayment::STATUS_REJECTED,
|
||||
'rejected_by_user_id' => $users['cashier']->id,
|
||||
'rejected_at' => now()->subDays(rand(3, 8)),
|
||||
'rejection_reason' => $i === 28 ? '收據影像不清晰,無法辨識' : '金額與收據不符',
|
||||
'notes' => '已退回',
|
||||
]);
|
||||
$payments[] = MembershipPayment::firstOrCreate(
|
||||
['reference' => 'REF-' . str_pad($i + 1, 5, '0', STR_PAD_LEFT)],
|
||||
[
|
||||
'member_id' => $members[$i % count($members)]->id,
|
||||
'amount' => 1000,
|
||||
'paid_at' => now()->subDays(rand(5, 10)),
|
||||
'payment_method' => $paymentMethods[array_rand($paymentMethods)],
|
||||
'receipt_path' => 'receipts/test-receipt-' . ($i + 1) . '.pdf',
|
||||
'status' => MembershipPayment::STATUS_REJECTED,
|
||||
'rejected_by_user_id' => $users['cashier']->id,
|
||||
'rejected_at' => now()->subDays(rand(3, 8)),
|
||||
'rejection_reason' => $i === 28 ? '收據影像不清晰,無法辨識' : '金額與收據不符',
|
||||
'notes' => '已退回',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return $payments;
|
||||
@@ -747,12 +771,12 @@ class TestDataSeeder extends Seeder
|
||||
$this->command->table(
|
||||
['Role', 'Email', 'Password', 'Permissions'],
|
||||
[
|
||||
['Admin', 'admin@test.com', 'password', 'All permissions'],
|
||||
['Cashier', 'cashier@test.com', 'password', 'Tier 1 payment verification'],
|
||||
['Accountant', 'accountant@test.com', 'password', 'Tier 2 payment verification'],
|
||||
['Chair', 'chair@test.com', 'password', 'Tier 3 payment verification'],
|
||||
['Manager', 'manager@test.com', 'password', 'Membership activation'],
|
||||
['Member', 'member@test.com', 'password', 'Member dashboard access'],
|
||||
['Admin (admin)', 'admin@test.com', 'password', 'All permissions'],
|
||||
['Finance Cashier (finance_cashier)', 'cashier@test.com', 'password', 'Payment + Finance cashier'],
|
||||
['Finance Accountant (finance_accountant)', 'accountant@test.com', 'password', 'Payment + Finance accountant'],
|
||||
['Finance Chair (finance_chair)', 'chair@test.com', 'password', 'Payment + Finance chair'],
|
||||
['Membership Manager (membership_manager)', 'manager@test.com', 'password', 'Membership activation'],
|
||||
['Member (no role)', 'member@test.com', 'password', 'Member dashboard access'],
|
||||
]
|
||||
);
|
||||
$this->command->info('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
|
||||
129
resources/views/admin/announcements/create.blade.php
Normal file
129
resources/views/admin/announcements/create.blade.php
Normal file
@@ -0,0 +1,129 @@
|
||||
<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 dark:text-gray-200">
|
||||
建立公告
|
||||
</h2>
|
||||
<a href="{{ route('admin.announcements.index') }}" class="inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
← 返回列表
|
||||
</a>
|
||||
</div>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-12">
|
||||
<div class="mx-auto max-w-4xl sm:px-6 lg:px-8">
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
||||
<form method="POST" action="{{ route('admin.announcements.store') }}" class="space-y-6 p-6">
|
||||
@csrf
|
||||
|
||||
<!-- Title -->
|
||||
<div>
|
||||
<label for="title" class="block text-sm font-medium text-gray-700 dark:text-gray-300">標題 <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 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300"
|
||||
placeholder="輸入公告標題">
|
||||
@error('title')
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div>
|
||||
<label for="content" class="block text-sm font-medium text-gray-700 dark:text-gray-300">內容 <span class="text-red-500">*</span></label>
|
||||
<textarea name="content" id="content" rows="10" required
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300"
|
||||
placeholder="輸入公告內容">{{ old('content') }}</textarea>
|
||||
@error('content')
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<!-- Access Level -->
|
||||
<div>
|
||||
<label for="access_level" class="block text-sm font-medium text-gray-700 dark:text-gray-300">存取權限 <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 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300">
|
||||
<option value="members" {{ old('access_level') === 'members' ? 'selected' : '' }}>會員(需付費會籍)</option>
|
||||
<option value="public" {{ old('access_level') === 'public' ? 'selected' : '' }}>公開(所有人可見)</option>
|
||||
<option value="board" {{ old('access_level') === 'board' ? 'selected' : '' }}>理事會(僅理事可見)</option>
|
||||
<option value="admin" {{ old('access_level') === 'admin' ? 'selected' : '' }}>管理員(僅管理員可見)</option>
|
||||
</select>
|
||||
@error('access_level')
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<!-- Published At -->
|
||||
<div>
|
||||
<label for="published_at" class="block text-sm font-medium text-gray-700 dark:text-gray-300">發布時間(選填,留空則立即發布)</label>
|
||||
<input type="datetime-local" name="published_at" id="published_at" value="{{ old('published_at') }}"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300">
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">設定未來時間可排程發布</p>
|
||||
@error('published_at')
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<!-- Expires At -->
|
||||
<div>
|
||||
<label for="expires_at" class="block text-sm font-medium text-gray-700 dark:text-gray-300">過期時間(選填)</label>
|
||||
<input type="datetime-local" name="expires_at" id="expires_at" value="{{ old('expires_at') }}"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300">
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">過期後將自動隱藏</p>
|
||||
@error('expires_at')
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<!-- Is Pinned -->
|
||||
<div class="flex items-center">
|
||||
<input type="checkbox" name="is_pinned" id="is_pinned" value="1" {{ old('is_pinned') ? 'checked' : '' }}
|
||||
class="h-4 w-4 rounded border-gray-300 dark:border-gray-700 text-indigo-600 focus:ring-indigo-500 dark:bg-gray-900">
|
||||
<label for="is_pinned" class="ml-2 block text-sm text-gray-700 dark:text-gray-300">置頂此公告</label>
|
||||
</div>
|
||||
|
||||
<!-- Display Order (only shown when pinned) -->
|
||||
<div id="display_order_container" style="display: none;">
|
||||
<label for="display_order" class="block text-sm font-medium text-gray-700 dark:text-gray-300">顯示順序</label>
|
||||
<input type="number" name="display_order" id="display_order" value="{{ old('display_order', 0) }}" min="0"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300">
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">數字越小越優先顯示</p>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="flex items-center justify-end space-x-3 border-t border-gray-200 dark:border-gray-700 pt-6">
|
||||
<a href="{{ route('admin.announcements.index') }}" class="rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
取消
|
||||
</a>
|
||||
<button type="submit" name="save_action" value="draft" class="rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
儲存為草稿
|
||||
</button>
|
||||
<button type="submit" name="save_action" value="publish" class="rounded-md border border-transparent bg-indigo-600 dark:bg-indigo-500 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 dark:hover:bg-indigo-600">
|
||||
發布公告
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const isPinnedCheckbox = document.getElementById('is_pinned');
|
||||
const displayOrderContainer = document.getElementById('display_order_container');
|
||||
|
||||
function toggleDisplayOrder() {
|
||||
if (isPinnedCheckbox.checked) {
|
||||
displayOrderContainer.style.display = 'block';
|
||||
} else {
|
||||
displayOrderContainer.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
isPinnedCheckbox.addEventListener('change', toggleDisplayOrder);
|
||||
toggleDisplayOrder();
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
</x-app-layout>
|
||||
126
resources/views/admin/announcements/edit.blade.php
Normal file
126
resources/views/admin/announcements/edit.blade.php
Normal file
@@ -0,0 +1,126 @@
|
||||
<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 dark:text-gray-200">
|
||||
編輯公告
|
||||
</h2>
|
||||
<a href="{{ route('admin.announcements.show', $announcement) }}" class="inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
← 返回查看
|
||||
</a>
|
||||
</div>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-12">
|
||||
<div class="mx-auto max-w-4xl sm:px-6 lg:px-8">
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
||||
<form method="POST" action="{{ route('admin.announcements.update', $announcement) }}" class="space-y-6 p-6">
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
|
||||
<!-- Title -->
|
||||
<div>
|
||||
<label for="title" class="block text-sm font-medium text-gray-700 dark:text-gray-300">標題 <span class="text-red-500">*</span></label>
|
||||
<input type="text" name="title" id="title" value="{{ old('title', $announcement->title) }}" required
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300">
|
||||
@error('title')
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div>
|
||||
<label for="content" class="block text-sm font-medium text-gray-700 dark:text-gray-300">內容 <span class="text-red-500">*</span></label>
|
||||
<textarea name="content" id="content" rows="10" required
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300">{{ old('content', $announcement->content) }}</textarea>
|
||||
@error('content')
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<!-- Access Level -->
|
||||
<div>
|
||||
<label for="access_level" class="block text-sm font-medium text-gray-700 dark:text-gray-300">存取權限 <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 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300">
|
||||
<option value="members" {{ old('access_level', $announcement->access_level) === 'members' ? 'selected' : '' }}>會員</option>
|
||||
<option value="public" {{ old('access_level', $announcement->access_level) === 'public' ? 'selected' : '' }}>公開</option>
|
||||
<option value="board" {{ old('access_level', $announcement->access_level) === 'board' ? 'selected' : '' }}>理事會</option>
|
||||
<option value="admin" {{ old('access_level', $announcement->access_level) === 'admin' ? 'selected' : '' }}>管理員</option>
|
||||
</select>
|
||||
@error('access_level')
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<!-- Published At -->
|
||||
<div>
|
||||
<label for="published_at" class="block text-sm font-medium text-gray-700 dark:text-gray-300">發布時間</label>
|
||||
<input type="datetime-local" name="published_at" id="published_at"
|
||||
value="{{ old('published_at', $announcement->published_at?->format('Y-m-d\TH:i')) }}"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300">
|
||||
@error('published_at')
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<!-- Expires At -->
|
||||
<div>
|
||||
<label for="expires_at" class="block text-sm font-medium text-gray-700 dark:text-gray-300">過期時間</label>
|
||||
<input type="datetime-local" name="expires_at" id="expires_at"
|
||||
value="{{ old('expires_at', $announcement->expires_at?->format('Y-m-d\TH:i')) }}"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300">
|
||||
@error('expires_at')
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<!-- Is Pinned -->
|
||||
<div class="flex items-center">
|
||||
<input type="checkbox" name="is_pinned" id="is_pinned" value="1"
|
||||
{{ old('is_pinned', $announcement->is_pinned) ? 'checked' : '' }}
|
||||
class="h-4 w-4 rounded border-gray-300 dark:border-gray-700 text-indigo-600 focus:ring-indigo-500 dark:bg-gray-900">
|
||||
<label for="is_pinned" class="ml-2 block text-sm text-gray-700 dark:text-gray-300">置頂此公告</label>
|
||||
</div>
|
||||
|
||||
<!-- Display Order -->
|
||||
<div id="display_order_container">
|
||||
<label for="display_order" class="block text-sm font-medium text-gray-700 dark:text-gray-300">顯示順序</label>
|
||||
<input type="number" name="display_order" id="display_order"
|
||||
value="{{ old('display_order', $announcement->display_order) }}" min="0"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300">
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="flex items-center justify-end space-x-3 border-t border-gray-200 dark:border-gray-700 pt-6">
|
||||
<a href="{{ route('admin.announcements.show', $announcement) }}" class="rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
取消
|
||||
</a>
|
||||
<button type="submit" class="rounded-md border border-transparent bg-indigo-600 dark:bg-indigo-500 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 dark:hover:bg-indigo-600">
|
||||
儲存變更
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const isPinnedCheckbox = document.getElementById('is_pinned');
|
||||
const displayOrderContainer = document.getElementById('display_order_container');
|
||||
|
||||
function toggleDisplayOrder() {
|
||||
if (isPinnedCheckbox.checked) {
|
||||
displayOrderContainer.style.display = 'block';
|
||||
} else {
|
||||
displayOrderContainer.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
isPinnedCheckbox.addEventListener('change', toggleDisplayOrder);
|
||||
toggleDisplayOrder();
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
</x-app-layout>
|
||||
186
resources/views/admin/announcements/index.blade.php
Normal file
186
resources/views/admin/announcements/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 dark:text-gray-200">
|
||||
公告管理
|
||||
</h2>
|
||||
<a href="{{ route('admin.announcements.create') }}" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 dark:bg-indigo-500 px-3 py-2 text-sm font-medium text-white hover:bg-indigo-700 dark:hover:bg-indigo-600">
|
||||
+ 建立公告
|
||||
</a>
|
||||
</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 dark:bg-green-900/50 p-4">
|
||||
<p class="text-sm font-medium text-green-800 dark:text-green-200">{{ session('status') }}</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if (session('error'))
|
||||
<div class="rounded-md bg-red-50 dark:bg-red-900/50 p-4">
|
||||
<p class="text-sm font-medium text-red-800 dark:text-red-200">{{ session('error') }}</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Statistics -->
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-5">
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg p-6">
|
||||
<div class="text-sm font-medium text-gray-500 dark:text-gray-400">總計</div>
|
||||
<div class="mt-2 text-3xl font-bold text-gray-900 dark:text-gray-100">{{ $stats['total'] }}</div>
|
||||
</div>
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg p-6">
|
||||
<div class="text-sm font-medium text-gray-500 dark:text-gray-400">草稿</div>
|
||||
<div class="mt-2 text-3xl font-bold text-gray-600 dark:text-gray-400">{{ $stats['draft'] }}</div>
|
||||
</div>
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg p-6">
|
||||
<div class="text-sm font-medium text-gray-500 dark:text-gray-400">已發布</div>
|
||||
<div class="mt-2 text-3xl font-bold text-green-600 dark:text-green-400">{{ $stats['published'] }}</div>
|
||||
</div>
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg p-6">
|
||||
<div class="text-sm font-medium text-gray-500 dark:text-gray-400">已歸檔</div>
|
||||
<div class="mt-2 text-3xl font-bold text-yellow-600 dark:text-yellow-400">{{ $stats['archived'] }}</div>
|
||||
</div>
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg p-6">
|
||||
<div class="text-sm font-medium text-gray-500 dark:text-gray-400">置頂中</div>
|
||||
<div class="mt-2 text-3xl font-bold text-blue-600 dark:text-blue-400">{{ $stats['pinned'] }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Search and Filter -->
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg p-6">
|
||||
<form method="GET" action="{{ route('admin.announcements.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 dark:text-gray-300">搜尋</label>
|
||||
<input type="text" name="search" id="search" value="{{ request('search') }}" placeholder="標題、內容..."
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300">
|
||||
</div>
|
||||
<div>
|
||||
<label for="status" class="block text-sm font-medium text-gray-700 dark:text-gray-300">狀態</label>
|
||||
<select name="status" id="status" class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300">
|
||||
<option value="">全部</option>
|
||||
<option value="draft" {{ request('status') === 'draft' ? 'selected' : '' }}>草稿</option>
|
||||
<option value="published" {{ request('status') === 'published' ? 'selected' : '' }}>已發布</option>
|
||||
<option value="archived" {{ request('status') === 'archived' ? 'selected' : '' }}>已歸檔</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="access_level" class="block text-sm font-medium text-gray-700 dark:text-gray-300">存取權限</label>
|
||||
<select name="access_level" id="access_level" class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300">
|
||||
<option value="">全部</option>
|
||||
<option value="public" {{ request('access_level') === 'public' ? 'selected' : '' }}>公開</option>
|
||||
<option value="members" {{ request('access_level') === 'members' ? 'selected' : '' }}>會員</option>
|
||||
<option value="board" {{ request('access_level') === 'board' ? 'selected' : '' }}>理事會</option>
|
||||
<option value="admin" {{ request('access_level') === 'admin' ? 'selected' : '' }}>管理員</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="pinned" class="block text-sm font-medium text-gray-700 dark:text-gray-300">置頂</label>
|
||||
<select name="pinned" id="pinned" class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300">
|
||||
<option value="">全部</option>
|
||||
<option value="yes" {{ request('pinned') === 'yes' ? 'selected' : '' }}>已置頂</option>
|
||||
<option value="no" {{ request('pinned') === 'no' ? 'selected' : '' }}>未置頂</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end space-x-2">
|
||||
<a href="{{ route('admin.announcements.index') }}" class="rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
清除
|
||||
</a>
|
||||
<button type="submit" class="rounded-md border border-transparent bg-indigo-600 dark:bg-indigo-500 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 dark:hover:bg-indigo-600">
|
||||
搜尋
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center">
|
||||
<div>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">共 {{ $announcements->total() }} 則公告</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Announcements Table -->
|
||||
<div class="bg-white dark:bg-gray-800 shadow overflow-hidden sm:rounded-lg">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<thead class="bg-gray-50 dark:bg-gray-700">
|
||||
<tr>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">公告</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">狀態</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">存取權限</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">建立者</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">瀏覽次數</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">建立時間</th>
|
||||
<th scope="col" class="px-6 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
|
||||
@forelse($announcements as $announcement)
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<div class="flex items-center">
|
||||
@if($announcement->is_pinned)
|
||||
<span class="mr-2 text-blue-500" title="置頂公告">📌</span>
|
||||
@endif
|
||||
<div>
|
||||
<div class="text-sm font-medium text-gray-900 dark:text-gray-100">
|
||||
<a href="{{ route('admin.announcements.show', $announcement) }}" class="hover:text-indigo-600 dark:hover:text-indigo-400">
|
||||
{{ $announcement->title }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ \Illuminate\Support\Str::limit($announcement->content, 60) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<span class="inline-flex rounded-full px-2 text-xs font-semibold leading-5
|
||||
@if($announcement->status === 'draft') bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-300
|
||||
@elseif($announcement->status === 'published') bg-green-100 dark:bg-green-900/50 text-green-800 dark:text-green-300
|
||||
@else bg-yellow-100 dark:bg-yellow-900/50 text-yellow-800 dark:text-yellow-300
|
||||
@endif">
|
||||
{{ $announcement->getStatusLabel() }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ $announcement->getAccessLevelLabel() }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ $announcement->creator->name ?? 'N/A' }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ $announcement->view_count }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ $announcement->created_at->format('Y-m-d H:i') }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium space-x-2">
|
||||
<a href="{{ route('admin.announcements.show', $announcement) }}" class="text-indigo-600 dark:text-indigo-400 hover:text-indigo-900 dark:hover:text-indigo-300">查看</a>
|
||||
@if($announcement->canBeEditedBy(auth()->user()))
|
||||
<a href="{{ route('admin.announcements.edit', $announcement) }}" class="text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-300">編輯</a>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="7" class="px-6 py-12 text-center text-sm text-gray-500 dark:text-gray-400">
|
||||
沒有找到公告。<a href="{{ route('admin.announcements.create') }}" class="text-indigo-600 dark:text-indigo-400 hover:underline">建立第一則公告</a>
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
@if($announcements->hasPages())
|
||||
<div class="bg-white dark:bg-gray-800 px-4 py-3 border-t border-gray-200 dark:border-gray-700 sm:px-6">
|
||||
{{ $announcements->links() }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
170
resources/views/admin/announcements/show.blade.php
Normal file
170
resources/views/admin/announcements/show.blade.php
Normal file
@@ -0,0 +1,170 @@
|
||||
<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 dark:text-gray-200">
|
||||
公告詳情
|
||||
</h2>
|
||||
<div class="flex items-center space-x-2">
|
||||
<a href="{{ route('admin.announcements.index') }}" class="inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
← 返回列表
|
||||
</a>
|
||||
@if($announcement->canBeEditedBy(auth()->user()))
|
||||
<a href="{{ route('admin.announcements.edit', $announcement) }}" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 dark:bg-indigo-500 px-3 py-2 text-sm font-medium text-white hover:bg-indigo-700 dark:hover:bg-indigo-600">
|
||||
編輯公告
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</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 dark:bg-green-900/50 p-4">
|
||||
<p class="text-sm font-medium text-green-800 dark:text-green-200">{{ session('status') }}</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if (session('error'))
|
||||
<div class="rounded-md bg-red-50 dark:bg-red-900/50 p-4">
|
||||
<p class="text-sm font-medium text-red-800 dark:text-red-200">{{ session('error') }}</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Announcement Content -->
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg p-6">
|
||||
<div class="mb-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-2xl font-bold text-gray-900 dark:text-gray-100">
|
||||
@if($announcement->is_pinned)
|
||||
<span class="text-blue-500" title="置頂公告">📌</span>
|
||||
@endif
|
||||
{{ $announcement->title }}
|
||||
</h3>
|
||||
<span class="inline-flex rounded-full px-3 py-1 text-sm font-semibold
|
||||
@if($announcement->status === 'draft') bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-300
|
||||
@elseif($announcement->status === 'published') bg-green-100 dark:bg-green-900/50 text-green-800 dark:text-green-300
|
||||
@else bg-yellow-100 dark:bg-yellow-900/50 text-yellow-800 dark:text-yellow-300
|
||||
@endif">
|
||||
{{ $announcement->getStatusLabel() }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="prose dark:prose-invert max-w-none">
|
||||
<div class="whitespace-pre-wrap text-gray-700 dark:text-gray-300">{{ $announcement->content }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Metadata -->
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg p-6">
|
||||
<h4 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">公告資訊</h4>
|
||||
<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">存取權限</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">{{ $announcement->getAccessLevelLabel() }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">瀏覽次數</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">{{ $announcement->view_count }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">建立者</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">{{ $announcement->creator->name ?? 'N/A' }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">建立時間</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">{{ $announcement->created_at->format('Y-m-d H:i:s') }}</dd>
|
||||
</div>
|
||||
@if($announcement->published_at)
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">發布時間</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
|
||||
{{ $announcement->published_at->format('Y-m-d H:i:s') }}
|
||||
@if($announcement->isScheduled())
|
||||
<span class="ml-2 inline-flex rounded-full bg-blue-100 dark:bg-blue-900/50 px-2 py-1 text-xs font-semibold text-blue-800 dark:text-blue-300">排程中</span>
|
||||
@endif
|
||||
</dd>
|
||||
</div>
|
||||
@endif
|
||||
@if($announcement->expires_at)
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">過期時間</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
|
||||
{{ $announcement->expires_at->format('Y-m-d H:i:s') }}
|
||||
@if($announcement->isExpired())
|
||||
<span class="ml-2 inline-flex rounded-full bg-red-100 dark:bg-red-900/50 px-2 py-1 text-xs font-semibold text-red-800 dark:text-red-300">已過期</span>
|
||||
@endif
|
||||
</dd>
|
||||
</div>
|
||||
@endif
|
||||
@if($announcement->lastUpdatedBy)
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">最後更新者</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">{{ $announcement->lastUpdatedBy->name }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">最後更新時間</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">{{ $announcement->updated_at->format('Y-m-d H:i:s') }}</dd>
|
||||
</div>
|
||||
@endif
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
@if($announcement->canBeEditedBy(auth()->user()))
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg p-6">
|
||||
<h4 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">操作</h4>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
@if($announcement->isDraft() && auth()->user()->can('publish_announcements'))
|
||||
<form method="POST" action="{{ route('admin.announcements.publish', $announcement) }}">
|
||||
@csrf
|
||||
<button type="submit" class="inline-flex items-center rounded-md border border-transparent bg-green-600 dark:bg-green-500 px-4 py-2 text-sm font-medium text-white hover:bg-green-700 dark:hover:bg-green-600">
|
||||
發布公告
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
|
||||
@if($announcement->isPublished() && auth()->user()->can('publish_announcements'))
|
||||
<form method="POST" action="{{ route('admin.announcements.archive', $announcement) }}">
|
||||
@csrf
|
||||
<button type="submit" class="inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
歸檔公告
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
|
||||
@if(!$announcement->is_pinned && auth()->user()->can('edit_announcements'))
|
||||
<form method="POST" action="{{ route('admin.announcements.pin', $announcement) }}">
|
||||
@csrf
|
||||
<button type="submit" class="inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
📌 置頂公告
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
|
||||
@if($announcement->is_pinned && auth()->user()->can('edit_announcements'))
|
||||
<form method="POST" action="{{ route('admin.announcements.unpin', $announcement) }}">
|
||||
@csrf
|
||||
<button type="submit" class="inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
取消置頂
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
|
||||
@if(auth()->user()->can('delete_announcements'))
|
||||
<form method="POST" action="{{ route('admin.announcements.destroy', $announcement) }}"
|
||||
onsubmit="return confirm('確定要刪除此公告嗎?');">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="inline-flex items-center rounded-md border border-transparent bg-red-600 dark:bg-red-500 px-4 py-2 text-sm font-medium text-white hover:bg-red-700 dark:hover:bg-red-600">
|
||||
刪除公告
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
@@ -1,7 +1,7 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
|
||||
{{ __('Audit Logs') }}
|
||||
稽核日誌
|
||||
</h2>
|
||||
</x-slot>
|
||||
|
||||
@@ -9,18 +9,18 @@
|
||||
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8">
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<form method="GET" action="{{ route('admin.audit.index') }}" class="space-y-4" role="search" aria-label="{{ __('Filter audit logs') }}">
|
||||
<form method="GET" action="{{ route('admin.audit.index') }}" class="space-y-4" role="search" aria-label="篩選稽核日誌">
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-4">
|
||||
<div>
|
||||
<label for="user_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ __('User') }}
|
||||
使用者
|
||||
</label>
|
||||
<select
|
||||
name="user_id"
|
||||
id="user_id"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-700 dark:text-gray-100"
|
||||
>
|
||||
<option value="">{{ __('All Users') }}</option>
|
||||
<option value="">所有使用者</option>
|
||||
@foreach ($users as $user)
|
||||
<option value="{{ $user->id }}" @selected(request('user_id') == $user->id)>
|
||||
{{ $user->name }}
|
||||
@@ -31,14 +31,14 @@
|
||||
|
||||
<div>
|
||||
<label for="event" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ __('Event') }}
|
||||
事件
|
||||
</label>
|
||||
<select
|
||||
name="event"
|
||||
id="event"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-700 dark:text-gray-100"
|
||||
>
|
||||
<option value="">{{ __('All Events') }}</option>
|
||||
<option value="">所有事件</option>
|
||||
@foreach ($actions as $action)
|
||||
<option value="{{ $action }}" @selected(request('action') == $action)>
|
||||
{{ ucfirst($action) }}
|
||||
@@ -49,14 +49,14 @@
|
||||
|
||||
<div>
|
||||
<label for="auditable_type" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ __('Model Type') }}
|
||||
模型類型
|
||||
</label>
|
||||
<select
|
||||
name="auditable_type"
|
||||
id="auditable_type"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-700 dark:text-gray-100"
|
||||
>
|
||||
<option value="">{{ __('All Types') }}</option>
|
||||
<option value="">所有類型</option>
|
||||
@foreach ($auditableTypes as $type)
|
||||
<option value="{{ $type }}" @selected(request('auditable_type') == $type)>
|
||||
{{ class_basename($type) }}
|
||||
@@ -67,7 +67,7 @@
|
||||
|
||||
<div class="flex items-end">
|
||||
<button type="submit" class="inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-600 dark:bg-indigo-500 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 dark:hover:bg-indigo-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:focus:ring-indigo-600 focus:ring-offset-2 dark:focus:ring-offset-gray-800">
|
||||
{{ __('Filter') }}
|
||||
篩選
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -78,22 +78,22 @@
|
||||
<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">
|
||||
{{ __('User') }}
|
||||
使用者
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">
|
||||
{{ __('Event') }}
|
||||
事件
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">
|
||||
{{ __('Model') }}
|
||||
模型
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">
|
||||
{{ __('Details') }}
|
||||
詳情
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">
|
||||
{{ __('IP Address') }}
|
||||
IP位址
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">
|
||||
{{ __('Date') }}
|
||||
日期
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -120,7 +120,7 @@
|
||||
<div>{{ $log->description ?: '—' }}</div>
|
||||
@if(!empty($log->metadata))
|
||||
<details class="cursor-pointer group mt-1">
|
||||
<summary class="text-xs text-indigo-600 dark:text-indigo-400 hover:text-indigo-900 dark:hover:text-indigo-300">{{ __('Metadata') }}</summary>
|
||||
<summary class="text-xs text-indigo-600 dark:text-indigo-400 hover:text-indigo-900 dark:hover:text-indigo-300">元數據</summary>
|
||||
<div class="mt-2 p-2 bg-gray-50 dark:bg-gray-900 rounded text-xs font-mono overflow-x-auto">
|
||||
<pre class="whitespace-pre-wrap text-gray-800 dark:text-gray-200">{{ json_encode($log->metadata, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) }}</pre>
|
||||
</div>
|
||||
@@ -137,7 +137,7 @@
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="6" class="px-3 py-4 text-sm text-gray-500 dark:text-gray-400 text-center">
|
||||
{{ __('No audit logs found.') }}
|
||||
找不到稽核日誌。
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800">
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
|
||||
製作銀行調節表
|
||||
</h2>
|
||||
</x-slot>
|
||||
@@ -8,22 +8,22 @@
|
||||
<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 class="rounded-md bg-red-50 dark:bg-red-900/30 p-4">
|
||||
<p class="text-sm font-medium text-red-800 dark:text-red-200">{{ session('error') }}</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Help Info -->
|
||||
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
||||
<div class="bg-blue-50 dark:bg-blue-900/30 border border-blue-200 dark:border-blue-800 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">
|
||||
<svg class="h-5 w-5 text-blue-400 dark:text-blue-500" 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">
|
||||
<h3 class="text-sm font-medium text-blue-800 dark:text-blue-200">銀行調節表說明</h3>
|
||||
<div class="mt-2 text-sm text-blue-700 dark:text-blue-300">
|
||||
<p>銀行調節表用於核對銀行對帳單餘額與內部現金簿餘額的差異。請準備好:</p>
|
||||
<ul class="list-disc pl-5 mt-2 space-y-1">
|
||||
<li>銀行對帳單 (PDF/圖片檔)</li>
|
||||
@@ -40,89 +40,89 @@
|
||||
@csrf
|
||||
|
||||
<!-- Basic Information -->
|
||||
<div class="bg-white shadow sm:rounded-lg">
|
||||
<div class="bg-white dark:bg-gray-800 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>
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100 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 for="reconciliation_month" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
調節月份 <span class="text-red-500 dark:text-red-400">*</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">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-700 dark:text-gray-100 @error('reconciliation_month') border-red-300 dark:border-red-700 @enderror">
|
||||
@error('reconciliation_month')
|
||||
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $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 for="bank_statement_date" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
對帳單日期 <span class="text-red-500 dark:text-red-400">*</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">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-700 dark:text-gray-100 @error('bank_statement_date') border-red-300 dark:border-red-700 @enderror">
|
||||
@error('bank_statement_date')
|
||||
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $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 for="bank_statement_balance" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
銀行對帳單餘額 <span class="text-red-500 dark:text-red-400">*</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>
|
||||
<span class="text-gray-500 dark:text-gray-400 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">
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-700 pl-12 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-700 dark:text-gray-100 @error('bank_statement_balance') border-red-300 dark:border-red-700 @enderror">
|
||||
</div>
|
||||
@error('bank_statement_balance')
|
||||
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $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 for="system_book_balance" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
系統帳面餘額 <span class="text-red-500 dark:text-red-400">*</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>
|
||||
<span class="text-gray-500 dark:text-gray-400 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">
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-700 pl-12 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-700 dark:text-gray-100 @error('system_book_balance') border-red-300 dark:border-red-700 @enderror">
|
||||
</div>
|
||||
<p class="mt-1 text-xs text-gray-500">從現金簿自動帶入: NT$ {{ number_format($systemBalance, 2) }}</p>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">從現金簿自動帶入: NT$ {{ number_format($systemBalance, 2) }}</p>
|
||||
@error('system_book_balance')
|
||||
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $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 for="bank_statement_file" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
銀行對帳單檔案
|
||||
</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
|
||||
class="mt-1 block w-full text-sm text-gray-500 dark:text-gray-400
|
||||
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>
|
||||
file:bg-indigo-50 dark:file:bg-indigo-900/50 file:text-indigo-700 dark:file:text-indigo-300
|
||||
hover:file:bg-indigo-100 dark:hover:file:bg-indigo-800">
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">支援格式: PDF, JPG, PNG (最大 10MB)</p>
|
||||
@error('bank_statement_file')
|
||||
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
@@ -130,13 +130,13 @@
|
||||
</div>
|
||||
|
||||
<!-- Outstanding Checks -->
|
||||
<div class="bg-white shadow sm:rounded-lg">
|
||||
<div class="bg-white dark:bg-gray-800 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>
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100 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">
|
||||
<button type="button" onclick="addOutstandingCheck()" class="mt-3 inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600">
|
||||
<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>
|
||||
@@ -146,13 +146,13 @@
|
||||
</div>
|
||||
|
||||
<!-- Deposits in Transit -->
|
||||
<div class="bg-white shadow sm:rounded-lg">
|
||||
<div class="bg-white dark:bg-gray-800 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>
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100 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">
|
||||
<button type="button" onclick="addDeposit()" class="mt-3 inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600">
|
||||
<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>
|
||||
@@ -162,13 +162,13 @@
|
||||
</div>
|
||||
|
||||
<!-- Bank Charges -->
|
||||
<div class="bg-white shadow sm:rounded-lg">
|
||||
<div class="bg-white dark:bg-gray-800 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>
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100 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">
|
||||
<button type="button" onclick="addCharge()" class="mt-3 inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600">
|
||||
<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>
|
||||
@@ -178,20 +178,20 @@
|
||||
</div>
|
||||
|
||||
<!-- Notes -->
|
||||
<div class="bg-white shadow sm:rounded-lg">
|
||||
<div class="bg-white dark:bg-gray-800 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>
|
||||
<label for="notes" class="block text-sm font-medium text-gray-700 dark:text-gray-300">備註</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>
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-700 dark:text-gray-100">{{ 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 href="{{ route('admin.bank-reconciliations.index') }}" class="inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:focus:ring-indigo-600 focus:ring-offset-2 dark:focus:ring-offset-gray-800">
|
||||
取消
|
||||
</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 type="submit" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 dark:bg-indigo-500 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 dark:hover:bg-indigo-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:focus:ring-indigo-600 focus:ring-offset-2 dark:focus:ring-offset-gray-800">
|
||||
製作調節表
|
||||
</button>
|
||||
</div>
|
||||
@@ -208,22 +208,22 @@
|
||||
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.className = 'grid grid-cols-1 gap-4 sm:grid-cols-3 p-4 border border-gray-200 dark:border-gray-700 rounded-md';
|
||||
div.innerHTML = `
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">支票號碼</label>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">支票號碼</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">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-700 dark:text-gray-100">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">金額 <span class="text-red-500">*</span></label>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">金額 <span class="text-red-500 dark:text-red-400">*</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">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-700 dark:text-gray-100">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">說明</label>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">說明</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">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-700 dark:text-gray-100">
|
||||
</div>
|
||||
`;
|
||||
container.appendChild(div);
|
||||
@@ -233,22 +233,22 @@
|
||||
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.className = 'grid grid-cols-1 gap-4 sm:grid-cols-3 p-4 border border-gray-200 dark:border-gray-700 rounded-md';
|
||||
div.innerHTML = `
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">存款日期</label>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">存款日期</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">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-700 dark:text-gray-100">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">金額 <span class="text-red-500">*</span></label>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">金額 <span class="text-red-500 dark:text-red-400">*</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">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-700 dark:text-gray-100">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">說明</label>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">說明</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">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-700 dark:text-gray-100">
|
||||
</div>
|
||||
`;
|
||||
container.appendChild(div);
|
||||
@@ -258,18 +258,18 @@
|
||||
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.className = 'grid grid-cols-1 gap-4 sm:grid-cols-2 p-4 border border-gray-200 dark:border-gray-700 rounded-md';
|
||||
div.innerHTML = `
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">金額 <span class="text-red-500">*</span></label>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">金額 <span class="text-red-500 dark:text-red-400">*</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">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-700 dark:text-gray-100">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">說明</label>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">說明</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">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-700 dark:text-gray-100">
|
||||
</div>
|
||||
`;
|
||||
container.appendChild(div);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800">
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
|
||||
銀行調節表
|
||||
</h2>
|
||||
</x-slot>
|
||||
@@ -8,21 +8,21 @@
|
||||
<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 class="rounded-md bg-green-50 dark:bg-green-900/30 p-4">
|
||||
<p class="text-sm font-medium text-green-800 dark:text-green-200">{{ 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 class="rounded-md bg-red-50 dark:bg-red-900/30 p-4">
|
||||
<p class="text-sm font-medium text-red-800 dark:text-red-200">{{ 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">
|
||||
<a href="{{ route('admin.bank-reconciliations.create') }}" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 dark:bg-indigo-500 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 dark:hover:bg-indigo-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:focus:ring-indigo-600 focus:ring-offset-2 dark:focus:ring-offset-gray-800">
|
||||
<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>
|
||||
@@ -32,12 +32,12 @@
|
||||
@endcan
|
||||
|
||||
<!-- Filters -->
|
||||
<div class="bg-white shadow sm:rounded-lg">
|
||||
<div class="bg-white dark:bg-gray-800 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">
|
||||
<label for="reconciliation_status" class="block text-sm font-medium text-gray-700 dark:text-gray-300">狀態</label>
|
||||
<select name="reconciliation_status" id="reconciliation_status" class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300">
|
||||
<option value="">全部</option>
|
||||
<option value="pending" {{ request('reconciliation_status') == 'pending' ? 'selected' : '' }}>待覆核</option>
|
||||
<option value="completed" {{ request('reconciliation_status') == 'completed' ? 'selected' : '' }}>已完成</option>
|
||||
@@ -46,16 +46,16 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="month" class="block text-sm font-medium text-gray-700">調節月份</label>
|
||||
<label for="month" class="block text-sm font-medium text-gray-700 dark:text-gray-300">調節月份</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">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300">
|
||||
</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 type="submit" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 dark:bg-indigo-500 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 dark:hover:bg-indigo-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:focus:ring-indigo-600 focus:ring-offset-2 dark:focus:ring-offset-gray-800">
|
||||
篩選
|
||||
</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 href="{{ route('admin.bank-reconciliations.index') }}" class="ml-2 inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:focus:ring-indigo-600 focus:ring-offset-2 dark:focus:ring-offset-gray-800">
|
||||
清除
|
||||
</a>
|
||||
</div>
|
||||
@@ -64,71 +64,71 @@
|
||||
</div>
|
||||
|
||||
<!-- Reconciliations Table -->
|
||||
<div class="bg-white shadow sm:rounded-lg">
|
||||
<div class="bg-white dark:bg-gray-800 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">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<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">
|
||||
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">
|
||||
調節月份
|
||||
</th>
|
||||
<th scope="col" class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500">
|
||||
<th scope="col" class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">
|
||||
銀行餘額
|
||||
</th>
|
||||
<th scope="col" class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500">
|
||||
<th scope="col" class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">
|
||||
帳面餘額
|
||||
</th>
|
||||
<th scope="col" class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500">
|
||||
<th scope="col" class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">
|
||||
差異金額
|
||||
</th>
|
||||
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
|
||||
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">
|
||||
狀態
|
||||
</th>
|
||||
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
|
||||
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">
|
||||
製表人
|
||||
</th>
|
||||
<th scope="col" class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500">
|
||||
<th scope="col" class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">
|
||||
<span class="sr-only">操作</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 bg-white">
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700 bg-white dark:bg-gray-800">
|
||||
@forelse ($reconciliations as $reconciliation)
|
||||
<tr>
|
||||
<td class="whitespace-nowrap px-4 py-4 text-sm font-medium text-gray-900">
|
||||
<td class="whitespace-nowrap px-4 py-4 text-sm font-medium text-gray-900 dark:text-gray-100">
|
||||
{{ $reconciliation->reconciliation_month->format('Y年m月') }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-4 text-sm text-right text-gray-500">
|
||||
<td class="whitespace-nowrap px-4 py-4 text-sm text-right text-gray-500 dark:text-gray-400">
|
||||
NT$ {{ number_format($reconciliation->bank_statement_balance, 2) }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-4 text-sm text-right text-gray-500">
|
||||
<td class="whitespace-nowrap px-4 py-4 text-sm text-right text-gray-500 dark:text-gray-400">
|
||||
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' }}">
|
||||
<td class="whitespace-nowrap px-4 py-4 text-sm text-right {{ $reconciliation->discrepancy_amount > 0 ? 'text-red-600 dark:text-red-400 font-semibold' : 'text-gray-500 dark:text-gray-400' }}">
|
||||
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
|
||||
@if($reconciliation->reconciliation_status === 'completed') bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200
|
||||
@elseif($reconciliation->reconciliation_status === 'discrepancy') bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200
|
||||
@else bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-200
|
||||
@endif">
|
||||
{{ $reconciliation->getStatusText() }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-4 text-sm text-gray-500">
|
||||
<td class="whitespace-nowrap px-4 py-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ $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 href="{{ route('admin.bank-reconciliations.show', $reconciliation) }}" class="text-indigo-600 dark:text-indigo-400 hover:text-indigo-900 dark:hover:text-indigo-300">
|
||||
查看
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="7" class="px-4 py-8 text-center text-sm text-gray-500">
|
||||
<td colspan="7" class="px-4 py-8 text-center text-sm text-gray-500 dark:text-gray-400">
|
||||
沒有銀行調節表記錄
|
||||
</td>
|
||||
</tr>
|
||||
@@ -144,16 +144,16 @@
|
||||
</div>
|
||||
|
||||
<!-- Help Info -->
|
||||
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
||||
<div class="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 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">
|
||||
<svg class="h-5 w-5 text-blue-400 dark:text-blue-500" 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">
|
||||
<h3 class="text-sm font-medium text-blue-800 dark:text-blue-200">關於銀行調節表</h3>
|
||||
<div class="mt-2 text-sm text-blue-700 dark:text-blue-300">
|
||||
<p>銀行調節表用於核對銀行對帳單與內部現金簿的差異。建議每月定期製作,以確保帳務正確。</p>
|
||||
<p class="mt-1">調節流程:出納製作 → 會計覆核 → 主管核准</p>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<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>
|
||||
|
||||
@@ -9,13 +9,13 @@
|
||||
<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') }}">
|
||||
<form method="POST" action="{{ route('admin.budgets.store') }}" class="space-y-6" aria-label="新增預算表單">
|
||||
@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>
|
||||
會計年度 <span class="text-red-500" aria-label="必填">*</span>
|
||||
</label>
|
||||
<input type="number"
|
||||
name="fiscal_year"
|
||||
@@ -27,7 +27,7 @@
|
||||
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>
|
||||
@@ -37,7 +37,7 @@
|
||||
<!-- 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>
|
||||
預算名稱 <span class="text-red-500" aria-label="必填">*</span>
|
||||
</label>
|
||||
<input type="text"
|
||||
name="name"
|
||||
@@ -46,10 +46,10 @@
|
||||
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') }}"
|
||||
placeholder="例如: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>
|
||||
@@ -59,19 +59,19 @@
|
||||
<!-- 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>
|
||||
期間類型 <span class="text-red-500" aria-label="必填">*</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>
|
||||
<option value="annual" @selected(old('period_type', 'annual') === 'annual')>年度 </option>
|
||||
<option value="quarterly" @selected(old('period_type') === 'quarterly')>季度 </option>
|
||||
<option value="monthly" @selected(old('period_type') === '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>
|
||||
@@ -82,7 +82,7 @@
|
||||
<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>
|
||||
期間開始日期 <span class="text-red-500" aria-label="必填">*</span>
|
||||
</label>
|
||||
<input type="date"
|
||||
name="period_start"
|
||||
@@ -98,7 +98,7 @@
|
||||
|
||||
<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>
|
||||
期間結束日期 <span class="text-red-500" aria-label="必填">*</span>
|
||||
</label>
|
||||
<input type="date"
|
||||
name="period_end"
|
||||
@@ -116,16 +116,16 @@
|
||||
<!-- 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...') }}"
|
||||
placeholder="關於此預算的選填備註..."
|
||||
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>
|
||||
@@ -136,11 +136,11 @@
|
||||
<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>
|
||||
@@ -157,14 +157,14 @@
|
||||
</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>
|
||||
<p>建立預算後,您將能夠:</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>
|
||||
<li>為收入和支出帳戶新增預算項目</li>
|
||||
<li>提交預算以供主席核准</li>
|
||||
<li>啟用預算以開始追蹤實際金額</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<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 }}
|
||||
編輯預算 - {{ $budget->fiscal_year }}
|
||||
</h2>
|
||||
</x-slot>
|
||||
|
||||
@@ -13,30 +13,30 @@
|
||||
|
||||
<!-- 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>
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">基本資訊</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>
|
||||
<label for="name" class="block text-sm font-medium text-gray-700 dark:text-gray-300">預算名稱 *</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>
|
||||
<label for="period_start" class="block text-sm font-medium text-gray-700 dark:text-gray-300">期間開始 *</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>
|
||||
<label for="period_end" class="block text-sm font-medium text-gray-700 dark:text-gray-300">期間結束 *</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>
|
||||
<label for="notes" class="block text-sm font-medium text-gray-700 dark:text-gray-300">備註</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>
|
||||
@@ -45,25 +45,25 @@
|
||||
<!-- 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>
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">收入 </h3>
|
||||
<button type="button" @click="addItem('income')" class="btn-secondary text-sm">+ 新增收入項目</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>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">帳戶</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>
|
||||
<option value="">選擇帳戶...</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>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">金額</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>
|
||||
@@ -75,7 +75,7 @@
|
||||
</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>
|
||||
@@ -83,25 +83,25 @@
|
||||
<!-- 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>
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">支出 (/<EFBFBD>)</h3>
|
||||
<button type="button" @click="addItem('expense')" class="btn-secondary text-sm">+ 新增支出項目</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>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">帳戶</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>
|
||||
<option value="">選擇帳戶...</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>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">金額</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>
|
||||
@@ -113,15 +113,15 @@
|
||||
</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>
|
||||
<a href="{{ route('admin.budgets.show', $budget) }}" class="btn-secondary">取消</a>
|
||||
<button type="submit" class="btn-primary">儲存預算</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<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>
|
||||
|
||||
@@ -31,34 +31,34 @@
|
||||
<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') }}">
|
||||
aria-label="新增預算">
|
||||
<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') }}">
|
||||
<form method="GET" action="{{ route('admin.budgets.index') }}" class="mb-6 space-y-4" role="search" aria-label="篩選預算">
|
||||
<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>
|
||||
<option value="">所有年度</option>
|
||||
@foreach($fiscalYears as $year)
|
||||
<option value="{{ $year }}" @selected(request('fiscal_year') == $year)>
|
||||
{{ $year }}
|
||||
@@ -69,23 +69,23 @@
|
||||
|
||||
<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>
|
||||
<option value="">所有狀態</option>
|
||||
<option value="draft" @selected(request('status') === 'draft')>草稿</option>
|
||||
<option value="submitted" @selected(request('status') === 'submitted')>已提交</option>
|
||||
<option value="approved" @selected(request('status') === 'approved')>已核准</option>
|
||||
<option value="active" @selected(request('status') === 'active')>使用中</option>
|
||||
<option value="closed" @selected(request('status') === '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>
|
||||
@@ -95,27 +95,27 @@
|
||||
<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>
|
||||
<span class="sr-only">操作</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -133,24 +133,24 @@
|
||||
</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 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="狀態:草稿">
|
||||
草稿
|
||||
</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 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="狀態:已提交">
|
||||
已提交
|
||||
</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 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="狀態:已核准">
|
||||
已核准
|
||||
</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 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="狀態:使用中">
|
||||
使用中
|
||||
</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 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="狀態:已結案">
|
||||
已結案
|
||||
</span>
|
||||
@endif
|
||||
</td>
|
||||
@@ -160,15 +160,15 @@
|
||||
<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') }}
|
||||
aria-label="檢視 {{ $budget->fiscal_year }} 年度預算">
|
||||
檢視
|
||||
</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') }}
|
||||
aria-label="編輯 {{ $budget->fiscal_year }} 年度預算">
|
||||
編輯
|
||||
</a>
|
||||
@endif
|
||||
</td>
|
||||
@@ -179,15 +179,15 @@
|
||||
<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>
|
||||
<p class="mt-2 text-sm font-semibold">找不到預算</p>
|
||||
<p class="mt-1 text-sm">開始建立新的預算。</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>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<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 }}
|
||||
預算詳情 - {{ $budget->fiscal_year }}
|
||||
</h2>
|
||||
</x-slot>
|
||||
|
||||
@@ -24,23 +24,23 @@
|
||||
</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>
|
||||
<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"> 使用中</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>
|
||||
<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">已核准</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>
|
||||
<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>
|
||||
<a href="{{ route('admin.budgets.edit', $budget) }}" class="btn-secondary">編輯</a>
|
||||
@endif
|
||||
@if($budget->isDraft())
|
||||
<form method="POST" action="{{ route('admin.budgets.submit', $budget) }}">
|
||||
@csrf
|
||||
<button type="submit" class="btn-primary">{{ __('Submit') }}</button>
|
||||
<button type="submit" class="btn-primary">提交</button>
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
@@ -49,19 +49,19 @@
|
||||
<!-- 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>
|
||||
<dt class="text-sm text-gray-500 dark:text-gray-400">預算收入</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>
|
||||
<dt class="text-sm text-gray-500 dark:text-gray-400">預算支出</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>
|
||||
<dt class="text-sm text-gray-500 dark:text-gray-400">實際收入</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>
|
||||
<dt class="text-sm text-gray-500 dark:text-gray-400">實際支出</dt>
|
||||
<dd class="text-2xl font-bold text-gray-900 dark:text-gray-100">NT$ {{ number_format($budget->total_actual_expense) }}</dd>
|
||||
</div>
|
||||
</div>
|
||||
@@ -69,14 +69,14 @@
|
||||
<!-- 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>
|
||||
<h3 class="text-base font-semibold text-gray-900 dark:text-gray-100 mb-4">收入 </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>
|
||||
<th scope="col" class="py-3 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">帳戶</th>
|
||||
<th scope="col" class="px-3 py-3 text-right text-sm font-semibold text-gray-900 dark:text-gray-100">預算</th>
|
||||
<th scope="col" class="px-3 py-3 text-right text-sm font-semibold text-gray-900 dark:text-gray-100">實際</th>
|
||||
<th scope="col" class="px-3 py-3 text-right text-sm font-semibold text-gray-900 dark:text-gray-100">差異</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
@@ -96,14 +96,14 @@
|
||||
<!-- 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>
|
||||
<h3 class="text-base font-semibold text-gray-900 dark:text-gray-100 mb-4">支出 (/<EFBFBD>)</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>
|
||||
<th scope="col" class="py-3 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">帳戶</th>
|
||||
<th scope="col" class="px-3 py-3 text-right text-sm font-semibold text-gray-900 dark:text-gray-100">預算</th>
|
||||
<th scope="col" class="px-3 py-3 text-right text-sm font-semibold text-gray-900 dark:text-gray-100">實際</th>
|
||||
<th scope="col" class="px-3 py-3 text-right text-sm font-semibold text-gray-900 dark:text-gray-100">使用率</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800">
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
|
||||
現金簿餘額報表
|
||||
</h2>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-6">
|
||||
<div class="mx-auto max-w-5xl sm:px-6 lg:px-8 space-y-6">
|
||||
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="p-6 text-gray-900">
|
||||
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="p-6 text-gray-900 dark:text-gray-100">
|
||||
<h3 class="font-semibold mb-4">帳戶餘額</h3>
|
||||
<ul class="list-disc list-inside space-y-1">
|
||||
@foreach($accounts as $account)
|
||||
@@ -18,8 +18,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="p-6 text-gray-900">
|
||||
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="p-6 text-gray-900 dark:text-gray-100">
|
||||
<h3 class="font-semibold mb-4">本月摘要</h3>
|
||||
<p>收入:{{ $monthlySummary['receipts'] ?? 0 }}</p>
|
||||
<p>支出:{{ $monthlySummary['payments'] ?? 0 }}</p>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800">
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
|
||||
記錄現金簿分錄
|
||||
</h2>
|
||||
</x-slot>
|
||||
@@ -8,32 +8,32 @@
|
||||
<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 class="rounded-md bg-red-50 dark:bg-red-900/50 p-4">
|
||||
<p class="text-sm font-medium text-red-800 dark:text-red-200">{{ 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>
|
||||
<div class="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-4">
|
||||
<h3 class="text-sm font-medium text-blue-900 dark:text-blue-100 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>
|
||||
<dt class="font-medium text-blue-700 dark:text-blue-300">標題</dt>
|
||||
<dd class="text-blue-900 dark:text-blue-100">{{ $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>
|
||||
<dt class="font-medium text-blue-700 dark:text-blue-300">金額</dt>
|
||||
<dd class="text-blue-900 dark:text-blue-100">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>
|
||||
<dt class="font-medium text-blue-700 dark:text-blue-300">付款單號</dt>
|
||||
<dd class="text-blue-900 dark:text-blue-100 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>
|
||||
<dt class="font-medium text-blue-700 dark:text-blue-300">付款方式</dt>
|
||||
<dd class="text-blue-900 dark:text-blue-100">{{ $financeDocument->paymentOrder->getPaymentMethodText() }}</dd>
|
||||
</div>
|
||||
@endif
|
||||
</dl>
|
||||
@@ -48,127 +48,127 @@
|
||||
<input type="hidden" name="finance_document_id" value="{{ $financeDocument->id }}">
|
||||
@endif
|
||||
|
||||
<div class="bg-white shadow sm:rounded-lg">
|
||||
<div class="bg-white dark:bg-gray-800 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>
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100 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 for="entry_date" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
記帳日期 <span class="text-red-500 dark:text-red-400">*</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">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300 @error('entry_date') border-red-300 dark:border-red-700 @enderror">
|
||||
@error('entry_date')
|
||||
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $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 for="entry_type" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
類型 <span class="text-red-500 dark:text-red-400">*</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">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300 @error('entry_type') border-red-300 dark:border-red-700 @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>
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $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 for="payment_method" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
付款方式 <span class="text-red-500 dark:text-red-400">*</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">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300 @error('payment_method') border-red-300 dark:border-red-700 @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>
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<!-- Bank Account -->
|
||||
<div>
|
||||
<label for="bank_account" class="block text-sm font-medium text-gray-700">
|
||||
<label for="bank_account" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
銀行帳戶
|
||||
</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>
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300 @error('bank_account') border-red-300 dark:border-red-700 @enderror">
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">用於區分不同的現金/銀行帳戶</p>
|
||||
@error('bank_account')
|
||||
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||||
<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">
|
||||
金額 <span class="text-red-500">*</span>
|
||||
<label for="amount" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
金額 <span class="text-red-500 dark:text-red-400">*</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>
|
||||
<span class="text-gray-500 dark:text-gray-400 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">
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-700 pl-12 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300 @error('amount') border-red-300 dark:border-red-700 @enderror">
|
||||
</div>
|
||||
@error('amount')
|
||||
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<!-- Receipt Number -->
|
||||
<div>
|
||||
<label for="receipt_number" class="block text-sm font-medium text-gray-700">
|
||||
<label for="receipt_number" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
收據/憑證編號
|
||||
</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">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300 @error('receipt_number') border-red-300 dark:border-red-700 @enderror">
|
||||
@error('receipt_number')
|
||||
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<!-- Transaction Reference -->
|
||||
<div>
|
||||
<label for="transaction_reference" class="block text-sm font-medium text-gray-700">
|
||||
<label for="transaction_reference" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
交易參考號
|
||||
</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">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300 @error('transaction_reference') border-red-300 dark:border-red-700 @enderror">
|
||||
@error('transaction_reference')
|
||||
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<!-- Notes -->
|
||||
<div>
|
||||
<label for="notes" class="block text-sm font-medium text-gray-700">
|
||||
<label for="notes" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
備註
|
||||
</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>
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300">{{ old('notes') }}</textarea>
|
||||
@error('notes')
|
||||
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
@@ -176,16 +176,16 @@
|
||||
</div>
|
||||
|
||||
<!-- Help Text -->
|
||||
<div class="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
|
||||
<div class="bg-yellow-50 dark:bg-yellow-900/30 border border-yellow-200 dark:border-yellow-800 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">
|
||||
<svg class="h-5 w-5 text-yellow-400 dark:text-yellow-500" 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">
|
||||
<h3 class="text-sm font-medium text-yellow-800 dark:text-yellow-300">注意事項</h3>
|
||||
<div class="mt-2 text-sm text-yellow-700 dark:text-yellow-400">
|
||||
<ul class="list-disc pl-5 space-y-1">
|
||||
<li>提交後將自動計算交易前後餘額</li>
|
||||
<li>請確認金額和類型(收入/支出)正確</li>
|
||||
@@ -199,10 +199,10 @@
|
||||
|
||||
<!-- 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 href="{{ route('admin.cashier-ledger.index') }}" class="inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:focus:ring-indigo-600 focus:ring-offset-2 dark:focus:ring-offset-gray-800">
|
||||
取消
|
||||
</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 type="submit" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 dark:bg-indigo-500 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 dark:hover:bg-indigo-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:focus:ring-indigo-600 focus:ring-offset-2 dark:focus:ring-offset-gray-800">
|
||||
記錄分錄
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800">
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
|
||||
出納現金簿
|
||||
</h2>
|
||||
</x-slot>
|
||||
@@ -8,34 +8,34 @@
|
||||
<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 class="rounded-md bg-green-50 dark:bg-green-900/50 p-4">
|
||||
<p class="text-sm font-medium text-green-800 dark:text-green-200">{{ 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 class="rounded-md bg-red-50 dark:bg-red-900/50 p-4">
|
||||
<p class="text-sm font-medium text-red-800 dark:text-red-200">{{ 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">
|
||||
<a href="{{ route('admin.cashier-ledger.balance-report') }}" class="inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:focus:ring-indigo-600 focus:ring-offset-2 dark:focus:ring-offset-gray-800">
|
||||
<svg class="-ml-1 mr-2 h-5 w-5 text-gray-500 dark:text-gray-400" 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">
|
||||
<a href="{{ route('admin.cashier-ledger.export', request()->all()) }}" class="inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:focus:ring-indigo-600 focus:ring-offset-2 dark:focus:ring-offset-gray-800">
|
||||
<svg class="-ml-1 mr-2 h-5 w-5 text-gray-500 dark:text-gray-400" 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">
|
||||
<a href="{{ route('admin.cashier-ledger.create') }}" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 dark:bg-indigo-500 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 dark:hover:bg-indigo-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:focus:ring-indigo-600 focus:ring-offset-2 dark:focus:ring-offset-gray-800">
|
||||
<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>
|
||||
@@ -47,14 +47,14 @@
|
||||
|
||||
<!-- Current Balances Summary -->
|
||||
@if(isset($balances) && $balances->isNotEmpty())
|
||||
<div class="bg-white shadow sm:rounded-lg">
|
||||
<div class="bg-white dark:bg-gray-800 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>
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100 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' }}">
|
||||
<div class="rounded-lg border border-gray-200 dark:border-gray-700 p-4">
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ $account }}</dt>
|
||||
<dd class="mt-1 text-2xl font-semibold {{ $balance >= 0 ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400' }}">
|
||||
NT$ {{ number_format($balance, 2) }}
|
||||
</dd>
|
||||
</div>
|
||||
@@ -65,12 +65,12 @@
|
||||
@endif
|
||||
|
||||
<!-- Filters -->
|
||||
<div class="bg-white shadow sm:rounded-lg">
|
||||
<div class="bg-white dark:bg-gray-800 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">
|
||||
<label for="entry_type" class="block text-sm font-medium text-gray-700 dark:text-gray-300">類型</label>
|
||||
<select name="entry_type" id="entry_type" class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300">
|
||||
<option value="">全部</option>
|
||||
<option value="receipt" {{ request('entry_type') == 'receipt' ? 'selected' : '' }}>收入</option>
|
||||
<option value="payment" {{ request('entry_type') == 'payment' ? 'selected' : '' }}>支出</option>
|
||||
@@ -78,8 +78,8 @@
|
||||
</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">
|
||||
<label for="payment_method" class="block text-sm font-medium text-gray-700 dark:text-gray-300">付款方式</label>
|
||||
<select name="payment_method" id="payment_method" class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300">
|
||||
<option value="">全部</option>
|
||||
<option value="bank_transfer" {{ request('payment_method') == 'bank_transfer' ? 'selected' : '' }}>銀行轉帳</option>
|
||||
<option value="check" {{ request('payment_method') == 'check' ? 'selected' : '' }}>支票</option>
|
||||
@@ -88,22 +88,22 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="date_from" class="block text-sm font-medium text-gray-700">開始日期</label>
|
||||
<label for="date_from" class="block text-sm font-medium text-gray-700 dark:text-gray-300">開始日期</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">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="date_to" class="block text-sm font-medium text-gray-700">結束日期</label>
|
||||
<label for="date_to" class="block text-sm font-medium text-gray-700 dark:text-gray-300">結束日期</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">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300">
|
||||
</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 type="submit" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 dark:bg-indigo-500 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 dark:hover:bg-indigo-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:focus:ring-indigo-600 focus:ring-offset-2 dark:focus:ring-offset-gray-800">
|
||||
篩選
|
||||
</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 href="{{ route('admin.cashier-ledger.index') }}" class="ml-2 inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:focus:ring-indigo-600 focus:ring-offset-2 dark:focus:ring-offset-gray-800">
|
||||
清除
|
||||
</a>
|
||||
</div>
|
||||
@@ -112,80 +112,80 @@
|
||||
</div>
|
||||
|
||||
<!-- Ledger Entries Table -->
|
||||
<div class="bg-white shadow sm:rounded-lg">
|
||||
<div class="bg-white dark:bg-gray-800 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">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<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">
|
||||
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">
|
||||
日期
|
||||
</th>
|
||||
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
|
||||
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">
|
||||
類型
|
||||
</th>
|
||||
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
|
||||
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">
|
||||
付款方式
|
||||
</th>
|
||||
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
|
||||
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">
|
||||
銀行帳戶
|
||||
</th>
|
||||
<th scope="col" class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500">
|
||||
<th scope="col" class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">
|
||||
金額
|
||||
</th>
|
||||
<th scope="col" class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500">
|
||||
<th scope="col" class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">
|
||||
交易前餘額
|
||||
</th>
|
||||
<th scope="col" class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500">
|
||||
<th scope="col" class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">
|
||||
交易後餘額
|
||||
</th>
|
||||
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
|
||||
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">
|
||||
記錄人
|
||||
</th>
|
||||
<th scope="col" class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500">
|
||||
<th scope="col" class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">
|
||||
<span class="sr-only">操作</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 bg-white">
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700 bg-white dark:bg-gray-800">
|
||||
@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">
|
||||
<tr class="{{ $entry->isReceipt() ? 'bg-green-50 dark:bg-green-900/20' : 'bg-red-50 dark:bg-red-900/20' }}">
|
||||
<td class="whitespace-nowrap px-4 py-4 text-sm text-gray-900 dark:text-gray-100">
|
||||
{{ $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->isReceipt() ? 'bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200' : 'bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200' }}">
|
||||
{{ $entry->getEntryTypeText() }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-4 text-sm text-gray-500">
|
||||
<td class="whitespace-nowrap px-4 py-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ $entry->getPaymentMethodText() }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-4 text-sm text-gray-500">
|
||||
<td class="whitespace-nowrap px-4 py-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ $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' }}">
|
||||
<td class="whitespace-nowrap px-4 py-4 text-sm text-right font-medium {{ $entry->isReceipt() ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400' }}">
|
||||
{{ $entry->isReceipt() ? '+' : '-' }} NT$ {{ number_format($entry->amount, 2) }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-4 text-sm text-right text-gray-500">
|
||||
<td class="whitespace-nowrap px-4 py-4 text-sm text-right text-gray-500 dark:text-gray-400">
|
||||
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">
|
||||
<td class="whitespace-nowrap px-4 py-4 text-sm text-right font-semibold text-gray-900 dark:text-gray-100">
|
||||
NT$ {{ number_format($entry->balance_after, 2) }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-4 text-sm text-gray-500">
|
||||
<td class="whitespace-nowrap px-4 py-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ $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 href="{{ route('admin.cashier-ledger.show', $entry) }}" class="text-indigo-600 dark:text-indigo-400 hover:text-indigo-900 dark:hover:text-indigo-300">
|
||||
查看
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="9" class="px-4 py-8 text-center text-sm text-gray-500">
|
||||
<td colspan="9" class="px-4 py-8 text-center text-sm text-gray-500 dark:text-gray-400">
|
||||
沒有現金簿記錄
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800">
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
|
||||
現金簿分錄詳情
|
||||
</h2>
|
||||
</x-slot>
|
||||
@@ -8,63 +8,63 @@
|
||||
<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="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">
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900">分錄資訊</h3>
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100">分錄資訊</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->isReceipt() ? 'bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200' : 'bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200' }}">
|
||||
{{ $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>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">記帳日期</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">{{ $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>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">付款方式</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">{{ $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>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">銀行帳戶</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100 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' }}">
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">金額</dt>
|
||||
<dd class="mt-1 text-lg font-semibold {{ $entry->isReceipt() ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400' }}">
|
||||
{{ $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>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">收據/憑證編號</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100 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>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">交易參考號</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100 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>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">記錄人</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">{{ $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>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">記錄時間</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">{{ $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>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">備註</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">{{ $entry->notes }}</dd>
|
||||
</div>
|
||||
@endif
|
||||
</dl>
|
||||
@@ -72,36 +72,36 @@
|
||||
</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="bg-gradient-to-r from-blue-50 to-indigo-50 dark:from-blue-900/30 dark:to-indigo-900/30 shadow sm:rounded-lg border border-blue-200 dark:border-blue-800">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900 mb-4">餘額變動</h3>
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100 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">
|
||||
<div class="text-center p-4 bg-white dark:bg-gray-700 rounded-lg shadow-sm">
|
||||
<dt class="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">交易前餘額</dt>
|
||||
<dd class="mt-2 text-2xl font-semibold text-gray-700 dark:text-gray-300">
|
||||
{{ 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 class="text-center p-4 bg-white dark:bg-gray-700 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">
|
||||
<svg class="h-8 w-8 mx-auto {{ $entry->isReceipt() ? 'text-green-500 dark:text-green-400' : 'text-red-500 dark:text-red-400' }}" 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' }}">
|
||||
<div class="mt-1 text-lg font-semibold {{ $entry->isReceipt() ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400' }}">
|
||||
{{ $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' }}">
|
||||
<div class="text-center p-4 bg-white dark:bg-gray-700 rounded-lg shadow-sm">
|
||||
<dt class="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">交易後餘額</dt>
|
||||
<dd class="mt-2 text-2xl font-semibold {{ $entry->balance_after >= 0 ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400' }}">
|
||||
{{ number_format($entry->balance_after, 2) }}
|
||||
</dd>
|
||||
</div>
|
||||
@@ -111,34 +111,34 @@
|
||||
|
||||
<!-- Related Finance Document -->
|
||||
@if($entry->financeDocument)
|
||||
<div class="bg-white shadow sm:rounded-lg">
|
||||
<div class="bg-white dark:bg-gray-800 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>
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100 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>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">申請標題</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">{{ $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>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">申請類型</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">{{ $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>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">申請金額</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">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>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">關聯會員</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">{{ $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>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">付款單號</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">
|
||||
<a href="{{ route('admin.payment-orders.show', $entry->financeDocument->paymentOrder) }}" class="text-indigo-600 dark:text-indigo-400 hover:text-indigo-900 dark:hover:text-indigo-300 font-mono">
|
||||
{{ $entry->financeDocument->paymentOrder->payment_order_number }} →
|
||||
</a>
|
||||
</dd>
|
||||
@@ -146,7 +146,7 @@
|
||||
@endif
|
||||
</dl>
|
||||
<div class="mt-4">
|
||||
<a href="{{ route('admin.finance.show', $entry->financeDocument) }}" class="text-indigo-600 hover:text-indigo-900">
|
||||
<a href="{{ route('admin.finance.show', $entry->financeDocument) }}" class="text-indigo-600 dark:text-indigo-400 hover:text-indigo-900 dark:hover:text-indigo-300">
|
||||
查看完整申請單 →
|
||||
</a>
|
||||
</div>
|
||||
@@ -156,13 +156,13 @@
|
||||
|
||||
<!-- 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 href="{{ route('admin.cashier-ledger.index') }}" class="inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:focus:ring-indigo-600 focus:ring-offset-2 dark:focus:ring-offset-gray-800">
|
||||
返回列表
|
||||
</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 href="{{ route('admin.finance.show', $entry->financeDocument) }}" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 dark:bg-indigo-500 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 dark:hover:bg-indigo-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:focus:ring-indigo-600 focus:ring-offset-2 dark:focus:ring-offset-gray-800">
|
||||
查看報銷申請單
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800">
|
||||
{{ __('Admin Dashboard') }}
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
|
||||
管理後台
|
||||
</h2>
|
||||
</x-slot>
|
||||
|
||||
@@ -10,18 +10,18 @@
|
||||
<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="rounded-md bg-yellow-50 dark:bg-yellow-900/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">
|
||||
<svg class="h-5 w-5 text-yellow-400 dark:text-yellow-300" 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') }}
|
||||
<p class="text-sm font-medium text-yellow-800 dark:text-yellow-200">
|
||||
您有 {{ $myPendingApprovals }} 個報銷單等待您的核准。
|
||||
<a href="{{ route('admin.finance.index') }}" class="font-semibold underline hover:text-yellow-700 dark:hover:text-yellow-100">
|
||||
查看待核准項目
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
@@ -32,103 +32,103 @@
|
||||
{{-- 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="overflow-hidden rounded-lg bg-white dark:bg-gray-800 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">
|
||||
<svg class="h-6 w-6 text-gray-400 dark:text-gray-500" 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>
|
||||
<dt class="truncate text-sm font-medium text-gray-500 dark:text-gray-400">會員總數</dt>
|
||||
<dd class="text-2xl font-semibold text-gray-900 dark:text-gray-100">{{ number_format($totalMembers) }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-50 px-5 py-3">
|
||||
<div class="bg-gray-50 dark:bg-gray-700 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 href="{{ route('admin.members.index') }}" class="font-medium text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300">
|
||||
查看全部
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Active Members --}}
|
||||
<div class="overflow-hidden rounded-lg bg-white shadow">
|
||||
<div class="overflow-hidden rounded-lg bg-white dark:bg-gray-800 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">
|
||||
<svg class="h-6 w-6 text-green-400 dark:text-green-500" 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>
|
||||
<dt class="truncate text-sm font-medium text-gray-500 dark:text-gray-400">使用中會員</dt>
|
||||
<dd class="text-2xl font-semibold text-green-600 dark:text-green-400">{{ number_format($activeMembers) }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-50 px-5 py-3">
|
||||
<div class="bg-gray-50 dark:bg-gray-700 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 href="{{ route('admin.members.index', ['status' => 'active']) }}" class="font-medium text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300">
|
||||
查看使用中
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Expired Members --}}
|
||||
<div class="overflow-hidden rounded-lg bg-white shadow">
|
||||
<div class="overflow-hidden rounded-lg bg-white dark:bg-gray-800 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">
|
||||
<svg class="h-6 w-6 text-red-400 dark:text-red-500" 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>
|
||||
<dt class="truncate text-sm font-medium text-gray-500 dark:text-gray-400">已過期會員</dt>
|
||||
<dd class="text-2xl font-semibold text-red-600 dark:text-red-400">{{ number_format($expiredMembers) }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-50 px-5 py-3">
|
||||
<div class="bg-gray-50 dark:bg-gray-700 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 href="{{ route('admin.members.index', ['status' => 'expired']) }}" class="font-medium text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300">
|
||||
查看已過期
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Expiring Soon --}}
|
||||
<div class="overflow-hidden rounded-lg bg-white shadow">
|
||||
<div class="overflow-hidden rounded-lg bg-white dark:bg-gray-800 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">
|
||||
<svg class="h-6 w-6 text-yellow-400 dark:text-yellow-500" 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>
|
||||
<dt class="truncate text-sm font-medium text-gray-500 dark:text-gray-400">30天內到期</dt>
|
||||
<dd class="text-2xl font-semibold text-yellow-600 dark:text-yellow-400">{{ number_format($expiringSoon) }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-50 px-5 py-3">
|
||||
<div class="bg-gray-50 dark:bg-gray-700 px-5 py-3">
|
||||
<div class="text-sm">
|
||||
<span class="text-gray-500">{{ __('Renewal reminders needed') }}</span>
|
||||
<span class="text-gray-500 dark:text-gray-400">需要更新提醒</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -137,74 +137,74 @@
|
||||
{{-- 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="overflow-hidden rounded-lg bg-white dark:bg-gray-800 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">
|
||||
<svg class="h-6 w-6 text-gray-400 dark:text-gray-500" 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>
|
||||
<dt class="truncate text-sm font-medium text-gray-500 dark:text-gray-400">總收入</dt>
|
||||
<dd class="text-2xl font-semibold text-gray-900 dark:text-gray-100">${{ 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 class="bg-gray-50 dark:bg-gray-700 px-5 py-3">
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ number_format($totalPayments) }} 總付款
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- This Month Revenue --}}
|
||||
<div class="overflow-hidden rounded-lg bg-white shadow">
|
||||
<div class="overflow-hidden rounded-lg bg-white dark:bg-gray-800 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">
|
||||
<svg class="h-6 w-6 text-green-400 dark:text-green-500" 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>
|
||||
<dt class="truncate text-sm font-medium text-gray-500 dark:text-gray-400">本月</dt>
|
||||
<dd class="text-2xl font-semibold text-green-600 dark:text-green-400">${{ 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 class="bg-gray-50 dark:bg-gray-700 px-5 py-3">
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ number_format($paymentsThisMonth) }} 本月付款
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Pending Approvals --}}
|
||||
<div class="overflow-hidden rounded-lg bg-white shadow">
|
||||
<div class="overflow-hidden rounded-lg bg-white dark:bg-gray-800 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">
|
||||
<svg class="h-6 w-6 text-blue-400 dark:text-blue-500" 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>
|
||||
<dt class="truncate text-sm font-medium text-gray-500 dark:text-gray-400">報銷單</dt>
|
||||
<dd class="text-2xl font-semibold text-blue-600 dark:text-blue-400">{{ number_format($pendingApprovals) }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-50 px-5 py-3">
|
||||
<div class="bg-gray-50 dark:bg-gray-700 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 href="{{ route('admin.finance.index') }}" class="font-medium text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300">
|
||||
查看待處理
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -214,25 +214,25 @@
|
||||
{{-- 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="overflow-hidden rounded-lg bg-white dark:bg-gray-800 shadow">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900">{{ __('Recent Payments') }}</h3>
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100">最近付款</h3>
|
||||
<div class="mt-4 flow-root">
|
||||
@if ($recentPayments->count() > 0)
|
||||
<ul role="list" class="-my-5 divide-y divide-gray-200">
|
||||
<ul role="list" class="-my-5 divide-y divide-gray-200 dark:divide-gray-700">
|
||||
@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">
|
||||
<p class="truncate text-sm font-medium text-gray-900 dark:text-gray-100">
|
||||
{{ $payment->member?->full_name ?? __('N/A') }}
|
||||
</p>
|
||||
<p class="truncate text-sm text-gray-500">
|
||||
<p class="truncate text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ $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">
|
||||
<span class="inline-flex items-center rounded-full bg-green-100 dark:bg-green-900 px-2.5 py-0.5 text-xs font-medium text-green-800 dark:text-green-200">
|
||||
${{ number_format($payment->amount, 2) }}
|
||||
</span>
|
||||
</div>
|
||||
@@ -241,43 +241,96 @@
|
||||
@endforeach
|
||||
</ul>
|
||||
@else
|
||||
<p class="text-sm text-gray-500">{{ __('No recent payments.') }}</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">沒有最近的付款記錄。</p>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Finance Document Stats --}}
|
||||
<div class="overflow-hidden rounded-lg bg-white shadow">
|
||||
<div class="overflow-hidden rounded-lg bg-white dark:bg-gray-800 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>
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100">報銷單狀態</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>
|
||||
<span class="ml-3 text-sm font-medium text-gray-900 dark:text-gray-100">待核准</span>
|
||||
</div>
|
||||
<span class="text-sm font-semibold text-gray-900">{{ number_format($pendingApprovals) }}</span>
|
||||
<span class="text-sm font-semibold text-gray-900 dark:text-gray-100">{{ 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>
|
||||
<span class="ml-3 text-sm font-medium text-gray-900 dark:text-gray-100">完全核准</span>
|
||||
</div>
|
||||
<span class="text-sm font-semibold text-gray-900">{{ number_format($fullyApprovedDocs) }}</span>
|
||||
<span class="text-sm font-semibold text-gray-900 dark:text-gray-100">{{ 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>
|
||||
<span class="ml-3 text-sm font-medium text-gray-900 dark:text-gray-100">已拒絕</span>
|
||||
</div>
|
||||
<span class="text-sm font-semibold text-gray-900">{{ number_format($rejectedDocs) }}</span>
|
||||
<span class="text-sm font-semibold text-gray-900 dark:text-gray-100">{{ number_format($rejectedDocs) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Recent Announcements --}}
|
||||
@if($recentAnnouncements->isNotEmpty())
|
||||
<div class="mt-8">
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
||||
<div class="border-b border-gray-200 dark:border-gray-700 px-4 py-5 sm:px-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100">📢 最新公告</h3>
|
||||
@can('view_announcements')
|
||||
<a href="{{ route('admin.announcements.index') }}" class="text-sm font-medium text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300">
|
||||
查看全部 →
|
||||
</a>
|
||||
@endcan
|
||||
</div>
|
||||
</div>
|
||||
<ul role="list" class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
@foreach($recentAnnouncements as $announcement)
|
||||
<li class="px-4 py-4 sm:px-6 hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors">
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center mb-1">
|
||||
@if($announcement->is_pinned)
|
||||
<span class="mr-2 text-blue-500" title="置頂公告">📌</span>
|
||||
@endif
|
||||
<h4 class="text-sm font-semibold text-gray-900 dark:text-gray-100 truncate">
|
||||
{{ $announcement->title }}
|
||||
</h4>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 line-clamp-2">
|
||||
{{ $announcement->getExcerpt(120) }}
|
||||
</p>
|
||||
<div class="mt-2 flex items-center space-x-4 text-xs text-gray-500 dark:text-gray-400">
|
||||
<span>{{ $announcement->published_at?->diffForHumans() ?? $announcement->created_at->diffForHumans() }}</span>
|
||||
<span>•</span>
|
||||
<span>{{ $announcement->getAccessLevelLabel() }}</span>
|
||||
@if($announcement->view_count > 0)
|
||||
<span>•</span>
|
||||
<span>👁 {{ $announcement->view_count }} 次瀏覽</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@can('view_announcements')
|
||||
<a href="{{ route('admin.announcements.show', $announcement) }}" class="ml-4 flex-shrink-0 text-sm font-medium text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300">
|
||||
查看 →
|
||||
</a>
|
||||
@endcan
|
||||
</div>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
</x-app-layout>
|
||||
@@ -1,95 +1,95 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800">
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
|
||||
新增文件類別
|
||||
</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="bg-white dark:bg-gray-800 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">
|
||||
<label for="name" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
類別名稱 <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">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 @error('name') border-red-500 @enderror dark:bg-gray-700 dark:text-gray-100">
|
||||
@error('name')
|
||||
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="slug" class="block text-sm font-medium text-gray-700">
|
||||
<label for="slug" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
代碼 (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>
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 @error('slug') border-red-500 @enderror dark:bg-gray-700 dark:text-gray-100">
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">留空則自動產生</p>
|
||||
@error('slug')
|
||||
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||||
<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">
|
||||
<label for="description" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
說明
|
||||
</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>
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 dark:bg-gray-700 dark:text-gray-100">{{ old('description') }}</textarea>
|
||||
@error('description')
|
||||
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="icon" class="block text-sm font-medium text-gray-700">
|
||||
<label for="icon" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
圖示 (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>
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 dark:bg-gray-700 dark:text-gray-100">
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">輸入 emoji,例如:📄 📝 📊 📋</p>
|
||||
@error('icon')
|
||||
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="default_access_level" class="block text-sm font-medium text-gray-700">
|
||||
<label for="default_access_level" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
預設存取權限 <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">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 dark:bg-gray-700 dark:text-gray-100">
|
||||
<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>
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="sort_order" class="block text-sm font-medium text-gray-700">
|
||||
<label for="sort_order" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
排序順序
|
||||
</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>
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 dark:bg-gray-700 dark:text-gray-100">
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">數字越小越前面</p>
|
||||
@error('sort_order')
|
||||
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $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 href="{{ route('admin.document-categories.index') }}" class="rounded-md border border-gray-300 bg-white dark:bg-gray-700 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600">
|
||||
取消
|
||||
</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 type="submit" class="rounded-md border border-transparent bg-indigo-600 dark:bg-indigo-500 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 dark:hover:bg-indigo-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:focus:ring-indigo-600 focus:ring-offset-2 dark:focus:ring-offset-gray-800">
|
||||
建立類別
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,96 +1,54 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800">
|
||||
編輯文件類別
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
|
||||
編輯分類
|
||||
</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="bg-white dark:bg-gray-800 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 for="name" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
分類名稱 <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">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-700 dark:text-gray-100">
|
||||
@error('name')
|
||||
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="slug" class="block text-sm font-medium text-gray-700">
|
||||
代碼 (URL slug)
|
||||
<label for="icon" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
圖示(表情符號)
|
||||
</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>
|
||||
<input type="text" name="icon" id="icon" value="{{ old('icon', $documentCategory->icon) }}"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-700 dark:text-gray-100">
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">例如:📁, 📃, 📊</p>
|
||||
@error('icon')
|
||||
<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">
|
||||
說明
|
||||
<label for="description" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
描述
|
||||
</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>
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-700 dark:text-gray-100">{{ old('description', $documentCategory->description) }}</textarea>
|
||||
@error('description')
|
||||
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $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">
|
||||
更新類別
|
||||
<div class="flex items-center justify-end">
|
||||
<button type="submit" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 dark:bg-indigo-500 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 dark:hover:bg-indigo-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:focus:ring-indigo-600 focus:ring-offset-2 dark:focus:ring-offset-gray-800">
|
||||
更新分類
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800">
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
|
||||
文件類別管理
|
||||
</h2>
|
||||
</x-slot>
|
||||
@@ -8,23 +8,23 @@
|
||||
<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 class="rounded-md bg-green-50 dark:bg-green-900/30 p-4">
|
||||
<p class="text-sm font-medium text-green-800 dark:text-green-200">{{ 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 class="rounded-md bg-red-50 dark:bg-red-900/30 p-4">
|
||||
<p class="text-sm font-medium text-red-800 dark:text-red-200">{{ 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>
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">文件類別</h3>
|
||||
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">管理文件分類,設定預設存取權限</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">
|
||||
<a href="{{ route('admin.document-categories.create') }}" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 dark:bg-indigo-500 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 dark:hover:bg-indigo-600">
|
||||
<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>
|
||||
@@ -32,60 +32,60 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="bg-white shadow sm:rounded-lg">
|
||||
<div class="bg-white dark:bg-gray-800 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">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<thead class="bg-gray-50 dark:bg-gray-700">
|
||||
<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>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">圖示</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">名稱</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">代碼</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">預設存取</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">文件數量</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">排序</th>
|
||||
<th scope="col" class="px-6 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 bg-white">
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700 bg-white dark:bg-gray-800">
|
||||
@forelse ($categories as $category)
|
||||
<tr>
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
<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>
|
||||
<div class="text-sm font-medium text-gray-900 dark:text-gray-100">{{ $category->name }}</div>
|
||||
@if($category->description)
|
||||
<div class="text-sm text-gray-500">{{ $category->description }}</div>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">{{ $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 class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
|
||||
<code class="px-2 py-1 bg-gray-100 rounded dark:bg-gray-700 dark:text-gray-200">{{ $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
|
||||
@if($category->default_access_level === 'public') bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-200
|
||||
@elseif($category->default_access_level === 'members') bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-200
|
||||
@elseif($category->default_access_level === 'admin') bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-200
|
||||
@else bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-200
|
||||
@endif">
|
||||
{{ $category->getAccessLevelLabel() }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ $category->active_documents_count }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ $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 href="{{ route('admin.document-categories.edit', $category) }}" class="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300">
|
||||
編輯
|
||||
</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 type="submit" class="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300">
|
||||
刪除
|
||||
</button>
|
||||
</form>
|
||||
@@ -93,7 +93,7 @@
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="7" class="px-6 py-4 text-center text-sm text-gray-500">
|
||||
<td colspan="7" class="px-6 py-4 text-center text-sm text-gray-500 dark:text-gray-400">
|
||||
尚無類別資料
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800">
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
|
||||
上傳文件
|
||||
</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="bg-white dark:bg-gray-800 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 for="document_category_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
文件類別 <span class="text-red-500 dark:text-red-400">*</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">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 dark:bg-gray-900 dark:text-gray-300 @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' : '' }}>
|
||||
@@ -25,97 +25,97 @@
|
||||
@endforeach
|
||||
</select>
|
||||
@error('document_category_id')
|
||||
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="title" class="block text-sm font-medium text-gray-700">
|
||||
文件標題 <span class="text-red-500">*</span>
|
||||
<label for="title" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
文件標題 <span class="text-red-500 dark:text-red-400">*</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">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 dark:bg-gray-900 dark:text-gray-300 @error('title') border-red-500 @enderror">
|
||||
@error('title')
|
||||
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="document_number" class="block text-sm font-medium text-gray-700">
|
||||
<label for="document_number" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
文件編號
|
||||
</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>
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 dark:bg-gray-900 dark:text-gray-300 @error('document_number') border-red-500 @enderror">
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">選填,用於正式文件編號</p>
|
||||
@error('document_number')
|
||||
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||||
<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">
|
||||
<label for="description" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
文件說明
|
||||
</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>
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 dark:bg-gray-900 dark:text-gray-300 @error('description') border-red-500 @enderror">{{ old('description') }}</textarea>
|
||||
@error('description')
|
||||
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $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 for="access_level" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
存取權限 <span class="text-red-500 dark:text-red-400">*</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">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 dark:bg-gray-900 dark:text-gray-300 @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>
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="file" class="block text-sm font-medium text-gray-700">
|
||||
上傳檔案 <span class="text-red-500">*</span>
|
||||
<label for="file" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
上傳檔案 <span class="text-red-500 dark:text-red-400">*</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
|
||||
class="mt-1 block w-full text-sm text-gray-500 dark:text-gray-400
|
||||
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
|
||||
file:bg-indigo-50 dark:file:bg-indigo-900/50 file:text-indigo-700 dark:file:text-indigo-300
|
||||
hover:file:bg-indigo-100 dark:hover:file:bg-indigo-800
|
||||
@error('file') border-red-500 @enderror">
|
||||
<p class="mt-1 text-sm text-gray-500">支援格式:PDF, Word, Excel, 文字檔,最大 10MB</p>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">支援格式:PDF, Word, Excel, 文字檔,最大 10MB</p>
|
||||
@error('file')
|
||||
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="version_notes" class="block text-sm font-medium text-gray-700">
|
||||
<label for="version_notes" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
版本說明
|
||||
</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>
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 dark:bg-gray-900 dark:text-gray-300">{{ old('version_notes') }}</textarea>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">說明此版本的內容或變更</p>
|
||||
@error('version_notes')
|
||||
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="border-t border-gray-200 pt-4">
|
||||
<div class="border-t border-gray-200 dark:border-gray-700 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 href="{{ route('admin.documents.index') }}" class="rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600">
|
||||
取消
|
||||
</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 type="submit" class="rounded-md border border-transparent bg-indigo-600 dark:bg-indigo-500 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 dark:hover:bg-indigo-600">
|
||||
上傳文件
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800">
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
|
||||
編輯文件資訊
|
||||
</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="bg-white dark:bg-gray-800 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 for="document_category_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
文件類別 <span class="text-red-500 dark:text-red-400">*</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">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 dark:bg-gray-900 dark:text-gray-300">
|
||||
@foreach($categories as $category)
|
||||
<option value="{{ $category->id }}" {{ old('document_category_id', $document->document_category_id) == $category->id ? 'selected' : '' }}>
|
||||
{{ $category->icon }} {{ $category->name }}
|
||||
@@ -25,69 +25,69 @@
|
||||
@endforeach
|
||||
</select>
|
||||
@error('document_category_id')
|
||||
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="title" class="block text-sm font-medium text-gray-700">
|
||||
文件標題 <span class="text-red-500">*</span>
|
||||
<label for="title" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
文件標題 <span class="text-red-500 dark:text-red-400">*</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">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 dark:bg-gray-900 dark:text-gray-300">
|
||||
@error('title')
|
||||
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="document_number" class="block text-sm font-medium text-gray-700">
|
||||
<label for="document_number" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
文件編號
|
||||
</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">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 dark:bg-gray-900 dark:text-gray-300">
|
||||
@error('document_number')
|
||||
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||||
<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">
|
||||
<label for="description" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
文件說明
|
||||
</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>
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 dark:bg-gray-900 dark:text-gray-300">{{ old('description', $document->description) }}</textarea>
|
||||
@error('description')
|
||||
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $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 for="access_level" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
存取權限 <span class="text-red-500 dark:text-red-400">*</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">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 dark:bg-gray-900 dark:text-gray-300">
|
||||
<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>
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="bg-yellow-50 border border-yellow-200 rounded-md p-4">
|
||||
<div class="bg-yellow-50 dark:bg-yellow-900/30 border border-yellow-200 dark:border-yellow-800 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">
|
||||
<svg class="h-5 w-5 text-yellow-400 dark:text-yellow-500" 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">
|
||||
<h3 class="text-sm font-medium text-yellow-800 dark:text-yellow-300">注意</h3>
|
||||
<div class="mt-2 text-sm text-yellow-700 dark:text-yellow-400">
|
||||
<p>此處僅更新文件資訊,不會變更檔案內容。如需更新檔案,請使用「上傳新版本」功能。</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -95,10 +95,10 @@
|
||||
</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 href="{{ route('admin.documents.show', $document) }}" class="rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600">
|
||||
取消
|
||||
</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 type="submit" class="rounded-md border border-transparent bg-indigo-600 dark:bg-indigo-500 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 dark:hover:bg-indigo-600">
|
||||
更新資訊
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<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 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
|
||||
文件管理
|
||||
</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 href="{{ route('admin.documents.statistics') }}" class="inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
📊 統計分析
|
||||
</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 href="{{ route('admin.documents.create') }}" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 dark:bg-indigo-500 px-3 py-2 text-sm font-medium text-white hover:bg-indigo-700 dark:hover:bg-indigo-600">
|
||||
+ 上傳文件
|
||||
</a>
|
||||
</div>
|
||||
@@ -18,29 +18,29 @@
|
||||
<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 class="rounded-md bg-green-50 dark:bg-green-900/50 p-4">
|
||||
<p class="text-sm font-medium text-green-800 dark:text-green-200">{{ 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 class="rounded-md bg-red-50 dark:bg-red-900/50 p-4">
|
||||
<p class="text-sm font-medium text-red-800 dark:text-red-200">{{ session('error') }}</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Search and Filter -->
|
||||
<div class="bg-white shadow sm:rounded-lg p-6">
|
||||
<div class="bg-white dark:bg-gray-800 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>
|
||||
<label for="search" class="block text-sm font-medium text-gray-700 dark:text-gray-300">搜尋</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">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300">
|
||||
</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">
|
||||
<label for="category" class="block text-sm font-medium text-gray-700 dark:text-gray-300">類別</label>
|
||||
<select name="category" id="category" class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300">
|
||||
<option value="">全部類別</option>
|
||||
@foreach($categories as $cat)
|
||||
<option value="{{ $cat->id }}" {{ request('category') == $cat->id ? 'selected' : '' }}>
|
||||
@@ -50,8 +50,8 @@
|
||||
</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">
|
||||
<label for="access_level" class="block text-sm font-medium text-gray-700 dark:text-gray-300">存取權限</label>
|
||||
<select name="access_level" id="access_level" class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300">
|
||||
<option value="">全部</option>
|
||||
<option value="public" {{ request('access_level') === 'public' ? 'selected' : '' }}>公開</option>
|
||||
<option value="members" {{ request('access_level') === 'members' ? 'selected' : '' }}>會員</option>
|
||||
@@ -60,8 +60,8 @@
|
||||
</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">
|
||||
<label for="status" class="block text-sm font-medium text-gray-700 dark:text-gray-300">狀態</label>
|
||||
<select name="status" id="status" class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300">
|
||||
<option value="">全部</option>
|
||||
<option value="active" {{ request('status') === 'active' ? 'selected' : '' }}>啟用</option>
|
||||
<option value="archived" {{ request('status') === 'archived' ? 'selected' : '' }}>封存</option>
|
||||
@@ -69,10 +69,10 @@
|
||||
</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 href="{{ route('admin.documents.index') }}" class="rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
清除
|
||||
</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 type="submit" class="rounded-md border border-transparent bg-indigo-600 dark:bg-indigo-500 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 dark:hover:bg-indigo-600">
|
||||
搜尋
|
||||
</button>
|
||||
</div>
|
||||
@@ -81,9 +81,9 @@
|
||||
|
||||
<div class="flex justify-between items-center">
|
||||
<div>
|
||||
<p class="text-sm text-gray-600">共 {{ $documents->total() }} 個文件</p>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">共 {{ $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">
|
||||
<a href="{{ route('admin.documents.create') }}" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 dark:bg-indigo-500 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 dark:hover:bg-indigo-600">
|
||||
<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>
|
||||
@@ -92,56 +92,56 @@
|
||||
</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">
|
||||
<div class="bg-white dark:bg-gray-800 shadow overflow-hidden sm:rounded-lg">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<thead class="bg-gray-50 dark:bg-gray-700">
|
||||
<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>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">文件</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">類別</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">存取</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">版本</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">統計</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">狀態</th>
|
||||
<th scope="col" class="px-6 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 bg-white">
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700 bg-white dark:bg-gray-800">
|
||||
@forelse ($documents as $document)
|
||||
<tr class="hover:bg-gray-50">
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
<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>
|
||||
<div class="text-sm font-medium text-gray-900 dark:text-gray-100">{{ $document->title }}</div>
|
||||
@if($document->document_number)
|
||||
<div class="text-xs text-gray-500">{{ $document->document_number }}</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400">{{ $document->document_number }}</div>
|
||||
@endif
|
||||
@if($document->description)
|
||||
<div class="text-xs text-gray-500 mt-1">{{ Str::limit($document->description, 60) }}</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400 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">
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ $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
|
||||
@if($document->access_level === 'public') bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200
|
||||
@elseif($document->access_level === 'members') bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200
|
||||
@elseif($document->access_level === 'admin') bg-purple-100 dark:bg-purple-900 text-purple-800 dark:text-purple-200
|
||||
@else bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-300
|
||||
@endif">
|
||||
{{ $document->getAccessLevelLabel() }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
|
||||
<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">
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
|
||||
<div class="flex items-center space-x-3">
|
||||
<span title="檢視次數">👁️ {{ $document->view_count }}</span>
|
||||
<span title="下載次數">⬇️ {{ $document->download_count }}</span>
|
||||
@@ -149,12 +149,12 @@
|
||||
</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->status === 'active' ? 'bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200' : 'bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-300' }}">
|
||||
{{ $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 href="{{ route('admin.documents.show', $document) }}" class="text-indigo-600 dark:text-indigo-400 hover:text-indigo-900 dark:hover:text-indigo-300">
|
||||
查看
|
||||
</a>
|
||||
</td>
|
||||
@@ -162,12 +162,12 @@
|
||||
@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">
|
||||
<svg class="mx-auto h-12 w-12 text-gray-400 dark:text-gray-500" 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 class="mt-2 text-sm text-gray-500 dark:text-gray-400">尚無文件</p>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
<a href="{{ route('admin.documents.create') }}" class="text-indigo-600 dark:text-indigo-400 hover:text-indigo-900 dark:hover:text-indigo-300">上傳第一個文件</a>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -176,7 +176,7 @@
|
||||
</table>
|
||||
|
||||
@if($documents->hasPages())
|
||||
<div class="px-6 py-4 border-t border-gray-200">
|
||||
<div class="px-6 py-4 border-t border-gray-200 dark:border-gray-700">
|
||||
{{ $documents->links() }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
<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 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
|
||||
{{ $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 href="{{ route('admin.documents.edit', $document) }}" class="inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
編輯資訊
|
||||
</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 type="submit" class="inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
封存
|
||||
</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 type="submit" class="inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
恢復
|
||||
</button>
|
||||
</form>
|
||||
@@ -30,88 +30,94 @@
|
||||
<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 class="rounded-md bg-green-50 dark:bg-green-900/50 p-4">
|
||||
<p class="text-sm font-medium text-green-800 dark:text-green-200">{{ 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 class="rounded-md bg-red-50 dark:bg-red-900/50 p-4">
|
||||
<p class="text-sm font-medium text-red-800 dark:text-red-200">{{ session('error') }}</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Document Info -->
|
||||
<div class="bg-white shadow sm:rounded-lg">
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
||||
<div class="px-6 py-5">
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900">文件資訊</h3>
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100">文件資訊</h3>
|
||||
</div>
|
||||
<div class="border-t border-gray-200 px-6 py-5">
|
||||
<div class="border-t border-gray-200 dark:border-gray-700 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>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">類別</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">{{ $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>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">文件編號</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">{{ $document->document_number ?? '—' }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">存取權限</dt>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">存取權限</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
|
||||
@if($document->access_level === 'public') bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200
|
||||
@elseif($document->access_level === 'members') bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200
|
||||
@elseif($document->access_level === 'admin') bg-purple-100 dark:bg-purple-900 text-purple-800 dark:text-purple-200
|
||||
@else bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-300
|
||||
@endif">
|
||||
{{ $document->getAccessLevelLabel() }}
|
||||
</span>
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">狀態</dt>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">狀態</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->status === 'active' ? 'bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200' : 'bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-300' }}">
|
||||
{{ $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>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">當前版本</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
|
||||
@if($document->currentVersion)
|
||||
v{{ $document->currentVersion->version_number }}
|
||||
@else
|
||||
<span class="text-gray-400">尚無版本</span>
|
||||
@endif
|
||||
</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>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">總版本數</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">{{ $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>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">檢視 / 下載次數</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">{{ $document->view_count }} / {{ $document->download_count }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">公開連結</dt>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">公開連結</dt>
|
||||
<dd class="mt-1 text-sm">
|
||||
<a href="{{ $document->getPublicUrl() }}" target="_blank" class="text-indigo-600 hover:text-indigo-900">
|
||||
<a href="{{ $document->getPublicUrl() }}" target="_blank" class="text-indigo-600 dark:text-indigo-400 hover:text-indigo-900 dark:hover:text-indigo-300">
|
||||
{{ $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>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">說明</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">{{ $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>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">建立者</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">{{ $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>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">最後更新</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">{{ $document->lastUpdatedBy->name }} · {{ $document->updated_at->format('Y-m-d H:i') }}</dd>
|
||||
</div>
|
||||
@endif
|
||||
</dl>
|
||||
@@ -119,31 +125,31 @@
|
||||
</div>
|
||||
|
||||
<!-- Upload New Version -->
|
||||
<div class="bg-white shadow sm:rounded-lg">
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
||||
<div class="px-6 py-5">
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900">上傳新版本</h3>
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100">上傳新版本</h3>
|
||||
</div>
|
||||
<div class="border-t border-gray-200 px-6 py-5">
|
||||
<div class="border-t border-gray-200 dark:border-gray-700 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>
|
||||
<label for="file" class="block text-sm font-medium text-gray-700 dark:text-gray-300">選擇檔案 <span class="text-red-500 dark:text-red-400">*</span></label>
|
||||
<input type="file" name="file" id="file" required
|
||||
class="mt-1 block w-full text-sm text-gray-500
|
||||
class="mt-1 block w-full text-sm text-gray-500 dark:text-gray-400
|
||||
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>
|
||||
file:bg-indigo-50 dark:file:bg-indigo-900/50 file:text-indigo-700 dark:file:text-indigo-300
|
||||
hover:file:bg-indigo-100 dark:hover:file:bg-indigo-800">
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">最大 10MB</p>
|
||||
</div>
|
||||
<div>
|
||||
<label for="version_notes" class="block text-sm font-medium text-gray-700">版本說明 <span class="text-red-500">*</span></label>
|
||||
<label for="version_notes" class="block text-sm font-medium text-gray-700 dark:text-gray-300">版本說明 <span class="text-red-500 dark:text-red-400">*</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>
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 dark:bg-gray-900 dark:text-gray-300"></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 type="submit" class="rounded-md border border-transparent bg-indigo-600 dark:bg-indigo-500 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 dark:hover:bg-indigo-600">
|
||||
上傳新版本
|
||||
</button>
|
||||
</div>
|
||||
@@ -152,13 +158,13 @@
|
||||
</div>
|
||||
|
||||
<!-- Version History -->
|
||||
<div class="bg-white shadow sm:rounded-lg">
|
||||
<div class="bg-white dark:bg-gray-800 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>
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100">版本歷史</h3>
|
||||
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">所有版本永久保留,無法刪除</p>
|
||||
</div>
|
||||
<div class="border-t border-gray-200">
|
||||
<ul class="divide-y divide-gray-200">
|
||||
<div class="border-t border-gray-200 dark:border-gray-700">
|
||||
<ul class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
@foreach($versionHistory as $history)
|
||||
@php $version = $history['version']; @endphp
|
||||
<li class="px-6 py-5">
|
||||
@@ -168,15 +174,15 @@
|
||||
<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>
|
||||
<span class="text-sm font-medium text-gray-900 dark:text-gray-100">版本 {{ $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 class="inline-flex rounded-full bg-green-100 dark:bg-green-900 px-2 py-1 text-xs font-semibold text-green-800 dark:text-green-200">
|
||||
當前版本
|
||||
</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">
|
||||
<div class="mt-1 text-sm text-gray-900 dark:text-gray-100">{{ $version->original_filename }}</div>
|
||||
<div class="mt-1 flex items-center space-x-4 text-xs text-gray-500 dark:text-gray-400">
|
||||
<span>{{ $version->getFileSizeHuman() }}</span>
|
||||
<span>{{ $version->uploadedBy->name }}</span>
|
||||
<span>{{ $version->uploaded_at->format('Y-m-d H:i') }}</span>
|
||||
@@ -185,17 +191,17 @@
|
||||
@endif
|
||||
</div>
|
||||
@if($version->version_notes)
|
||||
<div class="mt-2 text-sm text-gray-600">
|
||||
<div class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
||||
<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">
|
||||
<div class="mt-2 flex items-center space-x-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
<span>檔案雜湊:</span>
|
||||
<code class="px-2 py-1 bg-gray-100 rounded">{{ substr($version->file_hash, 0, 16) }}...</code>
|
||||
<code class="px-2 py-1 bg-gray-100 dark:bg-gray-700 rounded">{{ substr($version->file_hash, 0, 16) }}...</code>
|
||||
@if($version->verifyIntegrity())
|
||||
<span class="text-green-600">✓ 完整</span>
|
||||
<span class="text-green-600 dark:text-green-400">✓ 完整</span>
|
||||
@else
|
||||
<span class="text-red-600">✗ 損壞</span>
|
||||
<span class="text-red-600 dark:text-red-400">✗ 損壞</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@@ -203,13 +209,13 @@
|
||||
</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">
|
||||
class="inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600">
|
||||
下載
|
||||
</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"
|
||||
<button type="submit" class="w-full inline-flex items-center justify-center rounded-md border border-indigo-300 dark:border-indigo-700 bg-indigo-50 dark:bg-indigo-900/50 px-3 py-2 text-sm font-medium text-indigo-700 dark:text-indigo-300 hover:bg-indigo-100 dark:hover:bg-indigo-800"
|
||||
onclick="return confirm('確定要將此版本設為當前版本嗎?');">
|
||||
設為當前
|
||||
</button>
|
||||
@@ -224,48 +230,48 @@
|
||||
</div>
|
||||
|
||||
<!-- Access Logs -->
|
||||
<div class="bg-white shadow sm:rounded-lg">
|
||||
<div class="bg-white dark:bg-gray-800 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>
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100">存取記錄</h3>
|
||||
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">最近 20 筆</p>
|
||||
</div>
|
||||
<div class="border-t border-gray-200">
|
||||
<div class="border-t border-gray-200 dark:border-gray-700">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<thead class="bg-gray-50 dark:bg-gray-700">
|
||||
<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>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">時間</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">使用者</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">動作</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">IP</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">瀏覽器</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 bg-white">
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700 bg-white dark:bg-gray-800">
|
||||
@forelse($document->accessLogs->take(20) as $log)
|
||||
<tr>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ $log->accessed_at->format('Y-m-d H:i:s') }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
|
||||
{{ $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->action === 'view' ? 'bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200' : 'bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200' }}">
|
||||
{{ $log->getActionLabel() }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ $log->ip_address }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ $log->getBrowser() }}
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="5" class="px-6 py-4 text-center text-sm text-gray-500">
|
||||
<td colspan="5" class="px-6 py-4 text-center text-sm text-gray-500 dark:text-gray-400">
|
||||
尚無存取記錄
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -1,27 +1,26 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800">
|
||||
{{ __('New Finance Document') }}
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
|
||||
新增報銷單
|
||||
</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="bg-white dark:bg-gray-800 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') }}">
|
||||
<form method="POST" action="{{ route('admin.finance.store') }}" enctype="multipart/form-data" class="space-y-6" aria-label="報銷單提交表單">
|
||||
@csrf
|
||||
|
||||
<div>
|
||||
<label for="member_id" class="block text-sm font-medium text-gray-700">
|
||||
{{ __('Member (optional)') }}
|
||||
<label for="member_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
會員(選填)
|
||||
</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"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300"
|
||||
>
|
||||
<option value="">{{ __('Not linked to a member') }}</option>
|
||||
<option value="">未連結會員</option>
|
||||
@foreach ($members as $member)
|
||||
<option value="{{ $member->id }}" @selected(old('member_id') == $member->id)>
|
||||
{{ $member->full_name }}
|
||||
@@ -29,83 +28,89 @@
|
||||
@endforeach
|
||||
</select>
|
||||
@error('member_id')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-2 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="title" class="block text-sm font-medium text-gray-700">
|
||||
{{ __('Title') }}
|
||||
<label for="title" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
標題
|
||||
</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"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300"
|
||||
required
|
||||
>
|
||||
@error('title')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-2 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="amount" class="block text-sm font-medium text-gray-700">
|
||||
{{ __('Amount') }}
|
||||
<label for="amount" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
金額 <span class="text-red-500">*</span>
|
||||
</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"
|
||||
>
|
||||
<div class="relative mt-1">
|
||||
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
|
||||
<span class="text-gray-500 dark:text-gray-400 sm:text-sm">NT$</span>
|
||||
</div>
|
||||
<input
|
||||
type="number"
|
||||
step="0.01"
|
||||
name="amount"
|
||||
id="amount"
|
||||
value="{{ old('amount') }}"
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-700 pl-12 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300"
|
||||
required
|
||||
>
|
||||
</div>
|
||||
@error('amount')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-2 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">
|
||||
{{ __('Description') }}
|
||||
<label for="description" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
描述
|
||||
</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"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300"
|
||||
>{{ old('description') }}</textarea>
|
||||
@error('description')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-2 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="attachment" class="block text-sm font-medium text-gray-700">
|
||||
{{ __('Attachment (optional)') }}
|
||||
<label for="attachment" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
附件(選填)
|
||||
</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"
|
||||
class="mt-1 block w-full text-sm text-gray-900 dark:text-gray-300 border border-gray-300 dark:border-gray-700 rounded-md cursor-pointer focus:outline-none focus:ring-indigo-500 dark:focus:ring-indigo-600 focus:border-indigo-500 dark:bg-gray-900"
|
||||
>
|
||||
@error('attachment')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-2 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
<p class="mt-1 text-sm text-gray-500">
|
||||
{{ __('Max file size: 10MB') }}
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
最大檔案大小: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 href="{{ route('admin.finance.index') }}" class="inline-flex items-center rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:focus:ring-indigo-600 focus:ring-offset-2 dark:focus:ring-offset-gray-800">
|
||||
取消
|
||||
</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 type="submit" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 dark:bg-indigo-500 px-4 py-2 text-sm font-medium text-white dark:text-white hover:bg-indigo-700 dark:hover:bg-indigo-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:focus:ring-indigo-600 focus:ring-offset-2 dark:focus:ring-offset-gray-800">
|
||||
提交文件
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -1,82 +1,179 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800">
|
||||
{{ __('Finance Documents') }}
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
|
||||
報銷申請單
|
||||
</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 class="rounded-md bg-green-50 dark:bg-green-900/30 p-4">
|
||||
<p class="text-sm font-medium text-green-800 dark:text-green-200">{{ 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') }}
|
||||
<div class="flex justify-between items-center">
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">報銷單列表</h3>
|
||||
<a href="{{ route('admin.finance.create') }}" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 dark:bg-indigo-500 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 dark:hover:bg-indigo-600 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 4v16m8-8H4" />
|
||||
</svg>
|
||||
新增報銷單
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="bg-white shadow sm:rounded-lg">
|
||||
{{-- Filters --}}
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<form method="GET" action="{{ route('admin.finance.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 dark:text-gray-300">審核狀態</label>
|
||||
<select name="status" id="status" class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-900 dark:text-gray-300">
|
||||
<option value="">全部狀態</option>
|
||||
<option value="pending" @selected(request('status') == 'pending')>待審核</option>
|
||||
<option value="approved_secretary" @selected(request('status') == 'approved_secretary')>秘書長已核准</option>
|
||||
<option value="approved_chair" @selected(request('status') == 'approved_chair')>理事長已核准</option>
|
||||
<option value="approved_board" @selected(request('status') == 'approved_board')>董理事會已核准</option>
|
||||
<option value="rejected" @selected(request('status') == 'rejected')>已駁回</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{{-- 工作流程階段篩選 --}}
|
||||
<div>
|
||||
<label for="workflow_stage" class="block text-sm font-medium text-gray-700 dark:text-gray-300">工作流程階段</label>
|
||||
<select name="workflow_stage" id="workflow_stage" class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-900 dark:text-gray-300">
|
||||
<option value="">全部階段</option>
|
||||
<option value="approval" @selected(request('workflow_stage') == 'approval')>審核階段</option>
|
||||
<option value="payment" @selected(request('workflow_stage') == 'payment')>出帳階段</option>
|
||||
<option value="recording" @selected(request('workflow_stage') == 'recording')>入帳階段</option>
|
||||
<option value="completed" @selected(request('workflow_stage') == 'completed')>已完成</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{{-- 金額級別篩選 --}}
|
||||
<div>
|
||||
<label for="amount_tier" class="block text-sm font-medium text-gray-700 dark:text-gray-300">金額級別</label>
|
||||
<select name="amount_tier" id="amount_tier" class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-900 dark:text-gray-300">
|
||||
<option value="">全部級別</option>
|
||||
<option value="small" @selected(request('amount_tier') == 'small')>小額 (< 5,000)</option>
|
||||
<option value="medium" @selected(request('amount_tier') == 'medium')>中額 (5,000-50,000)</option>
|
||||
<option value="large" @selected(request('amount_tier') == 'large')>大額 (> 50,000)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{{-- 篩選按鈕 --}}
|
||||
<div class="flex items-end space-x-2">
|
||||
<button type="submit" class="inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600">
|
||||
篩選
|
||||
</button>
|
||||
<a href="{{ route('admin.finance.index') }}" class="inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600">
|
||||
重設
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Table --}}
|
||||
<div class="bg-white dark:bg-gray-800 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">
|
||||
<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">
|
||||
{{ __('Title') }}
|
||||
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">
|
||||
標題
|
||||
</th>
|
||||
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
|
||||
{{ __('Member') }}
|
||||
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">
|
||||
申請人
|
||||
</th>
|
||||
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
|
||||
{{ __('Amount') }}
|
||||
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">
|
||||
金額
|
||||
</th>
|
||||
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
|
||||
{{ __('Status') }}
|
||||
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">
|
||||
審核狀態
|
||||
</th>
|
||||
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
|
||||
{{ __('Submitted At') }}
|
||||
<th scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">
|
||||
工作流程
|
||||
</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 scope="col" class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-300">
|
||||
提交時間
|
||||
</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">
|
||||
<span class="sr-only">操作</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 bg-white">
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700 bg-white dark:bg-gray-800">
|
||||
@forelse ($documents as $document)
|
||||
<tr>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900">
|
||||
{{ $document->title }}
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900 dark:text-gray-100">
|
||||
<div class="font-medium">{{ $document->title }}</div>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900">
|
||||
{{ $document->member?->full_name ?? __('N/A') }}
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900 dark:text-gray-100">
|
||||
{{ $document->submittedBy?->name ?? '不適用' }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900">
|
||||
@if (! is_null($document->amount))
|
||||
{{ number_format($document->amount, 2) }}
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900 dark:text-gray-100">
|
||||
<div>NT$ {{ number_format($document->amount, 2) }}</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400">{{ $document->getAmountTierText() }}</div>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm">
|
||||
@if ($document->isRejected())
|
||||
<span class="inline-flex rounded-full bg-red-100 dark:bg-red-900 px-2 text-xs font-semibold leading-5 text-red-800 dark:text-red-200">
|
||||
{{ $document->status_label }}
|
||||
</span>
|
||||
@elseif ($document->isApprovalComplete())
|
||||
<span class="inline-flex rounded-full bg-green-100 dark:bg-green-900 px-2 text-xs font-semibold leading-5 text-green-800 dark:text-green-200">
|
||||
{{ $document->status_label }}
|
||||
</span>
|
||||
@else
|
||||
{{ __('N/A') }}
|
||||
<span class="inline-flex rounded-full bg-yellow-100 dark:bg-yellow-900 px-2 text-xs font-semibold leading-5 text-yellow-800 dark:text-yellow-200">
|
||||
{{ $document->status_label }}
|
||||
</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900">
|
||||
{{ ucfirst($document->status) }}
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm">
|
||||
@if ($document->isRejected())
|
||||
<span class="inline-flex rounded-full bg-red-100 dark:bg-red-900 px-2 text-xs font-semibold leading-5 text-red-800 dark:text-red-200">
|
||||
已駁回
|
||||
</span>
|
||||
@elseif ($document->isRecordingComplete())
|
||||
<span class="inline-flex rounded-full bg-green-100 dark:bg-green-900 px-2 text-xs font-semibold leading-5 text-green-800 dark:text-green-200">
|
||||
已完成
|
||||
</span>
|
||||
@elseif ($document->isDisbursementComplete())
|
||||
<span class="inline-flex rounded-full bg-blue-100 dark:bg-blue-900 px-2 text-xs font-semibold leading-5 text-blue-800 dark:text-blue-200">
|
||||
入帳階段
|
||||
</span>
|
||||
@elseif ($document->isApprovalComplete())
|
||||
<span class="inline-flex rounded-full bg-purple-100 dark:bg-purple-900 px-2 text-xs font-semibold leading-5 text-purple-800 dark:text-purple-200">
|
||||
出帳階段
|
||||
</span>
|
||||
@else
|
||||
<span class="inline-flex rounded-full bg-gray-100 dark:bg-gray-700 px-2 text-xs font-semibold leading-5 text-gray-800 dark:text-gray-200">
|
||||
審核階段
|
||||
</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900">
|
||||
{{ optional($document->submitted_at)->toDateString() }}
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900 dark:text-gray-100">
|
||||
{{ optional($document->submitted_at)->format('Y-m-d') }}
|
||||
</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 href="{{ route('admin.finance.show', $document) }}" class="text-indigo-600 dark:text-indigo-400 hover:text-indigo-900 dark:hover:text-indigo-300">
|
||||
檢視
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="6" class="px-4 py-4 text-sm text-gray-500">
|
||||
{{ __('No finance documents found.') }}
|
||||
<td colspan="7" 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" 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 font-semibold">找不到報銷申請單</p>
|
||||
<p class="mt-1 text-sm">點選「新增報銷單」開始建立。</p>
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
@@ -92,4 +189,3 @@
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<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 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
|
||||
報銷申請單詳情
|
||||
</h2>
|
||||
<a href="{{ route('admin.finance.index') }}" class="text-sm text-indigo-600 hover:text-indigo-900">
|
||||
← {{ __('Back to list') }}
|
||||
<a href="{{ route('admin.finance.index') }}" class="text-sm text-indigo-600 dark:text-indigo-400 hover:text-indigo-900 dark:hover:text-indigo-300">
|
||||
← 返回列表
|
||||
</a>
|
||||
</div>
|
||||
</x-slot>
|
||||
@@ -14,39 +14,82 @@
|
||||
<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">
|
||||
<div class="rounded-md bg-green-50 dark:bg-green-900/30 p-4" role="status" aria-live="polite">
|
||||
<p class="text-sm font-medium text-green-800 dark:text-green-200">
|
||||
{{ session('status') }}
|
||||
</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Document Details --}}
|
||||
<div class="bg-white shadow sm:rounded-lg">
|
||||
{{-- Workflow Stage Overview --}}
|
||||
<div class="bg-white dark:bg-gray-800 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') }}
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100">
|
||||
工作流程階段
|
||||
</h3>
|
||||
<span class="inline-flex rounded-full px-3 py-1 text-sm font-semibold
|
||||
@if ($document->isRejected())
|
||||
bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200
|
||||
@elseif ($document->isRecordingComplete())
|
||||
bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200
|
||||
@else
|
||||
bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-200
|
||||
@endif
|
||||
">
|
||||
{{ $document->workflow_stage_label }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{{-- Stage Progress Bar --}}
|
||||
<div class="flex items-center space-x-2">
|
||||
{{-- 審核階段 --}}
|
||||
<div class="flex-1 text-center">
|
||||
<div class="h-2 rounded-full {{ $document->isApprovalComplete() || $document->isRejected() ? 'bg-green-500' : 'bg-gray-200 dark:bg-gray-700' }}"></div>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400 mt-1">審核</span>
|
||||
</div>
|
||||
<div class="w-4 text-gray-400">→</div>
|
||||
{{-- 出帳階段 --}}
|
||||
<div class="flex-1 text-center">
|
||||
<div class="h-2 rounded-full {{ $document->isDisbursementComplete() ? 'bg-green-500' : ($document->isApprovalComplete() && !$document->isRejected() ? 'bg-yellow-500' : 'bg-gray-200 dark:bg-gray-700') }}"></div>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400 mt-1">出帳</span>
|
||||
</div>
|
||||
<div class="w-4 text-gray-400">→</div>
|
||||
{{-- 入帳階段 --}}
|
||||
<div class="flex-1 text-center">
|
||||
<div class="h-2 rounded-full {{ $document->isRecordingComplete() ? 'bg-green-500' : ($document->isDisbursementComplete() ? 'bg-yellow-500' : 'bg-gray-200 dark:bg-gray-700') }}"></div>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400 mt-1">入帳</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Document Details --}}
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100 mb-4">
|
||||
報銷單資訊
|
||||
</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>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">標題</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">{{ $document->title }}</dd>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">{{ __('Status') }}</dt>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">審核狀態</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">
|
||||
<span class="inline-flex rounded-full bg-red-100 dark:bg-red-900 px-2 text-xs font-semibold leading-5 text-red-800 dark:text-red-200">
|
||||
{{ $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">
|
||||
@elseif ($document->isApprovalComplete())
|
||||
<span class="inline-flex rounded-full bg-green-100 dark:bg-green-900 px-2 text-xs font-semibold leading-5 text-green-800 dark:text-green-200">
|
||||
{{ $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">
|
||||
<span class="inline-flex rounded-full bg-yellow-100 dark:bg-yellow-900 px-2 text-xs font-semibold leading-5 text-yellow-800 dark:text-yellow-200">
|
||||
{{ $document->status_label }}
|
||||
</span>
|
||||
@endif
|
||||
@@ -54,52 +97,47 @@
|
||||
</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">
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">金額</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
|
||||
NT$ {{ number_format($document->amount, 2) }}
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400 ml-1">({{ $document->getAmountTierText() }})</span>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">申請人</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
|
||||
{{ $document->submittedBy?->name ?? '不適用' }}
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">提交時間</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
|
||||
{{ $document->submitted_at?->format('Y-m-d H:i:s') ?? '不適用' }}
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
@if ($document->member)
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">關聯會員</dt>
|
||||
<dd class="mt-1 text-sm">
|
||||
<a href="{{ route('admin.members.show', $document->member) }}" class="text-indigo-600 dark:text-indigo-400 hover:text-indigo-900 dark:hover:text-indigo-300">
|
||||
{{ $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>
|
||||
</dd>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@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">
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">附件</dt>
|
||||
<dd class="mt-1 text-sm">
|
||||
<a href="{{ route('admin.finance.download', $document) }}" class="inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600">
|
||||
<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>
|
||||
@@ -107,57 +145,59 @@
|
||||
|
||||
@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>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">描述</dt>
|
||||
<dd class="mt-1 whitespace-pre-line text-sm text-gray-900 dark:text-gray-100">{{ $document->description }}</dd>
|
||||
</div>
|
||||
@endif
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Approval Timeline --}}
|
||||
<div class="bg-white shadow sm:rounded-lg">
|
||||
{{-- Approval Timeline (新工作流程) --}}
|
||||
<div class="bg-white dark:bg-gray-800 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 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100 mb-4">
|
||||
審核時程
|
||||
</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>
|
||||
<span class="absolute left-4 top-4 -ml-px h-full w-0.5 bg-gray-200 dark:bg-gray-700" 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">
|
||||
@if ($document->secretary_approved_at)
|
||||
<span class="flex h-8 w-8 items-center justify-center rounded-full bg-green-500 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>
|
||||
@elseif ($document->status === 'pending')
|
||||
<span class="flex h-8 w-8 items-center justify-center rounded-full bg-yellow-500 ring-8 ring-white dark:ring-gray-800">
|
||||
<span class="h-2.5 w-2.5 rounded-full bg-white"></span>
|
||||
</span>
|
||||
@else
|
||||
<span class="flex h-8 w-8 items-center justify-center rounded-full bg-gray-400 ring-8 ring-white">
|
||||
<span class="flex h-8 w-8 items-center justify-center rounded-full bg-gray-400 dark:bg-gray-600 ring-8 ring-white dark:ring-gray-800">
|
||||
<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>
|
||||
<p class="text-sm text-gray-900 dark:text-gray-100">
|
||||
秘書長核准
|
||||
@if ($document->secretary_approved_at)
|
||||
<span class="font-medium">{{ $document->approvedBySecretary?->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') }}
|
||||
<div class="whitespace-nowrap text-right text-sm text-gray-500 dark:text-gray-400">
|
||||
@if ($document->secretary_approved_at)
|
||||
{{ $document->secretary_approved_at->format('Y-m-d H:i') }}
|
||||
@else
|
||||
{{ __('Pending') }}
|
||||
待處理
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@@ -165,90 +205,102 @@
|
||||
</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">
|
||||
{{-- 理事長審核 (第二階段:中額以上) --}}
|
||||
@if (in_array($document->amount_tier, ['medium', 'large']))
|
||||
<li>
|
||||
<div class="relative pb-8">
|
||||
<span class="absolute left-4 top-4 -ml-px h-full w-0.5 bg-gray-200 dark:bg-gray-700" aria-hidden="true"></span>
|
||||
<div class="relative flex space-x-3">
|
||||
<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') }}
|
||||
<span class="flex h-8 w-8 items-center justify-center rounded-full bg-green-500 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>
|
||||
@elseif ($document->status === 'approved_secretary')
|
||||
<span class="flex h-8 w-8 items-center justify-center rounded-full bg-yellow-500 ring-8 ring-white dark:ring-gray-800">
|
||||
<span class="h-2.5 w-2.5 rounded-full bg-white"></span>
|
||||
</span>
|
||||
@else
|
||||
{{ __('Pending') }}
|
||||
<span class="flex h-8 w-8 items-center justify-center rounded-full bg-gray-400 dark:bg-gray-600 ring-8 ring-white dark:ring-gray-800">
|
||||
<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 dark:text-gray-100">
|
||||
理事長核准
|
||||
@if ($document->chair_approved_at)
|
||||
<span class="font-medium">{{ $document->approvedByChair?->name }}</span>
|
||||
@endif
|
||||
</p>
|
||||
</div>
|
||||
<div class="whitespace-nowrap text-right text-sm text-gray-500 dark:text-gray-400">
|
||||
@if ($document->chair_approved_at)
|
||||
{{ $document->chair_approved_at->format('Y-m-d H:i') }}
|
||||
@else
|
||||
待處理
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</li>
|
||||
@endif
|
||||
|
||||
{{-- Rejection Info --}}
|
||||
{{-- 董理事會審核 (第三階段:大額) --}}
|
||||
@if ($document->amount_tier === 'large')
|
||||
<li>
|
||||
<div class="relative pb-8">
|
||||
<div class="relative flex space-x-3">
|
||||
<div>
|
||||
@if ($document->board_meeting_approved_at)
|
||||
<span class="flex h-8 w-8 items-center justify-center rounded-full bg-green-500 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>
|
||||
@elseif ($document->status === 'approved_chair')
|
||||
<span class="flex h-8 w-8 items-center justify-center rounded-full bg-yellow-500 ring-8 ring-white dark:ring-gray-800">
|
||||
<span class="h-2.5 w-2.5 rounded-full bg-white"></span>
|
||||
</span>
|
||||
@else
|
||||
<span class="flex h-8 w-8 items-center justify-center rounded-full bg-gray-400 dark:bg-gray-600 ring-8 ring-white dark:ring-gray-800">
|
||||
<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 dark:text-gray-100">
|
||||
董理事會核准
|
||||
@if ($document->board_meeting_approved_at)
|
||||
<span class="font-medium">{{ $document->approvedByBoardMeeting?->title ?? '理事會決議' }}</span>
|
||||
@endif
|
||||
</p>
|
||||
</div>
|
||||
<div class="whitespace-nowrap text-right text-sm text-gray-500 dark:text-gray-400">
|
||||
@if ($document->board_meeting_approved_at)
|
||||
{{ $document->board_meeting_approved_at->format('Y-m-d H:i') }}
|
||||
@else
|
||||
待處理
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
@endif
|
||||
|
||||
{{-- 駁回資訊 --}}
|
||||
@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">
|
||||
<span class="flex h-8 w-8 items-center justify-center rounded-full bg-red-500 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>
|
||||
@@ -256,17 +308,17 @@
|
||||
</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 class="text-sm text-gray-900 dark:text-gray-100">
|
||||
駁回者
|
||||
<span class="font-medium">{{ $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 class="mt-2 text-sm text-red-600 dark:text-red-400">
|
||||
<strong>原因:</strong> {{ $document->rejection_reason }}
|
||||
</p>
|
||||
@endif
|
||||
</div>
|
||||
<div class="whitespace-nowrap text-right text-sm text-gray-500">
|
||||
<div class="whitespace-nowrap text-right text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ $document->rejected_at?->format('Y-m-d H:i') }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -279,23 +331,150 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Approval Actions --}}
|
||||
@if (!$document->isRejected() && !$document->isFullyApproved())
|
||||
<div class="bg-white shadow sm:rounded-lg">
|
||||
{{-- 出帳確認區塊 --}}
|
||||
@if ($document->isApprovalComplete() && !$document->isRejected())
|
||||
<div class="bg-white dark:bg-gray-800 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 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100 mb-4">
|
||||
出帳確認
|
||||
<span class="ml-2 text-sm font-normal text-gray-500 dark:text-gray-400">(需申請人與出納雙重確認)</span>
|
||||
</h3>
|
||||
|
||||
<div class="space-y-4">
|
||||
{{-- 申請人確認狀態 --}}
|
||||
<div class="flex items-center justify-between p-4 rounded-lg {{ $document->requester_confirmed_at ? 'bg-green-50 dark:bg-green-900/30' : 'bg-gray-50 dark:bg-gray-700' }}">
|
||||
<div class="flex items-center">
|
||||
@if ($document->requester_confirmed_at)
|
||||
<svg class="h-5 w-5 text-green-500 mr-3" 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>
|
||||
@else
|
||||
<span class="h-5 w-5 rounded-full border-2 border-gray-300 dark:border-gray-500 mr-3"></span>
|
||||
@endif
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-900 dark:text-gray-100">申請人確認領款</p>
|
||||
@if ($document->requester_confirmed_at)
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ $document->requesterConfirmedBy?->name }} - {{ $document->requester_confirmed_at->format('Y-m-d H:i') }}
|
||||
</p>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@if (!$document->requester_confirmed_at && $document->canRequesterConfirmDisbursement(auth()->user()))
|
||||
<form method="POST" action="{{ route('admin.finance.confirm-disbursement', $document) }}">
|
||||
@csrf
|
||||
<button type="submit" class="inline-flex items-center rounded-md bg-blue-600 px-3 py-2 text-sm font-medium text-white hover:bg-blue-700">
|
||||
確認已領款
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- 出納確認狀態 --}}
|
||||
<div class="flex items-center justify-between p-4 rounded-lg {{ $document->cashier_confirmed_at ? 'bg-green-50 dark:bg-green-900/30' : 'bg-gray-50 dark:bg-gray-700' }}">
|
||||
<div class="flex items-center">
|
||||
@if ($document->cashier_confirmed_at)
|
||||
<svg class="h-5 w-5 text-green-500 mr-3" 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>
|
||||
@else
|
||||
<span class="h-5 w-5 rounded-full border-2 border-gray-300 dark:border-gray-500 mr-3"></span>
|
||||
@endif
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-900 dark:text-gray-100">出納確認出帳</p>
|
||||
@if ($document->cashier_confirmed_at)
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ $document->cashierConfirmedBy?->name }} - {{ $document->cashier_confirmed_at->format('Y-m-d H:i') }}
|
||||
</p>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@if (!$document->cashier_confirmed_at && $document->canCashierConfirmDisbursement() && (auth()->user()->hasRole('finance_cashier') || auth()->user()->hasRole('admin')))
|
||||
<form method="POST" action="{{ route('admin.finance.confirm-disbursement', $document) }}">
|
||||
@csrf
|
||||
<button type="submit" class="inline-flex items-center rounded-md bg-blue-600 px-3 py-2 text-sm font-medium text-white hover:bg-blue-700">
|
||||
確認已出帳
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- 出帳狀態摘要 --}}
|
||||
<div class="text-center text-sm {{ $document->isDisbursementComplete() ? 'text-green-600 dark:text-green-400' : 'text-gray-500 dark:text-gray-400' }}">
|
||||
@if ($document->isDisbursementComplete())
|
||||
出帳確認完成
|
||||
@else
|
||||
{{ $document->disbursement_status_label }}
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- 入帳確認區塊 --}}
|
||||
@if ($document->isDisbursementComplete())
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100 mb-4">
|
||||
入帳確認
|
||||
</h3>
|
||||
|
||||
<div class="flex items-center justify-between p-4 rounded-lg {{ $document->accountant_recorded_at ? 'bg-green-50 dark:bg-green-900/30' : 'bg-gray-50 dark:bg-gray-700' }}">
|
||||
<div class="flex items-center">
|
||||
@if ($document->accountant_recorded_at)
|
||||
<svg class="h-5 w-5 text-green-500 mr-3" 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>
|
||||
@else
|
||||
<span class="h-5 w-5 rounded-full border-2 border-gray-300 dark:border-gray-500 mr-3"></span>
|
||||
@endif
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-900 dark:text-gray-100">會計確認入帳</p>
|
||||
@if ($document->accountant_recorded_at)
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ $document->accountantRecordedBy?->name }} - {{ $document->accountant_recorded_at->format('Y-m-d H:i') }}
|
||||
</p>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@if ($document->canAccountantConfirmRecording() && (auth()->user()->hasRole('finance_accountant') || auth()->user()->hasRole('admin')))
|
||||
<form method="POST" action="{{ route('admin.finance.confirm-recording', $document) }}">
|
||||
@csrf
|
||||
<button type="submit" class="inline-flex items-center rounded-md bg-green-600 px-3 py-2 text-sm font-medium text-white hover:bg-green-700">
|
||||
確認入帳
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Approval Actions (審核中才顯示) --}}
|
||||
@if (!$document->isRejected() && !$document->isApprovalComplete())
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100 mb-4">
|
||||
審核操作
|
||||
</h3>
|
||||
|
||||
<div class="flex gap-3">
|
||||
{{-- Approve Button --}}
|
||||
@php
|
||||
$canApprove = false;
|
||||
if (auth()->user()->hasRole('cashier') && $document->canBeApprovedByCashier()) {
|
||||
$isAdmin = auth()->user()->hasRole('admin');
|
||||
$isSecretary = auth()->user()->hasRole('secretary_general');
|
||||
$isChair = auth()->user()->hasRole('finance_chair');
|
||||
$isBoardMember = auth()->user()->hasRole('finance_board_member');
|
||||
|
||||
if ($isAdmin && !$document->isApprovalComplete() && !$document->isRejected()) {
|
||||
$canApprove = true;
|
||||
} elseif (auth()->user()->hasRole('accountant') && $document->canBeApprovedByAccountant()) {
|
||||
} elseif ($isSecretary && $document->canBeApprovedBySecretary(auth()->user())) {
|
||||
$canApprove = true;
|
||||
} elseif (auth()->user()->hasRole('chair') && $document->canBeApprovedByChair()) {
|
||||
} elseif ($isChair && $document->canBeApprovedByChair(auth()->user())) {
|
||||
$canApprove = true;
|
||||
} elseif ($isBoardMember && $document->canBeApprovedByBoard(auth()->user())) {
|
||||
$canApprove = true;
|
||||
}
|
||||
@endphp
|
||||
@@ -307,18 +486,21 @@
|
||||
<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') }}
|
||||
核准
|
||||
@if ($isAdmin)
|
||||
<span class="ml-1 text-xs opacity-75">(管理員)</span>
|
||||
@endif
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
|
||||
{{-- Reject Button (show for cashier, accountant, chair) --}}
|
||||
@if (auth()->user()->hasRole('cashier') || auth()->user()->hasRole('accountant') || auth()->user()->hasRole('chair'))
|
||||
{{-- Reject Button --}}
|
||||
@if (auth()->user()->hasRole('admin') || auth()->user()->hasRole('secretary_general') || auth()->user()->hasRole('finance_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>
|
||||
@@ -331,43 +513,43 @@
|
||||
{{-- 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="fixed inset-0 bg-gray-500 bg-opacity-75 dark:bg-gray-900 dark: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">
|
||||
<div class="inline-block transform overflow-hidden rounded-lg bg-white dark:bg-gray-800 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="bg-white dark:bg-gray-800 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">
|
||||
<div class="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-100 dark:bg-red-900 sm:mx-0 sm:h-10 sm:w-10">
|
||||
<svg class="h-6 w-6 text-red-600 dark:text-red-200" 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 class="text-base font-semibold leading-6 text-gray-900 dark:text-gray-100" id="modal-title">
|
||||
駁回報銷單
|
||||
</h3>
|
||||
<div class="mt-4">
|
||||
<label for="rejection_reason" class="block text-sm font-medium text-gray-700">
|
||||
{{ __('Rejection Reason') }}
|
||||
<label for="rejection_reason" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
駁回原因
|
||||
</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"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-red-500 focus:ring-red-500 sm:text-sm dark:bg-gray-900 dark:text-gray-300"
|
||||
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">
|
||||
<div class="bg-gray-50 dark:bg-gray-700 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 type="button" onclick="document.getElementById('rejectModal').classList.add('hidden')" class="mt-3 inline-flex w-full justify-center rounded-md bg-white dark:bg-gray-700 px-3 py-2 text-sm font-semibold text-gray-900 dark:text-gray-100 shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-600 hover:bg-gray-50 dark:hover:bg-gray-600 sm:mt-0 sm:w-auto">
|
||||
取消
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
270
resources/views/admin/general-ledger/index.blade.php
Normal file
270
resources/views/admin/general-ledger/index.blade.php
Normal file
@@ -0,0 +1,270 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
|
||||
總分類帳
|
||||
</h2>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-12">
|
||||
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8 space-y-6">
|
||||
<!-- Filter Form -->
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<form method="GET" action="{{ route('admin.general-ledger.index') }}" class="space-y-4">
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-4">
|
||||
<!-- Account Selection -->
|
||||
<div class="sm:col-span-2">
|
||||
<label for="account_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
科目
|
||||
</label>
|
||||
<select name="account_id" id="account_id"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:text-gray-100">
|
||||
<option value="">請選擇科目</option>
|
||||
@foreach($accounts as $account)
|
||||
<option value="{{ $account->id }}" @selected(request('account_id') == $account->id)>
|
||||
{{ $account->account_code }} - {{ $account->account_name_zh }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Start Date -->
|
||||
<div>
|
||||
<label for="start_date" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
開始日期
|
||||
</label>
|
||||
<input type="date" name="start_date" id="start_date" value="{{ $startDate }}"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:text-gray-100">
|
||||
</div>
|
||||
|
||||
<!-- End Date -->
|
||||
<div>
|
||||
<label for="end_date" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
結束日期
|
||||
</label>
|
||||
<input type="date" name="end_date" id="end_date" value="{{ $endDate }}"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-700 dark:text-gray-100">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4">
|
||||
<button type="submit" class="inline-flex justify-center rounded-md bg-indigo-600 dark:bg-indigo-500 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-700 dark:hover:bg-indigo-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800">
|
||||
查詢
|
||||
</button>
|
||||
<a href="{{ route('admin.general-ledger.index') }}" class="inline-flex justify-center rounded-md bg-white dark:bg-gray-700 px-4 py-2 text-sm font-semibold text-gray-900 dark:text-gray-100 shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-600 hover:bg-gray-50 dark:hover:bg-gray-600">
|
||||
清除
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if($selectedAccount)
|
||||
<!-- Account Info -->
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||
{{ $selectedAccount->account_code }} - {{ $selectedAccount->account_name_zh }}
|
||||
<span class="text-sm text-gray-500 dark:text-gray-400">
|
||||
({{ $selectedAccount->account_name_en }})
|
||||
</span>
|
||||
</h3>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
類型:
|
||||
@switch($selectedAccount->account_type)
|
||||
@case('asset') 資產 @break
|
||||
@case('liability') 負債 @break
|
||||
@case('net_asset') 淨資產/基金 @break
|
||||
@case('income') 收入 @break
|
||||
@case('expense') 支出 @break
|
||||
@default {{ $selectedAccount->account_type }}
|
||||
@endswitch
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Summary Cards -->
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||
<!-- Opening Balance -->
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg border-l-4 border-gray-400">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">
|
||||
期初餘額
|
||||
</dt>
|
||||
<dd class="mt-1 text-2xl font-semibold {{ $openingBalance >= 0 ? 'text-gray-900 dark:text-gray-100' : 'text-red-600 dark:text-red-400' }}">
|
||||
NT$ {{ number_format($openingBalance, 2) }}
|
||||
</dd>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Debit Total -->
|
||||
<div class="bg-blue-50 dark:bg-blue-900/30 shadow sm:rounded-lg border-l-4 border-blue-400">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<dt class="text-sm font-medium text-blue-800 dark:text-blue-200">
|
||||
借方總計
|
||||
</dt>
|
||||
<dd class="mt-1 text-2xl font-semibold text-blue-900 dark:text-blue-100">
|
||||
NT$ {{ number_format($debitTotal, 2) }}
|
||||
</dd>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Credit Total -->
|
||||
<div class="bg-purple-50 dark:bg-purple-900/30 shadow sm:rounded-lg border-l-4 border-purple-400">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<dt class="text-sm font-medium text-purple-800 dark:text-purple-200">
|
||||
貸方總計
|
||||
</dt>
|
||||
<dd class="mt-1 text-2xl font-semibold text-purple-900 dark:text-purple-100">
|
||||
NT$ {{ number_format($creditTotal, 2) }}
|
||||
</dd>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Closing Balance -->
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg border-l-4 {{ $closingBalance >= 0 ? 'border-green-400' : 'border-red-400' }}">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">
|
||||
期末餘額
|
||||
</dt>
|
||||
<dd class="mt-1 text-2xl font-semibold {{ $closingBalance >= 0 ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400' }}">
|
||||
NT$ {{ number_format($closingBalance, 2) }}
|
||||
</dd>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Entries Table -->
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">
|
||||
分錄明細
|
||||
</h4>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<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">
|
||||
日期
|
||||
</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">
|
||||
憑證
|
||||
</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">
|
||||
說明
|
||||
</th>
|
||||
<th scope="col" class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-blue-600 dark:text-blue-400">
|
||||
借方
|
||||
</th>
|
||||
<th scope="col" class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-purple-600 dark:text-purple-400">
|
||||
貸方
|
||||
</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">
|
||||
餘額
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700 bg-white dark:bg-gray-800">
|
||||
<!-- Opening Balance Row -->
|
||||
<tr class="bg-gray-50 dark:bg-gray-700">
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900 dark:text-gray-100">
|
||||
{{ \Carbon\Carbon::parse($startDate)->format('Y/m/d') }}
|
||||
</td>
|
||||
<td class="px-4 py-3 text-sm text-gray-500 dark:text-gray-400" colspan="2">
|
||||
<em>期初餘額</em>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-right text-gray-500 dark:text-gray-400">
|
||||
-
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-right text-gray-500 dark:text-gray-400">
|
||||
-
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-right font-medium {{ $openingBalance >= 0 ? 'text-gray-900 dark:text-gray-100' : 'text-red-600 dark:text-red-400' }}">
|
||||
{{ number_format($openingBalance, 2) }}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@php $runningBalance = $openingBalance; @endphp
|
||||
@forelse($entries as $entry)
|
||||
@php
|
||||
if (in_array($selectedAccount->account_type, ['asset', 'expense'])) {
|
||||
$runningBalance += ($entry->entry_type === 'debit' ? $entry->amount : -$entry->amount);
|
||||
} else {
|
||||
$runningBalance += ($entry->entry_type === 'credit' ? $entry->amount : -$entry->amount);
|
||||
}
|
||||
@endphp
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900 dark:text-gray-100">
|
||||
{{ $entry->entry_date->format('Y/m/d') }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm">
|
||||
@if($entry->financeDocument)
|
||||
<a href="{{ route('admin.finance.show', $entry->financeDocument) }}" class="text-indigo-600 dark:text-indigo-400 hover:underline">
|
||||
#{{ $entry->finance_document_id }}
|
||||
</a>
|
||||
@else
|
||||
<span class="text-gray-400">-</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-4 py-3 text-sm text-gray-900 dark:text-gray-100 max-w-xs truncate">
|
||||
{{ $entry->description ?? '-' }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-right text-blue-600 dark:text-blue-400">
|
||||
{{ $entry->entry_type === 'debit' ? number_format($entry->amount, 2) : '' }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-right text-purple-600 dark:text-purple-400">
|
||||
{{ $entry->entry_type === 'credit' ? number_format($entry->amount, 2) : '' }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-right font-medium {{ $runningBalance >= 0 ? 'text-gray-900 dark:text-gray-100' : 'text-red-600 dark:text-red-400' }}">
|
||||
{{ number_format($runningBalance, 2) }}
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="6" class="px-4 py-8 text-center text-sm text-gray-500 dark:text-gray-400">
|
||||
此期間無分錄
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
|
||||
<!-- Totals Row -->
|
||||
@if($entries && $entries->count() > 0)
|
||||
<tr class="bg-gray-100 dark:bg-gray-700 font-semibold">
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900 dark:text-gray-100" colspan="3">
|
||||
本期合計
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-right text-blue-700 dark:text-blue-300">
|
||||
{{ number_format($debitTotal, 2) }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-right text-purple-700 dark:text-purple-300">
|
||||
{{ number_format($creditTotal, 2) }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-right {{ $closingBalance >= 0 ? 'text-green-700 dark:text-green-300' : 'text-red-700 dark:text-red-300' }}">
|
||||
{{ number_format($closingBalance, 2) }}
|
||||
</td>
|
||||
</tr>
|
||||
@endif
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<!-- No Account Selected -->
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
||||
<div class="px-4 py-12 text-center">
|
||||
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<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 dark:text-gray-100">
|
||||
請選擇科目
|
||||
</h3>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
從上方下拉選單選擇科目以查看分錄明細
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
272
resources/views/admin/incomes/create.blade.php
Normal file
272
resources/views/admin/incomes/create.blade.php
Normal file
@@ -0,0 +1,272 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
|
||||
新增收入記錄
|
||||
</h2>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-12">
|
||||
<div class="mx-auto max-w-3xl sm:px-6 lg:px-8">
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<form method="POST" action="{{ route('admin.incomes.store') }}" enctype="multipart/form-data">
|
||||
@csrf
|
||||
|
||||
<div class="space-y-6">
|
||||
{{-- 基本資訊 --}}
|
||||
<div>
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100">基本資訊</h3>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">輸入收入的基本資料。</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2">
|
||||
{{-- 標題 --}}
|
||||
<div class="sm:col-span-2">
|
||||
<label for="title" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
標題 <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 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-900 dark:text-gray-300"
|
||||
placeholder="例如:2024年度會費收入">
|
||||
@error('title')
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
{{-- 收入日期 --}}
|
||||
<div>
|
||||
<label for="income_date" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
收入日期 <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<input type="date" name="income_date" id="income_date" value="{{ old('income_date', date('Y-m-d')) }}" required
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-900 dark:text-gray-300">
|
||||
@error('income_date')
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
{{-- 金額 --}}
|
||||
<div>
|
||||
<label for="amount" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
金額 (NT$) <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<input type="number" name="amount" id="amount" value="{{ old('amount') }}" required
|
||||
step="0.01" min="0.01"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-900 dark:text-gray-300"
|
||||
placeholder="0.00">
|
||||
@error('amount')
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
{{-- 收入類型 --}}
|
||||
<div>
|
||||
<label for="income_type" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
收入類型 <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<select name="income_type" id="income_type" required
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-900 dark:text-gray-300">
|
||||
<option value="">請選擇收入類型</option>
|
||||
<option value="membership_fee" @selected(old('income_type') == 'membership_fee')>會費</option>
|
||||
<option value="entrance_fee" @selected(old('income_type') == 'entrance_fee')>入會費</option>
|
||||
<option value="donation" @selected(old('income_type') == 'donation')>捐款</option>
|
||||
<option value="activity" @selected(old('income_type') == 'activity')>活動收入</option>
|
||||
<option value="grant" @selected(old('income_type') == 'grant')>補助款</option>
|
||||
<option value="interest" @selected(old('income_type') == 'interest')>利息收入</option>
|
||||
<option value="other" @selected(old('income_type') == 'other')>其他</option>
|
||||
</select>
|
||||
@error('income_type')
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
{{-- 會計科目 --}}
|
||||
<div>
|
||||
<label for="chart_of_account_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
會計科目 <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 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-900 dark:text-gray-300">
|
||||
<option value="">請選擇會計科目</option>
|
||||
@foreach ($chartOfAccounts as $account)
|
||||
<option value="{{ $account->id }}" @selected(old('chart_of_account_id') == $account->id)>
|
||||
{{ $account->account_code }} - {{ $account->account_name_zh }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@error('chart_of_account_id')
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="border-gray-200 dark:border-gray-700">
|
||||
|
||||
{{-- 付款資訊 --}}
|
||||
<div>
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100">付款資訊</h3>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">記錄付款方式與相關資訊。</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2">
|
||||
{{-- 付款方式 --}}
|
||||
<div>
|
||||
<label for="payment_method" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
付款方式 <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 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-900 dark:text-gray-300">
|
||||
<option value="">請選擇付款方式</option>
|
||||
<option value="cash" @selected(old('payment_method') == 'cash')>現金</option>
|
||||
<option value="bank_transfer" @selected(old('payment_method') == 'bank_transfer')>銀行轉帳</option>
|
||||
<option value="check" @selected(old('payment_method') == 'check')>支票</option>
|
||||
</select>
|
||||
@error('payment_method')
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
{{-- 銀行帳號 --}}
|
||||
<div>
|
||||
<label for="bank_account" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
銀行帳號
|
||||
</label>
|
||||
<input type="text" name="bank_account" id="bank_account" value="{{ old('bank_account') }}"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-900 dark:text-gray-300"
|
||||
placeholder="例如:012-12345678">
|
||||
@error('bank_account')
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
{{-- 付款人姓名 --}}
|
||||
<div>
|
||||
<label for="payer_name" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
付款人姓名
|
||||
</label>
|
||||
<input type="text" name="payer_name" id="payer_name" value="{{ old('payer_name') }}"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-900 dark:text-gray-300"
|
||||
placeholder="付款人姓名">
|
||||
@error('payer_name')
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
{{-- 關聯會員 --}}
|
||||
<div>
|
||||
<label for="member_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
關聯會員
|
||||
</label>
|
||||
<select name="member_id" id="member_id"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-900 dark:text-gray-300">
|
||||
<option value="">不關聯會員</option>
|
||||
@foreach ($members as $member)
|
||||
<option value="{{ $member->id }}" @selected(old('member_id', $selectedMember?->id) == $member->id)>
|
||||
{{ $member->full_name }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@error('member_id')
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
{{-- 收據編號 --}}
|
||||
<div>
|
||||
<label for="receipt_number" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
收據編號
|
||||
</label>
|
||||
<input type="text" name="receipt_number" id="receipt_number" value="{{ old('receipt_number') }}"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-900 dark:text-gray-300"
|
||||
placeholder="收據編號">
|
||||
@error('receipt_number')
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
{{-- 交易參考號 --}}
|
||||
<div>
|
||||
<label for="transaction_reference" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
交易參考號
|
||||
</label>
|
||||
<input type="text" name="transaction_reference" id="transaction_reference" value="{{ old('transaction_reference') }}"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-900 dark:text-gray-300"
|
||||
placeholder="銀行交易參考號">
|
||||
@error('transaction_reference')
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="border-gray-200 dark:border-gray-700">
|
||||
|
||||
{{-- 說明與備註 --}}
|
||||
<div>
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100">說明與備註</h3>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-6">
|
||||
{{-- 說明 --}}
|
||||
<div>
|
||||
<label for="description" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
說明
|
||||
</label>
|
||||
<textarea name="description" id="description" rows="3"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-900 dark:text-gray-300"
|
||||
placeholder="收入來源說明">{{ old('description') }}</textarea>
|
||||
@error('description')
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
{{-- 備註 --}}
|
||||
<div>
|
||||
<label for="notes" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
內部備註
|
||||
</label>
|
||||
<textarea name="notes" id="notes" rows="2"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-900 dark:text-gray-300"
|
||||
placeholder="內部備註(僅管理員可見)">{{ old('notes') }}</textarea>
|
||||
@error('notes')
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
{{-- 附件 --}}
|
||||
<div>
|
||||
<label for="attachment" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
附件
|
||||
</label>
|
||||
<input type="file" name="attachment" id="attachment"
|
||||
class="mt-1 block w-full text-sm text-gray-500 dark:text-gray-400
|
||||
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
|
||||
dark:file:bg-indigo-900 dark:file:text-indigo-300
|
||||
hover:file:bg-indigo-100 dark:hover:file:bg-indigo-800">
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">支援 PDF、圖片等格式,最大 10MB</p>
|
||||
@error('attachment')
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 提交按鈕 --}}
|
||||
<div class="flex justify-end space-x-3 pt-6">
|
||||
<a href="{{ route('admin.incomes.index') }}"
|
||||
class="inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600">
|
||||
取消
|
||||
</a>
|
||||
<button type="submit"
|
||||
class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 dark:bg-indigo-500 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 dark:hover:bg-indigo-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
|
||||
建立收入記錄
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
234
resources/views/admin/incomes/index.blade.php
Normal file
234
resources/views/admin/incomes/index.blade.php
Normal file
@@ -0,0 +1,234 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
|
||||
收入管理
|
||||
</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 dark:bg-green-900/30 p-4">
|
||||
<p class="text-sm font-medium text-green-800 dark:text-green-200">{{ session('status') }}</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if (session('error'))
|
||||
<div class="rounded-md bg-red-50 dark:bg-red-900/30 p-4">
|
||||
<p class="text-sm font-medium text-red-800 dark:text-red-200">{{ session('error') }}</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Statistics Cards --}}
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow rounded-lg">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">待確認收入</dt>
|
||||
<dd class="mt-1 text-2xl font-semibold text-yellow-600 dark:text-yellow-400">
|
||||
{{ $statistics['pending_count'] }} 筆
|
||||
</dd>
|
||||
<dd class="text-sm text-gray-500 dark:text-gray-400">
|
||||
NT$ {{ number_format($statistics['pending_amount'], 2) }}
|
||||
</dd>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow rounded-lg">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">已確認收入</dt>
|
||||
<dd class="mt-1 text-2xl font-semibold text-green-600 dark:text-green-400">
|
||||
{{ $statistics['confirmed_count'] }} 筆
|
||||
</dd>
|
||||
<dd class="text-sm text-gray-500 dark:text-gray-400">
|
||||
NT$ {{ number_format($statistics['confirmed_amount'], 2) }}
|
||||
</dd>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center">
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">收入記錄列表</h3>
|
||||
<div class="flex space-x-2">
|
||||
<a href="{{ route('admin.incomes.statistics') }}" class="inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600">
|
||||
<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="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>
|
||||
@can('record_income')
|
||||
<a href="{{ route('admin.incomes.create') }}" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 dark:bg-indigo-500 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 dark:hover:bg-indigo-600 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 4v16m8-8H4" />
|
||||
</svg>
|
||||
新增收入
|
||||
</a>
|
||||
@endcan
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Filters --}}
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<form method="GET" action="{{ route('admin.incomes.index') }}" class="grid grid-cols-1 gap-4 sm:grid-cols-5">
|
||||
{{-- 狀態篩選 --}}
|
||||
<div>
|
||||
<label for="status" class="block text-sm font-medium text-gray-700 dark:text-gray-300">狀態</label>
|
||||
<select name="status" id="status" class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-900 dark:text-gray-300">
|
||||
<option value="">全部狀態</option>
|
||||
<option value="pending" @selected(request('status') == 'pending')>待確認</option>
|
||||
<option value="confirmed" @selected(request('status') == 'confirmed')>已確認</option>
|
||||
<option value="cancelled" @selected(request('status') == 'cancelled')>已取消</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{{-- 收入類型篩選 --}}
|
||||
<div>
|
||||
<label for="income_type" class="block text-sm font-medium text-gray-700 dark:text-gray-300">收入類型</label>
|
||||
<select name="income_type" id="income_type" class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-900 dark:text-gray-300">
|
||||
<option value="">全部類型</option>
|
||||
<option value="membership_fee" @selected(request('income_type') == 'membership_fee')>會費</option>
|
||||
<option value="entrance_fee" @selected(request('income_type') == 'entrance_fee')>入會費</option>
|
||||
<option value="donation" @selected(request('income_type') == 'donation')>捐款</option>
|
||||
<option value="activity" @selected(request('income_type') == 'activity')>活動收入</option>
|
||||
<option value="grant" @selected(request('income_type') == 'grant')>補助款</option>
|
||||
<option value="interest" @selected(request('income_type') == 'interest')>利息收入</option>
|
||||
<option value="other" @selected(request('income_type') == 'other')>其他</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{{-- 付款方式篩選 --}}
|
||||
<div>
|
||||
<label for="payment_method" class="block text-sm font-medium text-gray-700 dark:text-gray-300">付款方式</label>
|
||||
<select name="payment_method" id="payment_method" class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-900 dark:text-gray-300">
|
||||
<option value="">全部方式</option>
|
||||
<option value="cash" @selected(request('payment_method') == 'cash')>現金</option>
|
||||
<option value="bank_transfer" @selected(request('payment_method') == 'bank_transfer')>銀行轉帳</option>
|
||||
<option value="check" @selected(request('payment_method') == 'check')>支票</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{{-- 日期範圍 --}}
|
||||
<div>
|
||||
<label for="date_from" class="block text-sm font-medium text-gray-700 dark:text-gray-300">起始日期</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 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-900 dark:text-gray-300">
|
||||
</div>
|
||||
|
||||
{{-- 篩選按鈕 --}}
|
||||
<div class="flex items-end space-x-2">
|
||||
<button type="submit" class="inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600">
|
||||
篩選
|
||||
</button>
|
||||
<a href="{{ route('admin.incomes.index') }}" class="inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600">
|
||||
重設
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Table --}}
|
||||
<div class="bg-white dark:bg-gray-800 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 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">
|
||||
收入編號
|
||||
</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">
|
||||
標題
|
||||
</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">
|
||||
收入類型
|
||||
</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">
|
||||
金額
|
||||
</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">
|
||||
付款方式
|
||||
</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">
|
||||
狀態
|
||||
</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">
|
||||
日期
|
||||
</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">
|
||||
<span class="sr-only">操作</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700 bg-white dark:bg-gray-800">
|
||||
@forelse ($incomes as $income)
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900 dark:text-gray-100">
|
||||
<div class="font-mono text-xs">{{ $income->income_number }}</div>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900 dark:text-gray-100">
|
||||
<div class="font-medium">{{ $income->title }}</div>
|
||||
@if ($income->member)
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400">
|
||||
會員:{{ $income->member->full_name }}
|
||||
</div>
|
||||
@elseif ($income->payer_name)
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400">
|
||||
付款人:{{ $income->payer_name }}
|
||||
</div>
|
||||
@endif
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900 dark:text-gray-100">
|
||||
{{ $income->getIncomeTypeText() }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900 dark:text-gray-100">
|
||||
<span class="font-semibold">NT$ {{ number_format($income->amount, 2) }}</span>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900 dark:text-gray-100">
|
||||
{{ $income->getPaymentMethodText() }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm">
|
||||
@if ($income->isCancelled())
|
||||
<span class="inline-flex rounded-full bg-red-100 dark:bg-red-900 px-2 text-xs font-semibold leading-5 text-red-800 dark:text-red-200">
|
||||
{{ $income->getStatusText() }}
|
||||
</span>
|
||||
@elseif ($income->isConfirmed())
|
||||
<span class="inline-flex rounded-full bg-green-100 dark:bg-green-900 px-2 text-xs font-semibold leading-5 text-green-800 dark:text-green-200">
|
||||
{{ $income->getStatusText() }}
|
||||
</span>
|
||||
@else
|
||||
<span class="inline-flex rounded-full bg-yellow-100 dark:bg-yellow-900 px-2 text-xs font-semibold leading-5 text-yellow-800 dark:text-yellow-200">
|
||||
{{ $income->getStatusText() }}
|
||||
</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900 dark:text-gray-100">
|
||||
{{ $income->income_date->format('Y-m-d') }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-right text-sm">
|
||||
<a href="{{ route('admin.incomes.show', $income) }}" class="text-indigo-600 dark:text-indigo-400 hover:text-indigo-900 dark:hover:text-indigo-300">
|
||||
檢視
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="8" 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" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<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" />
|
||||
</svg>
|
||||
<p class="mt-2 text-sm font-semibold">尚無收入記錄</p>
|
||||
<p class="mt-1 text-sm">點選「新增收入」開始建立。</p>
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
{{ $incomes->links() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
329
resources/views/admin/incomes/show.blade.php
Normal file
329
resources/views/admin/incomes/show.blade.php
Normal file
@@ -0,0 +1,329 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
|
||||
收入詳情 - {{ $income->income_number }}
|
||||
</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 dark:bg-green-900/30 p-4">
|
||||
<p class="text-sm font-medium text-green-800 dark:text-green-200">{{ session('status') }}</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if (session('error'))
|
||||
<div class="rounded-md bg-red-50 dark:bg-red-900/30 p-4">
|
||||
<p class="text-sm font-medium text-red-800 dark:text-red-200">{{ session('error') }}</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- 狀態卡片 --}}
|
||||
<div 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">
|
||||
<div>
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100">
|
||||
{{ $income->title }}
|
||||
</h3>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
收入編號:{{ $income->income_number }}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
@if ($income->isCancelled())
|
||||
<span class="inline-flex items-center rounded-full bg-red-100 dark:bg-red-900 px-3 py-1 text-sm font-medium text-red-800 dark:text-red-200">
|
||||
{{ $income->getStatusText() }}
|
||||
</span>
|
||||
@elseif ($income->isConfirmed())
|
||||
<span class="inline-flex items-center rounded-full bg-green-100 dark:bg-green-900 px-3 py-1 text-sm font-medium text-green-800 dark:text-green-200">
|
||||
{{ $income->getStatusText() }}
|
||||
</span>
|
||||
@else
|
||||
<span class="inline-flex items-center rounded-full bg-yellow-100 dark:bg-yellow-900 px-3 py-1 text-sm font-medium text-yellow-800 dark:text-yellow-200">
|
||||
{{ $income->getStatusText() }}
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 操作按鈕 --}}
|
||||
<div class="mt-6 flex space-x-3">
|
||||
@if ($income->canBeConfirmed())
|
||||
@can('confirm_income')
|
||||
<form method="POST" action="{{ route('admin.incomes.confirm', $income) }}" class="inline">
|
||||
@csrf
|
||||
<button type="submit" onclick="return confirm('確定要確認此收入嗎?將會自動產生出納日記帳和會計分錄。')"
|
||||
class="inline-flex items-center rounded-md border border-transparent bg-green-600 dark:bg-green-500 px-4 py-2 text-sm font-medium text-white hover:bg-green-700 dark:hover:bg-green-600 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>
|
||||
確認收入
|
||||
</button>
|
||||
</form>
|
||||
@endcan
|
||||
@endif
|
||||
|
||||
@if ($income->canBeCancelled())
|
||||
@can('cancel_income')
|
||||
<form method="POST" action="{{ route('admin.incomes.cancel', $income) }}" class="inline">
|
||||
@csrf
|
||||
<button type="submit" onclick="return confirm('確定要取消此收入嗎?')"
|
||||
class="inline-flex items-center rounded-md border border-red-300 dark:border-red-600 bg-white dark:bg-gray-700 px-4 py-2 text-sm font-medium text-red-700 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/30 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>
|
||||
取消收入
|
||||
</button>
|
||||
</form>
|
||||
@endcan
|
||||
@endif
|
||||
|
||||
@if ($income->attachment_path)
|
||||
<a href="{{ route('admin.incomes.download', $income) }}"
|
||||
class="inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600">
|
||||
<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="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
|
||||
</svg>
|
||||
下載附件
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 金額與日期資訊 --}}
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<h4 class="text-base font-medium text-gray-900 dark:text-gray-100 mb-4">金額與日期</h4>
|
||||
<dl class="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||
<div class="bg-gray-50 dark:bg-gray-700/50 rounded-lg p-4">
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">金額</dt>
|
||||
<dd class="mt-1 text-2xl font-semibold text-gray-900 dark:text-gray-100">
|
||||
NT$ {{ number_format($income->amount, 2) }}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="bg-gray-50 dark:bg-gray-700/50 rounded-lg p-4">
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">收入日期</dt>
|
||||
<dd class="mt-1 text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||
{{ $income->income_date->format('Y-m-d') }}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="bg-gray-50 dark:bg-gray-700/50 rounded-lg p-4">
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">收入類型</dt>
|
||||
<dd class="mt-1 text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||
{{ $income->getIncomeTypeText() }}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 會計資訊 --}}
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<h4 class="text-base font-medium text-gray-900 dark:text-gray-100 mb-4">會計資訊</h4>
|
||||
<dl class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">會計科目</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
|
||||
@if ($income->chartOfAccount)
|
||||
{{ $income->chartOfAccount->account_code }} - {{ $income->chartOfAccount->account_name_zh }}
|
||||
@else
|
||||
<span class="text-gray-400">未指定</span>
|
||||
@endif
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">付款方式</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
|
||||
{{ $income->getPaymentMethodText() }}
|
||||
</dd>
|
||||
</div>
|
||||
@if ($income->bank_account)
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">銀行帳號</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
|
||||
{{ $income->bank_account }}
|
||||
</dd>
|
||||
</div>
|
||||
@endif
|
||||
@if ($income->receipt_number)
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">收據編號</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
|
||||
{{ $income->receipt_number }}
|
||||
</dd>
|
||||
</div>
|
||||
@endif
|
||||
@if ($income->transaction_reference)
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">交易參考號</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
|
||||
{{ $income->transaction_reference }}
|
||||
</dd>
|
||||
</div>
|
||||
@endif
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 付款人資訊 --}}
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<h4 class="text-base font-medium text-gray-900 dark:text-gray-100 mb-4">付款人資訊</h4>
|
||||
<dl class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
@if ($income->member)
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">關聯會員</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
|
||||
<a href="{{ route('admin.members.show', $income->member) }}" class="text-indigo-600 dark:text-indigo-400 hover:underline">
|
||||
{{ $income->member->full_name }}
|
||||
</a>
|
||||
</dd>
|
||||
</div>
|
||||
@endif
|
||||
@if ($income->payer_name)
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">付款人姓名</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
|
||||
{{ $income->payer_name }}
|
||||
</dd>
|
||||
</div>
|
||||
@endif
|
||||
@if (!$income->member && !$income->payer_name)
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">付款人</dt>
|
||||
<dd class="mt-1 text-sm text-gray-400">未記錄</dd>
|
||||
</div>
|
||||
@endif
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 說明與備註 --}}
|
||||
@if ($income->description || $income->notes)
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<h4 class="text-base font-medium text-gray-900 dark:text-gray-100 mb-4">說明與備註</h4>
|
||||
<dl class="space-y-4">
|
||||
@if ($income->description)
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">說明</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100 whitespace-pre-wrap">{{ $income->description }}</dd>
|
||||
</div>
|
||||
@endif
|
||||
@if ($income->notes)
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">內部備註</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100 whitespace-pre-wrap">{{ $income->notes }}</dd>
|
||||
</div>
|
||||
@endif
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- 處理記錄 --}}
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<h4 class="text-base font-medium text-gray-900 dark:text-gray-100 mb-4">處理記錄</h4>
|
||||
<dl class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">出納記錄人</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
|
||||
{{ $income->recordedByCashier?->name ?? '不適用' }}
|
||||
</dd>
|
||||
@if ($income->recorded_at)
|
||||
<dd class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ $income->recorded_at->format('Y-m-d H:i') }}
|
||||
</dd>
|
||||
@endif
|
||||
</div>
|
||||
@if ($income->isConfirmed())
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">會計確認人</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
|
||||
{{ $income->confirmedByAccountant?->name ?? '不適用' }}
|
||||
</dd>
|
||||
@if ($income->confirmed_at)
|
||||
<dd class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ $income->confirmed_at->format('Y-m-d H:i') }}
|
||||
</dd>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 關聯記錄 --}}
|
||||
@if ($income->isConfirmed() && ($income->cashierLedgerEntry || $income->accountingEntries->count() > 0))
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<h4 class="text-base font-medium text-gray-900 dark:text-gray-100 mb-4">關聯記錄</h4>
|
||||
|
||||
@if ($income->cashierLedgerEntry)
|
||||
<div class="mb-4">
|
||||
<h5 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">出納日記帳</h5>
|
||||
<div class="bg-gray-50 dark:bg-gray-700/50 rounded-lg p-3">
|
||||
<a href="{{ route('admin.cashier-ledger.show', $income->cashierLedgerEntry) }}" class="text-indigo-600 dark:text-indigo-400 hover:underline">
|
||||
查看出納日記帳記錄
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if ($income->accountingEntries->count() > 0)
|
||||
<div>
|
||||
<h5 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">會計分錄</h5>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<thead class="bg-gray-50 dark:bg-gray-700">
|
||||
<tr>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-300">會計科目</th>
|
||||
<th class="px-4 py-2 text-right text-xs font-medium text-gray-500 dark:text-gray-300">借方</th>
|
||||
<th class="px-4 py-2 text-right text-xs font-medium text-gray-500 dark:text-gray-300">貸方</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
@foreach ($income->accountingEntries as $entry)
|
||||
<tr>
|
||||
<td class="px-4 py-2 text-sm text-gray-900 dark:text-gray-100">
|
||||
{{ $entry->chartOfAccount->account_code }} - {{ $entry->chartOfAccount->account_name_zh }}
|
||||
</td>
|
||||
<td class="px-4 py-2 text-sm text-right text-gray-900 dark:text-gray-100">
|
||||
@if ($entry->entry_type === 'debit')
|
||||
NT$ {{ number_format($entry->amount, 2) }}
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-4 py-2 text-sm text-right text-gray-900 dark:text-gray-100">
|
||||
@if ($entry->entry_type === 'credit')
|
||||
NT$ {{ number_format($entry->amount, 2) }}
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- 返回按鈕 --}}
|
||||
<div class="flex justify-start">
|
||||
<a href="{{ route('admin.incomes.index') }}"
|
||||
class="inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600">
|
||||
<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="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
||||
</svg>
|
||||
返回列表
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
216
resources/views/admin/incomes/statistics.blade.php
Normal file
216
resources/views/admin/incomes/statistics.blade.php
Normal file
@@ -0,0 +1,216 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
|
||||
收入統計報表
|
||||
</h2>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-12">
|
||||
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8 space-y-6">
|
||||
{{-- 篩選器 --}}
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<form method="GET" action="{{ route('admin.incomes.statistics') }}" class="flex items-end space-x-4">
|
||||
<div>
|
||||
<label for="year" class="block text-sm font-medium text-gray-700 dark:text-gray-300">年度</label>
|
||||
<select name="year" id="year" class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-900 dark:text-gray-300">
|
||||
@for ($y = date('Y'); $y >= date('Y') - 5; $y--)
|
||||
<option value="{{ $y }}" @selected($year == $y)>{{ $y }}</option>
|
||||
@endfor
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="month" class="block text-sm font-medium text-gray-700 dark:text-gray-300">月份</label>
|
||||
<select name="month" id="month" class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-900 dark:text-gray-300">
|
||||
<option value="">全年</option>
|
||||
@for ($m = 1; $m <= 12; $m++)
|
||||
<option value="{{ $m }}" @selected($month == $m)>{{ $m }}月</option>
|
||||
@endfor
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex space-x-2">
|
||||
<button type="submit" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 dark:bg-indigo-500 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 dark:hover:bg-indigo-600">
|
||||
查詢
|
||||
</button>
|
||||
<a href="{{ route('admin.incomes.export', ['date_from' => $year . '-01-01', 'date_to' => $year . '-12-31']) }}"
|
||||
class="inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600">
|
||||
<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="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
|
||||
</svg>
|
||||
匯出 CSV
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 總計 --}}
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">
|
||||
{{ $year }}年{{ $month ? $month . '月' : '' }}收入總計
|
||||
</h3>
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div class="bg-indigo-50 dark:bg-indigo-900/30 rounded-lg p-4">
|
||||
<dt class="text-sm font-medium text-indigo-600 dark:text-indigo-400">總收入金額</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold text-indigo-900 dark:text-indigo-100">
|
||||
NT$ {{ number_format($total['amount'], 2) }}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="bg-green-50 dark:bg-green-900/30 rounded-lg p-4">
|
||||
<dt class="text-sm font-medium text-green-600 dark:text-green-400">收入筆數</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold text-green-900 dark:text-green-100">
|
||||
{{ number_format($total['count']) }} 筆
|
||||
</dd>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
||||
{{-- 依收入類型統計 --}}
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">依收入類型統計</h3>
|
||||
@if ($byType->count() > 0)
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<thead class="bg-gray-50 dark:bg-gray-700">
|
||||
<tr>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-300">類型</th>
|
||||
<th class="px-4 py-2 text-right text-xs font-medium text-gray-500 dark:text-gray-300">筆數</th>
|
||||
<th class="px-4 py-2 text-right text-xs font-medium text-gray-500 dark:text-gray-300">金額</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
@foreach ($byType as $item)
|
||||
<tr>
|
||||
<td class="px-4 py-2 text-sm text-gray-900 dark:text-gray-100">
|
||||
{{ \App\Models\Income::getIncomeTypeLabel($item->income_type) }}
|
||||
</td>
|
||||
<td class="px-4 py-2 text-sm text-right text-gray-900 dark:text-gray-100">
|
||||
{{ number_format($item->count) }}
|
||||
</td>
|
||||
<td class="px-4 py-2 text-sm text-right text-gray-900 dark:text-gray-100">
|
||||
NT$ {{ number_format($item->total_amount, 2) }}
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@else
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">此期間無收入資料</p>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 依會計科目統計 --}}
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">依會計科目統計</h3>
|
||||
@if ($byAccount->count() > 0)
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<thead class="bg-gray-50 dark:bg-gray-700">
|
||||
<tr>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-300">科目</th>
|
||||
<th class="px-4 py-2 text-right text-xs font-medium text-gray-500 dark:text-gray-300">筆數</th>
|
||||
<th class="px-4 py-2 text-right text-xs font-medium text-gray-500 dark:text-gray-300">金額</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
@foreach ($byAccount as $item)
|
||||
<tr>
|
||||
<td class="px-4 py-2 text-sm text-gray-900 dark:text-gray-100">
|
||||
@if ($item->chartOfAccount)
|
||||
{{ $item->chartOfAccount->account_code }} - {{ $item->chartOfAccount->account_name_zh }}
|
||||
@else
|
||||
未分類
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-4 py-2 text-sm text-right text-gray-900 dark:text-gray-100">
|
||||
{{ number_format($item->count) }}
|
||||
</td>
|
||||
<td class="px-4 py-2 text-sm text-right text-gray-900 dark:text-gray-100">
|
||||
NT$ {{ number_format($item->total_amount, 2) }}
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@else
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">此期間無收入資料</p>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 依月份統計 --}}
|
||||
@if (!$month)
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">{{ $year }}年月度收入趨勢</h3>
|
||||
@if ($byMonth->count() > 0)
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<thead class="bg-gray-50 dark:bg-gray-700">
|
||||
<tr>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-300">月份</th>
|
||||
<th class="px-4 py-2 text-right text-xs font-medium text-gray-500 dark:text-gray-300">筆數</th>
|
||||
<th class="px-4 py-2 text-right text-xs font-medium text-gray-500 dark:text-gray-300">金額</th>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-300">圖示</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
@php
|
||||
$maxAmount = $byMonth->max('total_amount') ?: 1;
|
||||
@endphp
|
||||
@for ($m = 1; $m <= 12; $m++)
|
||||
@php
|
||||
$monthData = $byMonth->firstWhere('month', $m);
|
||||
$amount = $monthData->total_amount ?? 0;
|
||||
$count = $monthData->count ?? 0;
|
||||
$percentage = ($amount / $maxAmount) * 100;
|
||||
@endphp
|
||||
<tr>
|
||||
<td class="px-4 py-2 text-sm text-gray-900 dark:text-gray-100">
|
||||
{{ $m }}月
|
||||
</td>
|
||||
<td class="px-4 py-2 text-sm text-right text-gray-900 dark:text-gray-100">
|
||||
{{ number_format($count) }}
|
||||
</td>
|
||||
<td class="px-4 py-2 text-sm text-right text-gray-900 dark:text-gray-100">
|
||||
NT$ {{ number_format($amount, 2) }}
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
<div class="w-full bg-gray-200 dark:bg-gray-600 rounded-full h-2">
|
||||
<div class="bg-indigo-600 dark:bg-indigo-500 h-2 rounded-full" style="width: {{ $percentage }}%"></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@endfor
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@else
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">此年度無收入資料</p>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- 返回按鈕 --}}
|
||||
<div class="flex justify-start">
|
||||
<a href="{{ route('admin.incomes.index') }}"
|
||||
class="inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600">
|
||||
<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="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
||||
</svg>
|
||||
返回收入列表
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
@@ -1,59 +1,57 @@
|
||||
<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="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.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>
|
||||
名稱 <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') }}">
|
||||
placeholder="例如:錯誤、功能、增強">
|
||||
@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>
|
||||
顏色 <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 class="mt-1 flex items-center gap-2">
|
||||
<input type="color" name="color" id="color" value="{{ old('color', '#3b82f6') }}" required
|
||||
class="h-10 w-20 rounded-md border-gray-300 dark:border-gray-600 p-1">
|
||||
<input type="text" name="color_text" id="color_text" value="{{ old('color', '#3b82f6') }}" required maxlength="7"
|
||||
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"
|
||||
placeholder="#RRGGBB">
|
||||
</div>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">{{ __('Choose a color for this label') }}</p>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">標籤的十六進位色碼</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 for="text_color" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
文字顏色
|
||||
</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
|
||||
<select name="text_color" id="text_color"
|
||||
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="#ffffff" @selected(old('text_color') == '#ffffff')>White</option>
|
||||
<option value="#000000" @selected(old('text_color') == '#000000')>Black</option>
|
||||
</select>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">對比的文字顏色</p>
|
||||
@error('text_color')<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') }}
|
||||
<div class="flex items-center justify-end pt-6">
|
||||
<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">
|
||||
新增標籤
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -62,19 +60,17 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Sync color picker and text input
|
||||
const colorPicker = document.getElementById('color');
|
||||
const colorText = document.getElementById('color-text');
|
||||
const colorInput = document.getElementById('color');
|
||||
const colorTextInput = document.getElementById('color_text');
|
||||
|
||||
colorPicker.addEventListener('input', (e) => {
|
||||
colorText.value = e.target.value.toUpperCase();
|
||||
colorInput.addEventListener('input', (e) => {
|
||||
colorTextInput.value = e.target.value;
|
||||
});
|
||||
|
||||
colorText.addEventListener('input', (e) => {
|
||||
const value = e.target.value;
|
||||
if (/^#[0-9A-Fa-f]{6}$/.test(value)) {
|
||||
colorPicker.value = value;
|
||||
colorTextInput.addEventListener('input', (e) => {
|
||||
if (e.target.value.match(/^#[0-9A-Fa-f]{6}$/)) {
|
||||
colorInput.value = e.target.value;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</x-app-layout>
|
||||
</x-app-layout>
|
||||
@@ -1,12 +1,12 @@
|
||||
<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="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.issue-labels.update', $issueLabel) }}" class="space-y-6">
|
||||
@csrf
|
||||
@@ -14,43 +14,45 @@
|
||||
|
||||
<div>
|
||||
<label for="name" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ __('Name') }} <span class="text-red-500">*</span>
|
||||
名稱 <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">
|
||||
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="例如:錯誤、功能、增強">
|
||||
@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>
|
||||
顏色 <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<div class="mt-1 flex gap-2">
|
||||
<div class="mt-1 flex items-center 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">
|
||||
class="h-10 w-20 rounded-md border-gray-300 dark:border-gray-600 p-1">
|
||||
<input type="text" name="color_text" id="color_text" value="{{ old('color', $issueLabel->color) }}" required maxlength="7"
|
||||
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"
|
||||
placeholder="#RRGGBB">
|
||||
</div>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">標籤的十六進位色碼</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 for="text_color" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
文字顏色
|
||||
</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
|
||||
<select name="text_color" id="text_color"
|
||||
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="#ffffff" @selected(old('text_color', $issueLabel->text_color) == '#ffffff')>White</option>
|
||||
<option value="#000000" @selected(old('text_color', $issueLabel->text_color) == '#000000')>Black</option>
|
||||
</select>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">對比的文字顏色</p>
|
||||
@error('text_color')<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') }}
|
||||
<div class="flex items-center justify-end pt-6">
|
||||
<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">
|
||||
更新標籤
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -59,19 +61,17 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Sync color picker and text input
|
||||
const colorPicker = document.getElementById('color');
|
||||
const colorText = document.getElementById('color-text');
|
||||
const colorInput = document.getElementById('color');
|
||||
const colorTextInput = document.getElementById('color_text');
|
||||
|
||||
colorPicker.addEventListener('input', (e) => {
|
||||
colorText.value = e.target.value.toUpperCase();
|
||||
colorInput.addEventListener('input', (e) => {
|
||||
colorTextInput.value = e.target.value;
|
||||
});
|
||||
|
||||
colorText.addEventListener('input', (e) => {
|
||||
const value = e.target.value;
|
||||
if (/^#[0-9A-Fa-f]{6}$/.test(value)) {
|
||||
colorPicker.value = value;
|
||||
colorTextInput.addEventListener('input', (e) => {
|
||||
if (e.target.value.match(/^#[0-9A-Fa-f]{6}$/)) {
|
||||
colorInput.value = e.target.value;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</x-app-layout>
|
||||
</x-app-layout>
|
||||
@@ -1,14 +1,14 @@
|
||||
<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">
|
||||
<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">
|
||||
<p class="text-sm text-green-800 dark:text-green-200">{{ session('status') }}</p>
|
||||
</div>
|
||||
@endif
|
||||
@@ -16,13 +16,13 @@
|
||||
<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>
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">標籤</h3>
|
||||
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">管理用於分類任務的標籤</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">
|
||||
<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 focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:focus:ring-indigo-600 focus:ring-offset-2 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 Label') }}
|
||||
新增標籤
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -31,42 +31,42 @@
|
||||
<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>
|
||||
<th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">名稱</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">顏色</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">使用情況</th>
|
||||
<th scope="col" class="relative py-3.5 pl-3 pr-4"><span class="sr-only">操作</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 }}">
|
||||
<td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium">
|
||||
<span class="inline-flex items-center rounded-full px-2 py-1 text-xs font-medium" style="background-color: {{ $label->color }}20; color: {{ $label->color }}">
|
||||
{{ $label->name }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-900 dark:text-gray-100">
|
||||
{{ $label->description ?? '—' }}
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-4 h-4 rounded-full border border-gray-200 dark:border-gray-600" style="background-color: {{ $label->color }}"></div>
|
||||
{{ $label->color }}
|
||||
</div>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900 dark:text-gray-100">
|
||||
{{ $label->issues_count }}
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ $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
|
||||
<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">編輯</a>
|
||||
<form method="POST" action="{{ route('admin.issue-labels.destroy', $label) }}" class="inline" onsubmit="return confirm('刪除此標籤?')">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300 ml-2">刪除</button>
|
||||
</form>
|
||||
</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
|
||||
@@ -76,4 +76,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
</x-app-layout>
|
||||
@@ -1,7 +1,7 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
|
||||
{{ __('Task Reports & Analytics') }}
|
||||
任務報告與分析
|
||||
</h2>
|
||||
</x-slot>
|
||||
|
||||
@@ -13,20 +13,20 @@
|
||||
<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>
|
||||
@@ -34,19 +34,19 @@
|
||||
{{-- 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 Tasks') }}</dt>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">總任務數</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 Tasks') }}</dt>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">開啟的任務</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 Tasks') }}</dt>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">已結案任務</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 Tasks') }}</dt>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">逾期任務</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold text-gray-900 dark:text-gray-100">{{ number_format($stats['overdue_issues']) }}</dd>
|
||||
</div>
|
||||
</div>
|
||||
@@ -55,7 +55,7 @@
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
{{-- Tasks by Status --}}
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg px-4 py-5 sm:p-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">{{ __('Tasks by Status') }}</h3>
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">依狀態的任務</h3>
|
||||
<div class="space-y-2">
|
||||
@foreach(['new', 'assigned', 'in_progress', 'review', 'closed'] as $status)
|
||||
<div class="flex items-center justify-between">
|
||||
@@ -74,7 +74,7 @@
|
||||
|
||||
{{-- Tasks by Priority --}}
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg px-4 py-5 sm:p-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">{{ __('Tasks by Priority') }}</h3>
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">依優先級的任務</h3>
|
||||
<div class="space-y-2">
|
||||
@foreach(['low', 'medium', 'high', 'urgent'] as $priority)
|
||||
@php
|
||||
@@ -96,7 +96,7 @@
|
||||
|
||||
{{-- Tasks by Type --}}
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg px-4 py-5 sm:p-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">{{ __('Tasks by Type') }}</h3>
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">依類型的任務</h3>
|
||||
<div class="space-y-2">
|
||||
@foreach(['work_item', 'project_task', 'maintenance', 'member_request'] as $type)
|
||||
<div class="flex items-center justify-between">
|
||||
@@ -117,22 +117,22 @@
|
||||
{{-- Time Tracking Metrics --}}
|
||||
@if($timeTrackingMetrics && $timeTrackingMetrics->total_estimated > 0)
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg 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>
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">時間追蹤指標</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>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">總預估時數</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>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">總實際時數</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>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">平均預估時數</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>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">平均實際時數</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>
|
||||
@@ -142,7 +142,7 @@
|
||||
@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>
|
||||
@@ -154,24 +154,24 @@
|
||||
{{-- Average Resolution Time --}}
|
||||
@if($avgResolutionTime)
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg 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>
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-2">平均解決時間</h3>
|
||||
<p class="text-3xl font-semibold text-gray-900 dark:text-gray-100">{{ number_format($avgResolutionTime, 1) }} 天</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Assignee Performance --}}
|
||||
@if($assigneePerformance->isNotEmpty())
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg 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>
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">指派對象表現(前10名)</h3>
|
||||
<div class="overflow-hidden shadow ring-1 ring-black ring-opacity-5 sm:rounded-lg">
|
||||
<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>
|
||||
<th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">指派對象</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">總指派數</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">已完成</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">逾期</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">完成率</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700 bg-white dark:bg-gray-800">
|
||||
@@ -210,7 +210,7 @@
|
||||
{{-- Top Labels Used --}}
|
||||
@if($topLabels->isNotEmpty())
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg 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>
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">最常用標籤</h3>
|
||||
<div class="space-y-3">
|
||||
@foreach($topLabels as $label)
|
||||
<div class="flex items-center justify-between">
|
||||
@@ -220,7 +220,7 @@
|
||||
{{ $label->name }}
|
||||
</span>
|
||||
</div>
|
||||
<span class="text-sm font-semibold text-gray-900 dark:text-gray-100">{{ $label->usage_count }} {{ __('uses') }}</span>
|
||||
<span class="text-sm font-semibold text-gray-900 dark:text-gray-100">{{ $label->usage_count }} 使用</span>
|
||||
</div>
|
||||
<div class="w-full bg-gray-200 rounded-full h-2 dark:bg-gray-700">
|
||||
@php
|
||||
@@ -236,16 +236,16 @@
|
||||
|
||||
{{-- Recent Tasks --}}
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg px-4 py-5 sm:p-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">{{ __('Recent Tasks') }}</h3>
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">最近的任務</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">{{ __('Task') }}</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>
|
||||
<th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">任務</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">狀態</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">優先級</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">指派對象</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">已建立</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 bg-white dark:divide-gray-700 dark:bg-gray-800">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
|
||||
{{ __('Create Task') }} (建立任務)
|
||||
新增任務 (建立任務)
|
||||
</h2>
|
||||
</x-slot>
|
||||
|
||||
@@ -14,22 +14,22 @@
|
||||
<!-- 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>
|
||||
標題 <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 task') }}">
|
||||
placeholder="任務簡述">
|
||||
@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 task...') }}">{{ old('description') }}</textarea>
|
||||
placeholder="詳細描述此任務...">{{ old('description') }}</textarea>
|
||||
@error('description')<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>@enderror
|
||||
</div>
|
||||
|
||||
@@ -37,30 +37,30 @@
|
||||
<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">
|
||||
{{ __('Task Type') }} <span class="text-red-500">*</span>
|
||||
任務類型 <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>
|
||||
<option value="">選擇類型...</option>
|
||||
<option value="work_item" @selected(old('issue_type') === 'work_item')>工作項目</option>
|
||||
<option value="project_task" @selected(old('issue_type') === 'project_task')>專案任務</option>
|
||||
<option value="maintenance" @selected(old('issue_type') === 'maintenance')>維護</option>
|
||||
<option value="member_request" @selected(old('issue_type') === '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>
|
||||
優先級 <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>
|
||||
<option value="">選擇優先級...</option>
|
||||
<option value="low" @selected(old('priority') === 'low')>低 ↓</option>
|
||||
<option value="medium" @selected(old('priority', 'medium') === 'medium')>中 →</option>
|
||||
<option value="high" @selected(old('priority') === 'high')>高 ↑</option>
|
||||
<option value="urgent" @selected(old('priority') === 'urgent')>緊急 ⇈</option>
|
||||
</select>
|
||||
@error('priority')<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>@enderror
|
||||
</div>
|
||||
@@ -70,21 +70,21 @@
|
||||
<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>
|
||||
<option value="">未指派</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>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">選填:指派給團隊成員</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">
|
||||
@@ -95,50 +95,50 @@
|
||||
<!-- 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 task') }}</p>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">完成此任務的預估時間</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>
|
||||
<option value="">無</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>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">連結至會員(用於會員請求)</p>
|
||||
</div>
|
||||
|
||||
<!-- Parent Task (for sub-tasks) -->
|
||||
<div>
|
||||
<label for="parent_issue_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ __('Parent Task') }}
|
||||
父任務
|
||||
</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 task)') }}</option>
|
||||
<option value="">無(頂層任務)</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 task') }}</p>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">將此設為另一個任務的子任務</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)
|
||||
@@ -153,18 +153,18 @@
|
||||
</label>
|
||||
@endforeach
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">{{ __('Select one or more labels to categorize this task') }}</p>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">選擇一個或多個標籤來分類此任務</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 Task') }}
|
||||
新增任務
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -179,14 +179,14 @@
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<h3 class="text-sm font-medium text-blue-800 dark:text-blue-200">{{ __('Creating Tasks') }}</h3>
|
||||
<h3 class="text-sm font-medium text-blue-800 dark:text-blue-200">建立任務</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 tasks to team members to track responsibility') }}</li>
|
||||
<li>{{ __('Use labels to categorize and filter tasks easily') }}</li>
|
||||
<li>使用工作項目處理一般任務和待辦事項</li>
|
||||
<li>專案任務用於特定的專案里程碑</li>
|
||||
<li>會員請求追蹤來自會員的查詢或請求</li>
|
||||
<li>將任務指派給團隊成員以追蹤責任歸屬</li>
|
||||
<li>使用標籤輕鬆分類和篩選任務</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<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 }}
|
||||
編輯任務 - {{ $issue->issue_number }}
|
||||
</h2>
|
||||
</x-slot>
|
||||
|
||||
@@ -15,22 +15,22 @@
|
||||
<!-- 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>
|
||||
標題 <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') }}">
|
||||
placeholder="任務簡述">
|
||||
@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>
|
||||
placeholder="詳細描述此任務...">{{ old('description', $issue->description) }}</textarea>
|
||||
@error('description')<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>@enderror
|
||||
</div>
|
||||
|
||||
@@ -38,30 +38,30 @@
|
||||
<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>
|
||||
任務類型 <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>
|
||||
<option value="">選擇類型...</option>
|
||||
<option value="work_item" @selected(old('issue_type', $issue->issue_type) === 'work_item')>工作項目</option>
|
||||
<option value="project_task" @selected(old('issue_type', $issue->issue_type) === 'project_task')>專案任務</option>
|
||||
<option value="maintenance" @selected(old('issue_type', $issue->issue_type) === 'maintenance')>維護</option>
|
||||
<option value="member_request" @selected(old('issue_type', $issue->issue_type) === '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>
|
||||
優先級 <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>
|
||||
<option value="">選擇優先級...</option>
|
||||
<option value="low" @selected(old('priority', $issue->priority) === 'low')>低 ↓</option>
|
||||
<option value="medium" @selected(old('priority', $issue->priority) === 'medium')>中 →</option>
|
||||
<option value="high" @selected(old('priority', $issue->priority) === 'high')>高 ↑</option>
|
||||
<option value="urgent" @selected(old('priority', $issue->priority) === 'urgent')>緊急 ⇈</option>
|
||||
</select>
|
||||
@error('priority')<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>@enderror
|
||||
</div>
|
||||
@@ -71,11 +71,11 @@
|
||||
<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>
|
||||
<option value="">未指派</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
|
||||
@@ -84,11 +84,11 @@
|
||||
|
||||
<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>
|
||||
<option value="">無</option>
|
||||
@foreach($users as $user)
|
||||
<option value="{{ $user->id }}" @selected(old('reviewer_id', $issue->reviewer_id) == $user->id)>{{ $user->name }}</option>
|
||||
@endforeach
|
||||
@@ -100,7 +100,7 @@
|
||||
<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">
|
||||
@@ -109,7 +109,7 @@
|
||||
|
||||
<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">
|
||||
@@ -119,11 +119,11 @@
|
||||
<!-- 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>
|
||||
<option value="">無</option>
|
||||
@foreach($members as $member)
|
||||
<option value="{{ $member->id }}" @selected(old('member_id', $issue->member_id) == $member->id)>{{ $member->full_name }}</option>
|
||||
@endforeach
|
||||
@@ -133,11 +133,11 @@
|
||||
<!-- 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>
|
||||
<option value="">無(頂層任務)</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 }}
|
||||
@@ -149,7 +149,7 @@
|
||||
<!-- 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)
|
||||
@@ -170,11 +170,11 @@
|
||||
<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>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
|
||||
{{ __('Tasks') }} (任務追蹤)
|
||||
任務管理 (任務追蹤)
|
||||
</h2>
|
||||
</x-slot>
|
||||
|
||||
@@ -23,13 +23,13 @@
|
||||
<!-- 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">{{ __('Task Tracker') }}</h3>
|
||||
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">{{ __('Manage work items, tasks, and member requests') }}</p>
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">任務追蹤器</h3>
|
||||
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">管理工作項目、任務和會員請求</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 Task') }}
|
||||
新增任務
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -37,19 +37,19 @@
|
||||
<!-- 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>
|
||||
<dt class="text-sm font-medium text-blue-800 dark:text-blue-200">總開啟數</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>
|
||||
<dt class="text-sm font-medium text-purple-800 dark:text-purple-200">指派給我</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>
|
||||
<dt class="text-sm font-medium text-red-800 dark:text-red-200">逾期</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>
|
||||
<dt class="text-sm font-medium text-orange-800 dark:text-orange-200">高優先級</dt>
|
||||
<dd class="mt-1 text-2xl font-semibold text-orange-900 dark:text-orange-100">{{ $stats['high_priority'] }}</dd>
|
||||
</div>
|
||||
</div>
|
||||
@@ -58,40 +58,40 @@
|
||||
<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>
|
||||
<label for="issue_type" class="block text-sm font-medium text-gray-700 dark:text-gray-300">類型</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>
|
||||
<option value="">所有類型</option>
|
||||
<option value="work_item" @selected(request('issue_type') === 'work_item')>工作項目</option>
|
||||
<option value="project_task" @selected(request('issue_type') === 'project_task')>專案任務</option>
|
||||
<option value="maintenance" @selected(request('issue_type') === 'maintenance')>維護</option>
|
||||
<option value="member_request" @selected(request('issue_type') === 'member_request')>會員請求</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="status" class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ __('Status') }}</label>
|
||||
<label for="status" class="block text-sm font-medium text-gray-700 dark:text-gray-300">狀態</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>
|
||||
<option value="">所有狀態</option>
|
||||
<option value="new" @selected(request('status') === 'new')>新</option>
|
||||
<option value="assigned" @selected(request('status') === 'assigned')>已指派</option>
|
||||
<option value="in_progress" @selected(request('status') === 'in_progress')>進行中</option>
|
||||
<option value="review" @selected(request('status') === 'review')>審查</option>
|
||||
<option value="closed" @selected(request('status') === 'closed')>已結案</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="priority" class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ __('Priority') }}</label>
|
||||
<label for="priority" class="block text-sm font-medium text-gray-700 dark:text-gray-300">優先級</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>
|
||||
<option value="">所有優先級</option>
|
||||
<option value="low" @selected(request('priority') === 'low')>低</option>
|
||||
<option value="medium" @selected(request('priority') === 'medium')>中</option>
|
||||
<option value="high" @selected(request('priority') === 'high')>高</option>
|
||||
<option value="urgent" @selected(request('priority') === 'urgent')>緊急</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="assigned_to" class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ __('Assignee') }}</label>
|
||||
<label for="assigned_to" class="block text-sm font-medium text-gray-700 dark:text-gray-300">指派對象</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>
|
||||
<option value="">所有指派對象</option>
|
||||
@foreach($users as $user)
|
||||
<option value="{{ $user->id }}" @selected(request('assigned_to') == $user->id)>{{ $user->name }}</option>
|
||||
@endforeach
|
||||
@@ -100,14 +100,14 @@
|
||||
</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="{{ __('Task 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">
|
||||
<label for="search" class="block text-sm font-medium text-gray-700 dark:text-gray-300">搜尋</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 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>
|
||||
<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">篩選</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>
|
||||
<span class="ml-2 text-sm text-gray-700 dark:text-gray-300">顯示已結案</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -116,16 +116,16 @@
|
||||
<!-- 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 tasks with their current status and assignment') }}</caption>
|
||||
<caption class="sr-only">任務列表及其目前狀態與指派</caption>
|
||||
<thead class="bg-gray-50 dark:bg-gray-700">
|
||||
<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">{{ __('Task') }}</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>
|
||||
<th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">任務</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">類型</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">狀態</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">優先級</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">指派對象</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">截止日期</th>
|
||||
<th scope="col" class="relative py-3.5 pl-3 pr-4"><span class="sr-only">操作</span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 bg-white dark:divide-gray-700 dark:bg-gray-800">
|
||||
@@ -163,7 +163,7 @@
|
||||
<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>
|
||||
<span class="text-xs">(逾期)</span>
|
||||
@endif
|
||||
</span>
|
||||
@else
|
||||
@@ -171,16 +171,16 @@
|
||||
@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>
|
||||
<a href="{{ route('admin.issues.show', $issue) }}" class="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300">檢視</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 tasks found') }}</p>
|
||||
<p>找不到任務</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 Task') }}
|
||||
+ 新增第一個任務
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
@@ -31,23 +31,23 @@
|
||||
<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>建立者 {{ $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)
|
||||
@if(!$issue->isClosed() || Auth::user()->hasRole('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?') }}')">
|
||||
@if(Auth::user()->hasRole('admin'))
|
||||
<form method="POST" action="{{ route('admin.issues.destroy', $issue) }}" onsubmit="return confirm('您確定嗎?')">
|
||||
@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
|
||||
@@ -68,55 +68,55 @@
|
||||
|
||||
<!-- 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>
|
||||
<h4 class="text-sm font-medium text-gray-900 dark:text-gray-100 mb-2">描述</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>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">指派對象</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>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">審查者</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>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">截止日期</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)]) }})
|
||||
(逾期 {{ abs($issue->days_until_due) }} 天)
|
||||
@elseif($issue->days_until_due !== null && $issue->days_until_due >= 0)
|
||||
({{ __(':days days left', ['days' => $issue->days_until_due]) }})
|
||||
({{ $issue->days_until_due }} 天剩餘)
|
||||
@endif
|
||||
</span>
|
||||
@else
|
||||
<span class="text-gray-400">{{ __('No due date') }}</span>
|
||||
<span class="text-gray-400">無截止日期</span>
|
||||
@endif
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Time Tracking') }}</dt>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">時間追蹤</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') }}
|
||||
/ {{ number_format($issue->estimated_hours, 1) }}h 預估
|
||||
@endif
|
||||
</dd>
|
||||
</div>
|
||||
@if($issue->member)
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ __('Related Member') }}</dt>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">相關會員</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>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">父任務</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 }}
|
||||
@@ -129,7 +129,7 @@
|
||||
<!-- 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>
|
||||
<h4 class="text-sm font-medium text-gray-900 dark:text-gray-100 mb-3">子任務 ({{ $issue->subTasks->count() }})</h4>
|
||||
<ul class="space-y-2">
|
||||
@foreach($issue->subTasks as $subTask)
|
||||
<li class="flex items-center gap-2">
|
||||
@@ -150,21 +150,21 @@
|
||||
<!-- 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>
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">操作</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>
|
||||
<option value="new" @selected($issue->status === 'new')>新</option>
|
||||
<option value="assigned" @selected($issue->status === 'assigned')>已指派</option>
|
||||
<option value="in_progress" @selected($issue->status === 'in_progress')>進行中</option>
|
||||
<option value="review" @selected($issue->status === 'review')>審查</option>
|
||||
<option value="closed" @selected($issue->status === '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>
|
||||
|
||||
@@ -172,13 +172,13 @@
|
||||
<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>
|
||||
<option value="">未指派</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>
|
||||
@@ -188,7 +188,7 @@
|
||||
<!-- 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() }})
|
||||
留言 ({{ $issue->comments->count() }})
|
||||
</h3>
|
||||
|
||||
<!-- Comments List -->
|
||||
@@ -199,13 +199,13 @@
|
||||
<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>
|
||||
<span class="text-xs bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200 px-2 py-0.5 rounded">內部</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>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 text-center py-4">尚無留言</p>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
@@ -214,14 +214,14 @@
|
||||
@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>
|
||||
placeholder="新增留言..."></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>
|
||||
<span class="ml-2 text-sm text-gray-700 dark:text-gray-300">內部留言</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>
|
||||
@@ -230,7 +230,7 @@
|
||||
<!-- 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() }})
|
||||
附件 ({{ $issue->attachments->count() }})
|
||||
</h3>
|
||||
|
||||
<div class="space-y-2 mb-6">
|
||||
@@ -246,18 +246,18 @@
|
||||
</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?') }}')">
|
||||
<a href="{{ route('admin.issues.attachments.download', $attachment) }}" class="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 text-sm">下載</a>
|
||||
@if(Auth::user()->hasRole('admin'))
|
||||
<form method="POST" action="{{ route('admin.issues.attachments.destroy', $attachment) }}" onsubmit="return confirm('刪除此附件?')">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="text-red-600 hover:text-red-900 dark:text-red-400 text-sm">{{ __('Delete') }}</button>
|
||||
<button type="submit" class="text-red-600 hover:text-red-900 dark:text-red-400 text-sm">刪除</button>
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 text-center py-4">{{ __('No attachments') }}</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 text-center py-4">無附件</p>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
@@ -267,17 +267,17 @@
|
||||
<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>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">最大大小: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)
|
||||
時間追蹤 ({{ number_format($issue->total_time_logged, 1) }}h total)
|
||||
</h3>
|
||||
|
||||
<div class="space-y-2 mb-6">
|
||||
@@ -289,21 +289,21 @@
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 text-center py-4">{{ __('No time logged yet') }}</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 text-center py-4">尚未記錄時間</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
|
||||
<input type="number" name="hours" step="0.25" min="0.25" placeholder="時數" 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?') }}"
|
||||
<input type="text" name="description" placeholder="您做了什麼?"
|
||||
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>
|
||||
@@ -313,11 +313,11 @@
|
||||
<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>
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">進度</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="text-gray-700 dark:text-gray-300">完成度</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">
|
||||
@@ -329,7 +329,7 @@
|
||||
<!-- 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() }})
|
||||
觀察者 ({{ $issue->watchers->count() }})
|
||||
</h3>
|
||||
|
||||
<ul class="space-y-2 mb-4">
|
||||
@@ -341,7 +341,7 @@
|
||||
@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>
|
||||
<button type="submit" class="text-xs text-red-600 hover:text-red-900 dark:text-red-400">移除</button>
|
||||
</form>
|
||||
@endif
|
||||
</li>
|
||||
@@ -353,13 +353,13 @@
|
||||
@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>
|
||||
<option value="">新增觀察者...</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>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<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 }}
|
||||
啟用會員資格 - {{ $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">
|
||||
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg px-4 py-5 sm:p-6">
|
||||
|
||||
@if($approvedPayment)
|
||||
{{-- Approved Payment Info --}}
|
||||
@@ -19,12 +19,12 @@
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<h3 class="text-sm font-medium text-green-800 dark:text-green-200">{{ __('Payment Approved') }}</h3>
|
||||
<h3 class="text-sm font-medium text-green-800 dark:text-green-200">付款已核准</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>
|
||||
<p>金額: TWD {{ number_format($approvedPayment->amount, 0) }}</p>
|
||||
<p>付款日期: {{ $approvedPayment->paid_at->format('Y-m-d') }}</p>
|
||||
<p>付款方式: {{ $approvedPayment->payment_method_label }}</p>
|
||||
<p>核准日期: {{ $approvedPayment->chair_verified_at->format('Y-m-d H:i') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -33,18 +33,18 @@
|
||||
|
||||
{{-- Member Info --}}
|
||||
<div class="mb-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-3">{{ __('Member Information') }}</h3>
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-3">會員資訊</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>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">全名</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>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">電子郵件</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>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">目前狀態</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 }}
|
||||
@@ -59,19 +59,19 @@
|
||||
@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>
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">會員啟用詳情</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>
|
||||
會員類型 <span class="text-red-500 dark:text-red-400">*</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>
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm @error('membership_type') border-red-300 @enderror">
|
||||
<option value="regular" {{ old('membership_type', 'regular') == 'regular' ? 'selected' : '' }}>一般會員</option>
|
||||
<option value="student" {{ old('membership_type') == 'student' ? 'selected' : '' }}>學生會員</option>
|
||||
<option value="honorary" {{ old('membership_type') == 'honorary' ? 'selected' : '' }}>榮譽會員</option>
|
||||
<option value="lifetime" {{ old('membership_type') == 'lifetime' ? 'selected' : '' }}>終身會員</option>
|
||||
</select>
|
||||
@error('membership_type')<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>@enderror
|
||||
</div>
|
||||
@@ -79,23 +79,23 @@
|
||||
{{-- 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>
|
||||
會員資格開始日 <span class="text-red-500 dark:text-red-400">*</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">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm @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>
|
||||
會員資格到期日 <span class="text-red-500 dark:text-red-400">*</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>
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm @error('membership_expires_at') border-red-300 @enderror">
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">預設:開始日期後一年</p>
|
||||
@error('membership_expires_at')<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>@enderror
|
||||
</div>
|
||||
</div>
|
||||
@@ -110,21 +110,21 @@
|
||||
</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">
|
||||
<div class="flex items-center justify-end gap-x-4 border-t border-gray-200 dark:border-gray-700 pt-6">
|
||||
<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') }}
|
||||
class="rounded-md bg-white dark:bg-gray-700 px-3 py-2 text-sm font-semibold text-gray-900 dark:text-gray-100 shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-600 hover:bg-gray-50 dark:hover:bg-gray-600">
|
||||
取消
|
||||
</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') }}
|
||||
class="inline-flex justify-center rounded-md bg-indigo-600 dark:bg-indigo-500 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 dark:hover:bg-indigo-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">
|
||||
啟用會員資格
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -168,4 +168,4 @@
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</x-app-layout>
|
||||
</x-app-layout>
|
||||
@@ -1,236 +1,236 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800">
|
||||
{{ __('Create new member') }}
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
|
||||
新增會員
|
||||
</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="bg-white dark:bg-gray-800 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">
|
||||
<div class="mb-4 rounded-md bg-green-50 dark:bg-green-900/50 p-4" role="status" aria-live="polite">
|
||||
<p class="text-sm font-medium text-green-800 dark:text-green-200">
|
||||
{{ session('status') }}
|
||||
</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<form method="POST" action="{{ route('admin.members.store') }}" class="space-y-6" aria-label="{{ __('Create member form') }}">
|
||||
<form method="POST" action="{{ route('admin.members.store') }}" class="space-y-6" aria-label="新增會員表單">
|
||||
@csrf
|
||||
|
||||
<div>
|
||||
<label for="full_name" class="block text-sm font-medium text-gray-700">
|
||||
{{ __('Full name') }}
|
||||
<label for="full_name" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
全名
|
||||
</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"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300"
|
||||
required
|
||||
>
|
||||
@error('full_name')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-2 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="email" class="block text-sm font-medium text-gray-700">
|
||||
{{ __('Email') }}
|
||||
<label for="email" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
電子郵件
|
||||
</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"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300"
|
||||
required
|
||||
>
|
||||
@error('email')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-2 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
<p class="mt-1 text-sm text-gray-500">
|
||||
{{ __('An activation email will be sent to this address.') }}
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
啟用電子郵件將發送至此地址。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="national_id" class="block text-sm font-medium text-gray-700">
|
||||
{{ __('National ID') }}
|
||||
<label for="national_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
身分證號
|
||||
</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"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300"
|
||||
autocomplete="off"
|
||||
>
|
||||
@error('national_id')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-2 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
<p class="mt-1 text-sm text-gray-500">
|
||||
{{ __('Will be stored encrypted for security.') }}
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
將加密儲存以確保安全。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="phone" class="block text-sm font-medium text-gray-700">
|
||||
{{ __('Phone') }}
|
||||
<label for="phone" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
電話
|
||||
</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"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300"
|
||||
>
|
||||
@error('phone')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-2 text-sm text-red-600 dark:text-red-400">{{ $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 for="membership_started_at" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
會員資格開始日
|
||||
</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"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300"
|
||||
>
|
||||
@error('membership_started_at')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-2 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="membership_expires_at" class="block text-sm font-medium text-gray-700">
|
||||
{{ __('Membership expiry date') }}
|
||||
<label for="membership_expires_at" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
會員資格到期日
|
||||
</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"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300"
|
||||
>
|
||||
@error('membership_expires_at')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-2 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="address_line_1" class="block text-sm font-medium text-gray-700">
|
||||
{{ __('Address Line 1') }}
|
||||
<label for="address_line_1" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
地址第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"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300"
|
||||
>
|
||||
@error('address_line_1')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-2 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="address_line_2" class="block text-sm font-medium text-gray-700">
|
||||
{{ __('Address Line 2') }}
|
||||
<label for="address_line_2" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
地址第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"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300"
|
||||
>
|
||||
@error('address_line_2')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-2 text-sm text-red-600 dark:text-red-400">{{ $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 for="city" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
城市
|
||||
</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"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300"
|
||||
>
|
||||
@error('city')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-2 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="postal_code" class="block text-sm font-medium text-gray-700">
|
||||
{{ __('Postal Code') }}
|
||||
<label for="postal_code" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
郵遞區號
|
||||
</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"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300"
|
||||
>
|
||||
@error('postal_code')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-2 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="emergency_contact_name" class="block text-sm font-medium text-gray-700">
|
||||
{{ __('Emergency Contact Name') }}
|
||||
<label for="emergency_contact_name" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
緊急聯絡人姓名
|
||||
</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"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300"
|
||||
>
|
||||
@error('emergency_contact_name')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-2 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="emergency_contact_phone" class="block text-sm font-medium text-gray-700">
|
||||
{{ __('Emergency Contact Phone') }}
|
||||
<label for="emergency_contact_phone" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
緊急聯絡人電話
|
||||
</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"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300"
|
||||
>
|
||||
@error('emergency_contact_phone')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-2 text-sm text-red-600 dark:text-red-400">{{ $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 href="{{ route('admin.members.index') }}" class="inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:focus:ring-indigo-600 focus:ring-offset-2 dark:focus:ring-offset-gray-800">
|
||||
取消
|
||||
</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 type="submit" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 dark:bg-indigo-500 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 dark:hover:bg-indigo-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:focus:ring-indigo-600 focus:ring-offset-2 dark:focus:ring-offset-gray-800">
|
||||
新增會員
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800">
|
||||
{{ __('Edit member') }}
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
|
||||
編輯會員
|
||||
</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="bg-white dark:bg-gray-800 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">
|
||||
<div class="mb-4 rounded-md bg-green-50 dark:bg-green-900/50 p-4" role="status" aria-live="polite">
|
||||
<p class="text-sm font-medium text-green-800 dark:text-green-200">
|
||||
{{ session('status') }}
|
||||
</p>
|
||||
</div>
|
||||
@@ -22,210 +22,210 @@
|
||||
@method('PATCH')
|
||||
|
||||
<div>
|
||||
<label for="full_name" class="block text-sm font-medium text-gray-700">
|
||||
{{ __('Full name') }}
|
||||
<label for="full_name" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
全名
|
||||
</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"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300"
|
||||
required
|
||||
>
|
||||
@error('full_name')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-2 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="email" class="block text-sm font-medium text-gray-700">
|
||||
{{ __('Email') }}
|
||||
<label for="email" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
電子郵件
|
||||
</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"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300"
|
||||
required
|
||||
>
|
||||
@error('email')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-2 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="national_id" class="block text-sm font-medium text-gray-700">
|
||||
{{ __('National ID') }}
|
||||
<label for="national_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
身分證號
|
||||
</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"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300"
|
||||
autocomplete="off"
|
||||
>
|
||||
@error('national_id')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-2 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
<p class="mt-1 text-sm text-gray-500">
|
||||
{{ __('Will be stored encrypted for security.') }}
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
將加密儲存以確保安全。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="phone" class="block text-sm font-medium text-gray-700">
|
||||
{{ __('Phone') }}
|
||||
<label for="phone" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
電話
|
||||
</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"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300"
|
||||
>
|
||||
@error('phone')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-2 text-sm text-red-600 dark:text-red-400">{{ $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 for="membership_started_at" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
會員資格開始日
|
||||
</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"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300"
|
||||
>
|
||||
@error('membership_started_at')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-2 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="membership_expires_at" class="block text-sm font-medium text-gray-700">
|
||||
{{ __('Membership expiry date') }}
|
||||
<label for="membership_expires_at" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
會員資格到期日
|
||||
</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"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300"
|
||||
>
|
||||
@error('membership_expires_at')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-2 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="address_line_1" class="block text-sm font-medium text-gray-700">
|
||||
{{ __('Address Line 1') }}
|
||||
<label for="address_line_1" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
地址第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"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300"
|
||||
>
|
||||
@error('address_line_1')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-2 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="address_line_2" class="block text-sm font-medium text-gray-700">
|
||||
{{ __('Address Line 2') }}
|
||||
<label for="address_line_2" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
地址第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"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300"
|
||||
>
|
||||
@error('address_line_2')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-2 text-sm text-red-600 dark:text-red-400">{{ $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 for="city" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
城市
|
||||
</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"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300"
|
||||
>
|
||||
@error('city')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-2 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="postal_code" class="block text-sm font-medium text-gray-700">
|
||||
{{ __('Postal Code') }}
|
||||
<label for="postal_code" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
郵遞區號
|
||||
</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"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300"
|
||||
>
|
||||
@error('postal_code')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-2 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="emergency_contact_name" class="block text-sm font-medium text-gray-700">
|
||||
{{ __('Emergency Contact Name') }}
|
||||
<label for="emergency_contact_name" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
緊急聯絡人姓名
|
||||
</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"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300"
|
||||
>
|
||||
@error('emergency_contact_name')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-2 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="emergency_contact_phone" class="block text-sm font-medium text-gray-700">
|
||||
{{ __('Emergency Contact Phone') }}
|
||||
<label for="emergency_contact_phone" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
緊急聯絡人電話
|
||||
</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"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 sm:text-sm dark:bg-gray-900 dark:text-gray-300"
|
||||
>
|
||||
@error('emergency_contact_phone')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-2 text-sm text-red-600 dark:text-red-400">{{ $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 type="submit" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 dark:bg-indigo-500 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 dark:hover:bg-indigo-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:focus:ring-indigo-600 focus:ring-offset-2 dark:focus:ring-offset-gray-800">
|
||||
儲存變更
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -1,54 +1,54 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800">
|
||||
{{ __('Import members from CSV') }}
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
|
||||
從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="bg-white dark:bg-gray-800 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 class="text-sm text-gray-700 dark:text-gray-300 mb-4">
|
||||
上傳具有以下標題列的CSV檔案(現有會員將依電子郵件比對並更新):
|
||||
</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 class="list-disc list-inside text-sm text-gray-700 dark:text-gray-300 mb-4 space-y-1">
|
||||
<li><code class="text-gray-800 dark:text-gray-200">full_name</code></li>
|
||||
<li><code class="text-gray-800 dark:text-gray-200">email</code></li>
|
||||
<li><code class="text-gray-800 dark:text-gray-200">phone</code></li>
|
||||
<li><code class="text-gray-800 dark:text-gray-200">address_line_1</code> (optional)</li>
|
||||
<li><code class="text-gray-800 dark:text-gray-200">address_line_2</code> (optional)</li>
|
||||
<li><code class="text-gray-800 dark:text-gray-200">city</code> (optional)</li>
|
||||
<li><code class="text-gray-800 dark:text-gray-200">postal_code</code> (optional)</li>
|
||||
<li><code class="text-gray-800 dark:text-gray-200">emergency_contact_name</code> (optional)</li>
|
||||
<li><code class="text-gray-800 dark:text-gray-200">emergency_contact_phone</code> (optional)</li>
|
||||
<li><code class="text-gray-800 dark:text-gray-200">membership_started_at</code> (YYYY-MM-DD)</li>
|
||||
<li><code class="text-gray-800 dark:text-gray-200">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 for="file" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
CSV檔案
|
||||
</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"
|
||||
class="mt-1 block w-full text-sm text-gray-900 dark:text-gray-100 file:mr-4 file:rounded-md file:border-0 file:bg-indigo-50 dark:file:bg-indigo-900/50 file:px-4 file:py-2 file:text-sm file:font-semibold file:text-indigo-700 dark:file:text-indigo-300 hover:file:bg-indigo-100 dark:hover:file:bg-indigo-900"
|
||||
required
|
||||
>
|
||||
@error('file')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-2 text-sm text-red-600 dark:text-red-400">{{ $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 type="submit" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 dark:bg-indigo-500 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 dark:hover:bg-indigo-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:focus:ring-indigo-600 focus:ring-offset-2 dark:focus:ring-offset-gray-800">
|
||||
開始匯入
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user