Files
star-erp/resources/js/Pages/Accounting/Report.tsx
sky121113 32c2612a5f
All checks were successful
Koori-ERP-Deploy-System / deploy-demo (push) Successful in 56s
Koori-ERP-Deploy-System / deploy-production (push) Has been skipped
feat(accounting): 實作公共事業費管理與會計支出報表功能
2026-01-20 09:44:05 +08:00

231 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState } from "react";
import { Button } from "@/Components/ui/button";
import { Input } from "@/Components/ui/input";
import {
BarChart3,
Download,
Calendar,
Filter,
ArrowUpRight,
TrendingDown,
FileSpreadsheet,
Package,
Pocket
} from 'lucide-react';
import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout";
import { Head, router } from "@inertiajs/react";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/Components/ui/table";
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from "@/Components/ui/card";
interface Record {
id: string;
date: string;
source: string;
category: string;
item: string;
reference: string;
invoice_number?: string;
amount: number | string;
}
interface PageProps {
records: Record[];
summary: {
total_amount: number;
purchase_total: number;
utility_total: number;
record_count: number;
};
filters: {
date_start: string;
date_end: string;
};
}
export default function AccountingReport({ records, summary, filters }: PageProps) {
const [dateStart, setDateStart] = useState(filters.date_start);
const [dateEnd, setDateEnd] = useState(filters.date_end);
const handleFilter = () => {
router.get(
route("accounting.report"),
{
date_start: dateStart,
date_end: dateEnd,
},
{ preserveState: true }
);
};
const handleExport = () => {
window.location.href = route("accounting.export", {
date_start: dateStart,
date_end: dateEnd,
});
};
return (
<AuthenticatedLayout breadcrumbs={[{ label: "報表管理", href: "#" }, { label: "會計報表", href: route("accounting.report") }]}>
<Head title="會計報表" />
<div className="container mx-auto p-6 max-w-7xl">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4 mb-6">
<div>
<h1 className="text-2xl font-bold text-grey-0 flex items-center gap-2">
<BarChart3 className="h-6 w-6 text-primary-main" />
</h1>
<p className="text-gray-500 mt-1"></p>
</div>
<Button
onClick={handleExport}
variant="outline"
className="button-outlined-primary gap-2"
>
<Download className="h-4 w-4" /> CSV
</Button>
</div>
{/* Filters */}
<div className="bg-white rounded-lg shadow-sm border p-4 mb-6">
<div className="flex flex-wrap items-end gap-4">
<div className="space-y-2">
<label className="text-sm font-medium text-gray-700"></label>
<div className="relative">
<Calendar className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4 pointer-events-none" />
<Input
type="date"
value={dateStart}
onChange={(e) => setDateStart(e.target.value)}
className="pl-10 h-10 w-48"
/>
</div>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-gray-700"></label>
<div className="relative">
<Calendar className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4 pointer-events-none" />
<Input
type="date"
value={dateEnd}
onChange={(e) => setDateEnd(e.target.value)}
className="pl-10 h-10 w-48"
/>
</div>
</div>
<Button
onClick={handleFilter}
className="button-filled-primary h-10 px-6 gap-2"
>
<Filter className="h-4 w-4" />
</Button>
</div>
</div>
{/* Summary Cards */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
<Card className="border-l-4 border-l-red-500 shadow-sm">
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium text-gray-500"></CardTitle>
<TrendingDown className="h-4 w-4 text-red-500" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-gray-900">$ {Number(summary.total_amount).toLocaleString()}</div>
<p className="text-xs text-gray-400 mt-1"> {summary.record_count} </p>
</CardContent>
</Card>
<Card className="border-l-4 border-l-orange-500 shadow-sm">
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium text-gray-500"></CardTitle>
<Package className="h-4 w-4 text-orange-500" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-gray-900">$ {Number(summary.purchase_total).toLocaleString()}</div>
<p className="text-xs text-gray-400 mt-1"></p>
</CardContent>
</Card>
<Card className="border-l-4 border-l-blue-500 shadow-sm">
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium text-gray-500"></CardTitle>
<Pocket className="h-4 w-4 text-blue-500" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-gray-900">$ {Number(summary.utility_total).toLocaleString()}</div>
<p className="text-xs text-gray-400 mt-1"></p>
</CardContent>
</Card>
</div>
{/* Results Table */}
<div className="bg-white rounded-lg shadow-sm border overflow-hidden">
<Table>
<TableHeader className="bg-gray-50">
<TableRow>
<TableHead className="w-[120px] py-4 px-4"></TableHead>
<TableHead className="w-[120px]"></TableHead>
<TableHead className="w-[120px]"></TableHead>
<TableHead className="px-4"></TableHead>
<TableHead className="w-[180px]"> / </TableHead>
<TableHead className="w-[150px] text-right px-4"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{records.length === 0 ? (
<TableRow>
<TableCell colSpan={6} className="h-48 text-center text-gray-500">
</TableCell>
</TableRow>
) : (
records.map((record) => (
<TableRow key={record.id} className="hover:bg-gray-50/50">
<TableCell className="font-medium">{record.date}</TableCell>
<TableCell>
<span className={`px-2 py-0.5 rounded text-xs font-medium ${record.source === '採購單' ? 'bg-orange-100 text-orange-700' : 'bg-blue-100 text-blue-700'
}`}>
{record.source}
</span>
</TableCell>
<TableCell className="text-gray-600">{record.category}</TableCell>
<TableCell>
<div className="flex flex-col">
<span className="font-medium text-gray-900">{record.item}</span>
{record.invoice_number && (
<span className="text-xs text-gray-400">{record.invoice_number}</span>
)}
</div>
</TableCell>
<TableCell className="font-mono text-sm text-gray-500">
{record.reference}
</TableCell>
<TableCell className="text-right font-bold text-gray-900 px-4">
$ {Number(record.amount).toLocaleString()}
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</div>
</div>
</AuthenticatedLayout>
);
}