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

176 lines
6.4 KiB
TypeScript
Raw Normal View History

2025-12-30 15:03:19 +08:00
/**
*
*/
import { useState, useCallback } from "react";
import { Plus, ShoppingCart } 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 { 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";
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";
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;
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 [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");
2025-12-30 15:03:19 +08:00
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,
});
};
2025-12-30 15:03:19 +08:00
return (
2026-01-07 13:06:49 +08:00
<AuthenticatedLayout breadcrumbs={getBreadcrumbs("purchaseOrders")}>
2025-12-30 15:03:19 +08:00
<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-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="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>
2025-12-30 15:03:19 +08:00
<Pagination links={orders.links} />
</div>
</div>
</AuthenticatedLayout>
);
}