Files
star-erp/resources/js/Pages/PurchaseOrder/Index.tsx
sky121113 566dfa31ae
All checks were successful
Koori-ERP-Deploy-System / deploy-demo (push) Successful in 1m11s
Koori-ERP-Deploy-System / deploy-production (push) Has been skipped
feat: 統一清單頁面分頁與每頁顯示 UI
2026-01-13 17:09:52 +08:00

176 lines
6.4 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, useCallback } from "react";
import { Plus, ShoppingCart } from 'lucide-react';
import { Button } from "@/Components/ui/button";
import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout";
import { Head, router } from "@inertiajs/react";
import PurchaseOrderTable from "@/Components/PurchaseOrder/PurchaseOrderTable";
import { PurchaseOrderFilters } from "@/Components/PurchaseOrder/PurchaseOrderFilters";
import { type DateRange } from "@/Components/PurchaseOrder/DateFilter";
import type { PurchaseOrder } from "@/types/purchase-order";
import { debounce } from "lodash";
import Pagination from "@/Components/shared/Pagination";
import { getBreadcrumbs } from "@/utils/breadcrumb";
import { Can } from "@/Components/Permission/Can";
import { SearchableSelect } from "@/Components/ui/searchable-select";
interface Props {
orders: {
data: PurchaseOrder[];
links: any[];
total: number;
from: number;
to: number;
};
filters: {
search?: string;
status?: string;
warehouse_id?: string;
sort_field?: string;
sort_direction?: string;
per_page?: string;
};
warehouses: { id: number; name: string }[];
}
export default function PurchaseOrderIndex({ orders, filters, warehouses }: Props) {
const [searchQuery, setSearchQuery] = useState(filters.search || "");
const [statusFilter, setStatusFilter] = useState<string>(filters.status || "all");
const [requesterFilter, setRequesterFilter] = useState<string>(filters.warehouse_id || "all");
const [perPage, setPerPage] = useState<string>(filters.per_page || "10");
const [dateRange, setDateRange] = useState<DateRange | null>(null);
const handleFilterChange = (newFilters: any) => {
router.get("/purchase-orders", {
...filters,
...newFilters,
page: 1,
}, {
preserveState: true,
replace: true,
});
};
const handleSearch = useCallback(
debounce((value: string) => {
handleFilterChange({ search: value });
}, 500),
[filters]
);
const onSearchChange = (value: string) => {
setSearchQuery(value);
handleSearch(value);
};
const onStatusChange = (value: string) => {
setStatusFilter(value);
handleFilterChange({ status: value });
};
const onWarehouseChange = (value: string) => {
setRequesterFilter(value);
handleFilterChange({ warehouse_id: value });
};
const handleClearFilters = () => {
setSearchQuery("");
setStatusFilter("all");
setRequesterFilter("all");
setDateRange(null);
router.get("/purchase-orders");
};
const hasActiveFilters = searchQuery !== "" || statusFilter !== "all" || requesterFilter !== "all" || dateRange !== null;
const handleNavigateToCreateOrder = () => {
router.get("/purchase-orders/create");
};
const handlePerPageChange = (value: string) => {
setPerPage(value);
router.get("/purchase-orders", {
...filters,
per_page: value,
page: 1,
}, {
preserveState: false,
replace: true,
});
};
return (
<AuthenticatedLayout breadcrumbs={getBreadcrumbs("purchaseOrders")}>
<Head title="採購管理 - 管理採購單" />
<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-[#01ab83]" />
</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>
</div>
</div>
<div className="mb-6">
<PurchaseOrderFilters
searchQuery={searchQuery}
statusFilter={statusFilter}
requesterFilter={requesterFilter}
warehouses={warehouses}
onSearchChange={onSearchChange}
onStatusChange={onStatusChange}
onRequesterChange={onWarehouseChange}
onClearFilters={handleClearFilters}
hasActiveFilters={hasActiveFilters}
dateRange={dateRange}
onDateRangeChange={setDateRange}
/>
</div>
<PurchaseOrderTable
orders={orders.data}
/>
{/* 分頁元件 - 統一樣式 */}
<div className="mt-4 flex flex-col sm:flex-row 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-[80px] h-8"
showSearch={false}
/>
<span></span>
</div>
<Pagination links={orders.links} />
</div>
</div>
</AuthenticatedLayout>
);
}