import { useState, useEffect, useCallback } from "react"; import { Button } from "@/Components/ui/button"; import { Input } from "@/Components/ui/input"; import { SearchableSelect } from "@/Components/ui/searchable-select"; import { Plus, Search, Package, X, Upload } from 'lucide-react'; import ProductTable from "@/Components/Product/ProductTable"; import ProductDialog from "@/Components/Product/ProductDialog"; import ProductImportDialog from "@/Components/Product/ProductImportDialog"; import CategoryManagerDialog from "@/Components/Category/CategoryManagerDialog"; import UnitManagerDialog, { Unit } from "@/Components/Unit/UnitManagerDialog"; import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout"; import { Head, router, usePage } from "@inertiajs/react"; import { PageProps as GlobalPageProps } from "@/types/global"; import { debounce } from "lodash"; import Pagination from "@/Components/shared/Pagination"; import { getBreadcrumbs } from "@/utils/breadcrumb"; import { Can } from "@/Components/Permission/Can"; export interface Category { id: number; name: string; } export interface Product { id: string; code: string; barcode?: string; name: string; categoryId: number; category?: Category; brand?: string; specification?: string; baseUnitId: number; baseUnit?: Unit; largeUnitId?: number; largeUnit?: Unit; conversionRate?: number; purchaseUnitId?: number; purchaseUnit?: Unit; location?: string; createdAt?: string; updatedAt?: string; cost_price?: number; price?: number; member_price?: number; wholesale_price?: number; } interface PageProps { products: { data: Product[]; links: any[]; // Todo: pagination types from: number; }; categories: Category[]; units: Unit[]; filters: { search?: string; category_id?: string; per_page?: string; sort_field?: string; sort_direction?: string; }; } export default function ProductManagement({ products, categories, units, filters }: PageProps) { const { branding } = usePage().props; const [searchTerm, setSearchTerm] = useState(filters.search || ""); const [typeFilter, setTypeFilter] = useState(filters.category_id || "all"); const [perPage, setPerPage] = useState(filters.per_page || "10"); const [sortField, setSortField] = useState(filters.sort_field || null); const [sortDirection, setSortDirection] = useState<"asc" | "desc" | null>(filters.sort_direction as "asc" | "desc" || null); const [isDialogOpen, setIsDialogOpen] = useState(false); const [isCategoryDialogOpen, setIsCategoryDialogOpen] = useState(false); const [isUnitDialogOpen, setIsUnitDialogOpen] = useState(false); const [isImportDialogOpen, setIsImportDialogOpen] = useState(false); const [editingProduct, setEditingProduct] = useState(null); // Sync state with props when they change (e.g. navigation) useEffect(() => { setSearchTerm(filters.search || ""); setTypeFilter(filters.category_id || "all"); setSearchTerm(filters.search || ""); setTypeFilter(filters.category_id || "all"); setPerPage(filters.per_page || "10"); setSortField(filters.sort_field || null); setSortDirection(filters.sort_direction as "asc" | "desc" || null); }, [filters]); const handleSort = (field: string) => { let newField: string | null = field; let newDirection: "asc" | "desc" | null = "asc"; if (sortField === field) { if (sortDirection === "asc") { newDirection = "desc"; } else { // desc -> reset newDirection = null; newField = null; } } setSortField(newField); setSortDirection(newDirection); router.get( route("products.index"), { search: searchTerm, category_id: typeFilter, per_page: perPage, sort_field: newField, sort_direction: newDirection }, { preserveState: true, replace: true, preserveScroll: true } ); }; // Debounced Search Handler const debouncedSearch = useCallback( debounce((term: string, category: string) => { router.get( route("products.index"), { search: term, category_id: category }, { preserveState: true, replace: true, preserveScroll: true } ); }, 500), [] ); const handleSearchChange = (term: string) => { setSearchTerm(term); debouncedSearch(term, typeFilter); }; const handleCategoryChange = (value: string) => { setTypeFilter(value); // Immediate update for category router.get( route("products.index"), { search: searchTerm, category_id: value }, { preserveState: false, replace: true, preserveScroll: true } ); }; const handleClearSearch = () => { setSearchTerm(""); // Immediate update for clear router.get( route("products.index"), { search: "", category_id: typeFilter, per_page: perPage }, { preserveState: true, replace: true, preserveScroll: true } ); }; const handlePerPageChange = (value: string) => { setPerPage(value); router.get( route("products.index"), { search: searchTerm, category_id: typeFilter, per_page: value }, { preserveState: false, replace: true, preserveScroll: true } ); }; const handleAddProduct = () => { setEditingProduct(null); setIsDialogOpen(true); }; const handleEditProduct = (product: Product) => { setEditingProduct(product); setIsDialogOpen(true); }; const handleDeleteProduct = (id: string) => { router.delete(route('products.destroy', id), { onSuccess: () => { // Toast handled by flash message } }); }; return (
{/* Header */}

商品資料管理

管理 {branding?.short_name || 'Star'} 原物料與成品資料

{/* Toolbar */}
{/* Search */}
handleSearchChange(e.target.value)} className="pl-10 pr-10" /> {searchTerm && ( )}
{/* Type Filter */} ({ label: cat.name, value: cat.id.toString() })) ]} placeholder="商品分類" className="w-full md:w-[180px]" /> {/* Action Buttons */}
{/* Product Table */} {/* 分頁元件 */}
每頁顯示
); }