Files
star-erp/resources/js/Pages/PurchaseOrder/Index.tsx

267 lines
12 KiB
TypeScript
Raw Normal View History

2025-12-30 15:03:19 +08:00
/**
*
*/
import { useState, useEffect } from "react";
import { Plus, ShoppingCart, Search, RotateCcw, Calendar } from 'lucide-react';
2025-12-30 15:03:19 +08:00
import { Button } from "@/Components/ui/button";
import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout";
import { Head, router } from "@inertiajs/react";
import PurchaseOrderTable from "@/Components/PurchaseOrder/PurchaseOrderTable";
import type { PurchaseOrder } from "@/types/purchase-order";
import Pagination from "@/Components/shared/Pagination";
2026-01-07 13:06:49 +08:00
import { getBreadcrumbs } from "@/utils/breadcrumb";
import { Can } from "@/Components/Permission/Can";
import { SearchableSelect } from "@/Components/ui/searchable-select";
import { Input } from "@/Components/ui/input";
import { Label } from "@/Components/ui/label";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/Components/ui/select";
2025-12-30 15:03:19 +08:00
interface Props {
orders: {
data: PurchaseOrder[];
links: any[];
total: number;
from: number;
to: number;
};
filters: {
search?: string;
status?: string;
warehouse_id?: string;
date_start?: string;
date_end?: string;
2025-12-30 15:03:19 +08:00
sort_field?: string;
sort_direction?: string;
per_page?: string;
2025-12-30 15:03:19 +08:00
};
warehouses: { id: number; name: string }[];
}
export default function PurchaseOrderIndex({ orders, filters, warehouses }: Props) {
// 篩選狀態
const [search, setSearch] = useState(filters.search || "");
const [status, setStatus] = useState<string>(filters.status || "all");
const [warehouseId, setWarehouseId] = useState<string>(filters.warehouse_id || "all");
const [dateStart, setDateStart] = useState(filters.date_start || "");
const [dateEnd, setDateEnd] = useState(filters.date_end || "");
const [perPage, setPerPage] = useState<string>(filters.per_page || "10");
2025-12-30 15:03:19 +08:00
// 同步 URL 參數到 State (雖有初始值,但若由外部連結進入可確保同步)
useEffect(() => {
setSearch(filters.search || "");
setStatus(filters.status || "all");
setWarehouseId(filters.warehouse_id || "all");
setDateStart(filters.date_start || "");
setDateEnd(filters.date_end || "");
setPerPage(filters.per_page || "10");
}, [filters]);
2025-12-30 15:03:19 +08:00
const handleFilter = () => {
router.get(
route('purchase-orders.index'),
{
search,
status: status === 'all' ? undefined : status,
warehouse_id: warehouseId === 'all' ? undefined : warehouseId,
date_start: dateStart,
date_end: dateEnd,
per_page: perPage,
sort_field: filters.sort_field,
sort_direction: filters.sort_direction,
},
{ preserveState: true, replace: true }
);
2025-12-30 15:03:19 +08:00
};
const handleReset = () => {
setSearch("");
setStatus("all");
setWarehouseId("all");
setDateStart("");
setDateEnd("");
2025-12-30 15:03:19 +08:00
router.get(route('purchase-orders.index'));
2025-12-30 15:03:19 +08:00
};
const handlePerPageChange = (value: string) => {
setPerPage(value);
router.get(
route("purchase-orders.index"),
{
...filters,
per_page: value,
},
{ preserveState: false, replace: true, preserveScroll: true }
);
2025-12-30 15:03:19 +08:00
};
const handleNavigateToCreateOrder = () => {
router.get(route('purchase-orders.create'));
};
2025-12-30 15:03:19 +08:00
return (
2026-01-07 13:06:49 +08:00
<AuthenticatedLayout breadcrumbs={getBreadcrumbs("purchaseOrders")}>
<Head title="採購單管理" />
2025-12-30 15:03:19 +08:00
<div className="container mx-auto p-6 max-w-7xl">
<div className="flex items-center justify-between mb-6">
<div>
<h1 className="text-2xl font-bold text-grey-0 flex items-center gap-2">
<ShoppingCart className="h-6 w-6 text-primary-main" />
</h1>
<p className="text-gray-500 mt-1">
</p>
</div>
<div className="flex gap-2">
<Can permission="purchase_orders.create">
<Button
onClick={handleNavigateToCreateOrder}
className="gap-2 button-filled-primary"
>
<Plus className="h-4 w-4" />
</Button>
</Can>
2025-12-30 15:03:19 +08:00
</div>
</div>
{/* 篩選區塊 */}
<div className="bg-white p-5 rounded-lg shadow-sm border border-gray-200 mb-6">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-4">
{/* 關鍵字搜尋 */}
<div className="space-y-1">
<Label className="text-xs text-gray-500"></Label>
<div className="relative">
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-gray-400" />
<Input
placeholder="搜尋採購單號、廠商..."
value={search}
onChange={(e) => setSearch(e.target.value)}
className="pl-9"
onKeyDown={(e) => e.key === 'Enter' && handleFilter()}
/>
</div>
</div>
{/* 狀態篩選 */}
<div className="space-y-1">
<Label className="text-xs text-gray-500"></Label>
<Select value={status} onValueChange={setStatus}>
<SelectTrigger>
<SelectValue placeholder="選擇狀態" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all"></SelectItem>
<SelectItem value="draft">稿</SelectItem>
<SelectItem value="pending"></SelectItem>
<SelectItem value="processing"></SelectItem>
<SelectItem value="shipping"></SelectItem>
<SelectItem value="confirming"></SelectItem>
<SelectItem value="completed"></SelectItem>
<SelectItem value="cancelled"></SelectItem>
</SelectContent>
</Select>
</div>
{/* 倉庫篩選 */}
<div className="space-y-1">
<Label className="text-xs text-gray-500"></Label>
<SearchableSelect
value={warehouseId}
onValueChange={setWarehouseId}
options={[
{ label: "全部倉庫", value: "all" },
...warehouses.map(w => ({ label: w.name, value: String(w.id) }))
]}
placeholder="選擇倉庫"
className="w-full"
/>
</div>
{/* 日期範圍 - 開始 */}
<div className="space-y-1">
<Label className="text-xs text-gray-500"></Label>
<div className="relative">
<Calendar className="absolute left-2.5 top-2.5 h-4 w-4 text-gray-400" />
<Input
type="date"
value={dateStart}
onChange={(e) => setDateStart(e.target.value)}
className="pl-9 block w-full"
/>
</div>
</div>
{/* 日期範圍 - 結束 */}
<div className="space-y-1">
<Label className="text-xs text-gray-500"></Label>
<div className="relative">
<Calendar className="absolute left-2.5 top-2.5 h-4 w-4 text-gray-400" />
<Input
type="date"
value={dateEnd}
onChange={(e) => setDateEnd(e.target.value)}
className="pl-9 block w-full text-left"
/>
</div>
</div>
</div>
<div className="flex items-center justify-end gap-3 pt-2 border-t border-gray-100">
<Button
variant="outline"
onClick={handleReset}
className="flex items-center gap-2 button-outlined-primary"
>
<RotateCcw className="h-4 w-4" />
</Button>
<Button
onClick={handleFilter}
className="flex items-center gap-2 button-filled-primary"
>
<Search className="h-4 w-4" />
</Button>
</div>
2025-12-30 15:03:19 +08:00
</div>
<PurchaseOrderTable
orders={orders.data}
/>
{/* 分頁元件 - 統一樣式 */}
<div className="mt-4 flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
<div className="flex items-center gap-2 text-sm text-gray-500">
<span></span>
<SearchableSelect
value={perPage}
onValueChange={handlePerPageChange}
options={[
{ label: "10", value: "10" },
{ label: "20", value: "20" },
{ label: "50", value: "50" },
{ label: "100", value: "100" }
]}
className="w-[100px] h-8"
showSearch={false}
/>
<span></span>
</div>
<div className="w-full sm:w-auto flex justify-center sm:justify-end">
<Pagination links={orders.links} />
</div>
2025-12-30 15:03:19 +08:00
</div>
</div>
</AuthenticatedLayout>
);
}