diff --git a/app/Http/Controllers/AccountingReportController.php b/app/Http/Controllers/AccountingReportController.php
index 929c867..3ae426c 100644
--- a/app/Http/Controllers/AccountingReportController.php
+++ b/app/Http/Controllers/AccountingReportController.php
@@ -7,13 +7,14 @@ use App\Models\UtilityFee;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Illuminate\Support\Carbon;
+use Illuminate\Pagination\LengthAwarePaginator;
class AccountingReportController extends Controller
{
public function index(Request $request)
{
- $dateStart = $request->input('date_start', Carbon::now()->startOfMonth()->toDateString());
- $dateEnd = $request->input('date_end', Carbon::now()->endOfMonth()->toDateString());
+ $dateStart = $request->input('date_start', Carbon::now()->toDateString());
+ $dateEnd = $request->input('date_end', Carbon::now()->toDateString());
// 1. Get Purchase Orders (Completed or Received that are ready for accounting)
$purchaseOrders = PurchaseOrder::with(['vendor'])
@@ -23,7 +24,7 @@ class AccountingReportController extends Controller
->map(function ($po) {
return [
'id' => 'PO-' . $po->id,
- 'date' => $po->created_at->toDateString(),
+ 'date' => Carbon::parse($po->created_at)->timezone(config('app.timezone'))->toDateString(),
'source' => '採購單',
'category' => '進貨支出',
'item' => $po->vendor->name ?? '未知廠商',
@@ -39,7 +40,7 @@ class AccountingReportController extends Controller
->map(function ($fee) {
return [
'id' => 'UF-' . $fee->id,
- 'date' => $fee->transaction_date,
+ 'date' => $fee->transaction_date->format('Y-m-d'),
'source' => '公共事業費',
'category' => $fee->category,
'item' => $fee->description ?: $fee->category,
@@ -54,6 +55,19 @@ class AccountingReportController extends Controller
->sortByDesc('date')
->values();
+ // 3. Manual Pagination
+ $perPage = $request->input('per_page', 10);
+ $page = $request->input('page', 1);
+ $offset = ($page - 1) * $perPage;
+
+ $paginatedRecords = new LengthAwarePaginator(
+ $allRecords->slice($offset, $perPage)->values(),
+ $allRecords->count(),
+ $perPage,
+ $page,
+ ['path' => $request->url(), 'query' => $request->query()]
+ );
+
$summary = [
'total_amount' => $allRecords->sum('amount'),
'purchase_total' => $purchaseOrders->sum('amount'),
@@ -62,19 +76,20 @@ class AccountingReportController extends Controller
];
return Inertia::render('Accounting/Report', [
- 'records' => $allRecords,
+ 'records' => $paginatedRecords,
'summary' => $summary,
'filters' => [
'date_start' => $dateStart,
'date_end' => $dateEnd,
+ 'per_page' => (int)$perPage,
],
]);
}
public function export(Request $request)
{
- $dateStart = $request->input('date_start', Carbon::now()->startOfMonth()->toDateString());
- $dateEnd = $request->input('date_end', Carbon::now()->endOfMonth()->toDateString());
+ $dateStart = $request->input('date_start', Carbon::now()->toDateString());
+ $dateEnd = $request->input('date_end', Carbon::now()->toDateString());
$purchaseOrders = PurchaseOrder::with(['vendor'])
->whereIn('status', ['received', 'completed'])
diff --git a/app/Models/PurchaseOrder.php b/app/Models/PurchaseOrder.php
index 1fa8ff1..794647e 100644
--- a/app/Models/PurchaseOrder.php
+++ b/app/Models/PurchaseOrder.php
@@ -30,8 +30,8 @@ class PurchaseOrder extends Model
];
protected $casts = [
- 'expected_delivery_date' => 'date',
- 'invoice_date' => 'date',
+ 'expected_delivery_date' => 'date:Y-m-d',
+ 'invoice_date' => 'date:Y-m-d',
'total_amount' => 'decimal:2',
'tax_amount' => 'decimal:2',
'grand_total' => 'decimal:2',
@@ -76,7 +76,7 @@ class PurchaseOrder extends Model
public function getExpectedDateAttribute(): ?string
{
- return $this->expected_delivery_date ? $this->expected_delivery_date->format('Y-m-d') : null;
+ return $this->attributes['expected_delivery_date'] ?? null;
}
public function getTotalAmountAttribute(): float
@@ -111,8 +111,7 @@ class PurchaseOrder extends Model
public function getInvoiceDateAttribute(): ?string
{
- $date = $this->attributes['invoice_date'] ?? null;
- return $date ? \Illuminate\Support\Carbon::parse($date)->format('Y-m-d') : null;
+ return $this->attributes['invoice_date'] ?? null;
}
public function getInvoiceAmountAttribute(): ?float
diff --git a/resources/js/Components/Product/ProductTable.tsx b/resources/js/Components/Product/ProductTable.tsx
index 6f77620..a6c0c83 100644
--- a/resources/js/Components/Product/ProductTable.tsx
+++ b/resources/js/Components/Product/ProductTable.tsx
@@ -71,7 +71,7 @@ export default function ProductTable({
<>
-
+
#
diff --git a/resources/js/Components/UtilityFee/UtilityFeeDialog.tsx b/resources/js/Components/UtilityFee/UtilityFeeDialog.tsx
index 124fc1b..feafee4 100644
--- a/resources/js/Components/UtilityFee/UtilityFeeDialog.tsx
+++ b/resources/js/Components/UtilityFee/UtilityFeeDialog.tsx
@@ -68,7 +68,7 @@ export default function UtilityFeeDialog({
clearErrors();
if (fee) {
setData({
- transaction_date: fee.transaction_date.split("T")[0].split(" ")[0],
+ transaction_date: fee.transaction_date,
category: fee.category,
amount: fee.amount.toString(),
invoice_number: fee.invoice_number || "",
diff --git a/resources/js/Components/Vendor/VendorTable.tsx b/resources/js/Components/Vendor/VendorTable.tsx
index 693db7d..18f7a66 100644
--- a/resources/js/Components/Vendor/VendorTable.tsx
+++ b/resources/js/Components/Vendor/VendorTable.tsx
@@ -57,7 +57,7 @@ export default function VendorTable({
return (
-
+
#
diff --git a/resources/js/Layouts/AuthenticatedLayout.tsx b/resources/js/Layouts/AuthenticatedLayout.tsx
index 91c80bb..65736d3 100644
--- a/resources/js/Layouts/AuthenticatedLayout.tsx
+++ b/resources/js/Layouts/AuthenticatedLayout.tsx
@@ -154,7 +154,7 @@ export default function AuthenticatedLayout({
id: "accounting-report",
label: "會計報表",
icon: ,
- route: "/accounting/report",
+ route: "/accounting-report",
permission: "accounting.view",
},
],
diff --git a/resources/js/Pages/Accounting/Report.tsx b/resources/js/Pages/Accounting/Report.tsx
index 3bcfe10..e86abc3 100644
--- a/resources/js/Pages/Accounting/Report.tsx
+++ b/resources/js/Pages/Accounting/Report.tsx
@@ -1,16 +1,17 @@
import { useState } from "react";
import { Button } from "@/Components/ui/button";
import { Input } from "@/Components/ui/input";
+import { Label } from "@/Components/ui/label";
import {
BarChart3,
Download,
Calendar,
Filter,
- ArrowUpRight,
TrendingDown,
- FileSpreadsheet,
Package,
- Pocket
+ Pocket,
+ RotateCcw,
+ FileText
} from 'lucide-react';
import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout";
import { Head, router } from "@inertiajs/react";
@@ -22,12 +23,10 @@ import {
TableHeader,
TableRow,
} from "@/Components/ui/table";
-import {
- Card,
- CardContent,
- CardHeader,
- CardTitle,
-} from "@/Components/ui/card";
+import { getDateRange, formatDateWithDayOfWeek } from "@/utils/format";
+import { Badge } from "@/Components/ui/badge";
+import Pagination from "@/Components/shared/Pagination";
+import { SearchableSelect } from "@/Components/ui/searchable-select";
interface Record {
id: string;
@@ -41,7 +40,14 @@ interface Record {
}
interface PageProps {
- records: Record[];
+ records: {
+ data: Record[];
+ links: any[];
+ total: number;
+ from: number;
+ to: number;
+ current_page: number;
+ };
summary: {
total_amount: number;
purchase_total: number;
@@ -51,6 +57,7 @@ interface PageProps {
filters: {
date_start: string;
date_end: string;
+ per_page?: number;
};
}
@@ -58,17 +65,54 @@ export default function AccountingReport({ records, summary, filters }: PageProp
const [dateStart, setDateStart] = useState(filters.date_start);
const [dateEnd, setDateEnd] = useState(filters.date_end);
+ // Determine initial range type
+ const today = new Date().toISOString().split('T')[0];
+ const initialRangeType = (filters.date_start === today && filters.date_end === today) ? "today" : "custom";
+ const [dateRangeType, setDateRangeType] = useState(initialRangeType);
+ const [perPage, setPerPage] = useState(filters.per_page?.toString() || "10");
+
+ const handleDateRangeChange = (type: string) => {
+ setDateRangeType(type);
+ if (type === "custom") return;
+
+ const { start, end } = getDateRange(type);
+ setDateStart(start);
+ setDateEnd(end);
+ };
+
+
const handleFilter = () => {
router.get(
route("accounting.report"),
{
date_start: dateStart,
date_end: dateEnd,
+ per_page: perPage,
},
{ preserveState: true }
);
};
+ const handlePerPageChange = (value: string) => {
+ setPerPage(value);
+ router.get(
+ route("accounting.report"),
+ {
+ date_start: dateStart,
+ date_end: dateEnd,
+ per_page: value,
+ },
+ { preserveState: true }
+ );
+ };
+
+ const handleClearFilters = () => {
+ setDateStart("");
+ setDateEnd("");
+ setPerPage("10");
+ router.get(route("accounting.report"), {}, { preserveState: false });
+ };
+
const handleExport = () => {
window.location.href = route("accounting.export", {
date_start: dateStart,
@@ -77,15 +121,16 @@ export default function AccountingReport({ records, summary, filters }: PageProp
};
return (
-
+
+ {/* Header */}
- 會計支出報表
+ 會計報表
彙整採購支出與各項公用事業費用
@@ -99,111 +144,161 @@ export default function AccountingReport({ records, summary, filters }: PageProp
- {/* Filters */}
-
-
-
-
-
-
-
setDateStart(e.target.value)}
- className="pl-10 h-10 w-48"
- />
+ {/* Filters with Quick Date Range */}
+
+
+
+
+
+
+ {[
+ { label: "今日", value: "today" },
+ { label: "昨日", value: "yesterday" },
+ { label: "本週", value: "this_week" },
+ { label: "本月", value: "this_month" },
+ { label: "上月", value: "last_month" },
+ ].map((opt) => (
+
+ ))}
+
+
+
+
+
+
+
+
+
+ {
+ setDateStart(e.target.value);
+ setDateRangeType('custom');
+ }}
+ className="pl-9 block w-full h-9 bg-white"
+ />
+
+
+
+
+
+
+ {
+ setDateEnd(e.target.value);
+ setDateRangeType('custom');
+ }}
+ className="pl-9 block w-full h-9 bg-white"
+ />
+
+
+
-
-
-
-
- setDateEnd(e.target.value)}
- className="pl-10 h-10 w-48"
- />
-
+ {/* Action Buttons */}
+
+
+
-
-
- {/* Summary Cards */}
-
-
-
- 總計支出
-
-
-
- $ {Number(summary.total_amount).toLocaleString()}
- 共有 {summary.record_count} 筆紀錄
-
-
+ {/* Compact Summary - Full Width Grid (Horizontal Style) */}
+
+
+
+
+ 總計支出
+ $ {Number(summary.total_amount).toLocaleString()}
+
+
-
-
- 採購支出
-
-
-
- $ {Number(summary.purchase_total).toLocaleString()}
- 採購單彙整
-
-
+
+
+
+ 採購支出
+ $ {Number(summary.purchase_total).toLocaleString()}
+
+
-
-
- 公共事業費
-
-
-
- $ {Number(summary.utility_total).toLocaleString()}
- 水、電、瓦斯、電信等費項
-
-
+
+
+
+ 公共事業費
+ $ {Number(summary.utility_total).toLocaleString()}
+
+
{/* Results Table */}
-
+
- 日期
- 來源
- 類別
- 項目詳細
- 憑證 / 單號
- 金額
+ 日期
+ 來源
+ 類別
+ 項目詳細
+ 金額
- {records.length === 0 ? (
+ {records.data.length === 0 ? (
-
- 此日期區間內無支出紀錄
+
+
) : (
- records.map((record) => (
-
- {record.date}
-
-
- {record.source}
-
+ records.data.map((record) => (
+
+
+ {formatDateWithDayOfWeek(record.date)}
+
+
+
+ {record.source}
+
+
+
+
+ {record.category}
+
- {record.category}
{record.item}
@@ -212,9 +307,6 @@ export default function AccountingReport({ records, summary, filters }: PageProp
)}
-
- {record.reference}
-
$ {Number(record.amount).toLocaleString()}
@@ -224,6 +316,29 @@ export default function AccountingReport({ records, summary, filters }: PageProp
+
+ {/* Pagination Footer */}
+
);
diff --git a/resources/js/Pages/UtilityFee/Index.tsx b/resources/js/Pages/UtilityFee/Index.tsx
index fe31f9b..eb9155e 100644
--- a/resources/js/Pages/UtilityFee/Index.tsx
+++ b/resources/js/Pages/UtilityFee/Index.tsx
@@ -212,7 +212,7 @@ export default function UtilityFeeIndex({ fees, availableCategories, filters }:
};
return (
-
+
@@ -385,7 +385,7 @@ export default function UtilityFeeIndex({ fees, availableCategories, filters }:
{/* Table */}
-
+
#
diff --git a/routes/web.php b/routes/web.php
index b6aa958..268016e 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -144,6 +144,12 @@ Route::middleware('auth')->group(function () {
->middleware('permission:inventory.view')
->name('api.warehouses.inventories');
+ // 系統管理
+ Route::middleware('permission:accounting.view')->prefix('accounting-report')->group(function () {
+ Route::get('/', [AccountingReportController::class, 'index'])->name('accounting.report');
+ Route::get('/export', [AccountingReportController::class, 'export'])->name('accounting.export');
+ });
+
// 系統管理
Route::prefix('admin')->group(function () {
Route::middleware('permission:roles.view')->group(function () {
@@ -171,11 +177,7 @@ Route::middleware('auth')->group(function () {
Route::middleware('permission:system.view_logs')->group(function () {
Route::get('/activity-logs', [ActivityLogController::class, 'index'])->name('activity-logs.index');
});
- // 會計報表
- Route::middleware('permission:accounting.view')->group(function () {
- Route::get('/accounting/report', [AccountingReportController::class, 'index'])->name('accounting.report');
- Route::get('/accounting/report/export', [AccountingReportController::class, 'export'])->name('accounting.export');
- });
+
});
}); // End of auth middleware group