import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'; import { Head, Link, useForm, router } from '@inertiajs/react'; import { useState, useCallback, useEffect } from 'react'; import { usePermission } from '@/hooks/usePermission'; import { debounce } from "lodash"; import { SearchableSelect } from "@/Components/ui/searchable-select"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/Components/ui/table'; import { Button } from '@/Components/ui/button'; import { Input } from '@/Components/ui/input'; import { Badge } from '@/Components/ui/badge'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "@/Components/ui/dialog"; import { Label } from '@/Components/ui/label'; import { Plus, Search, X, ClipboardCheck, Eye, Pencil, Trash2 } from 'lucide-react'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/Components/ui/alert-dialog"; import Pagination from '@/Components/shared/Pagination'; import { Can } from '@/Components/Permission/Can'; export default function Index({ docs, warehouses, filters }: any) { const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false); const [deleteId, setDeleteId] = useState(null); const { data, setData, post, processing, reset, errors, delete: destroy } = useForm({ warehouse_id: '', remarks: '', }); const { can } = usePermission(); const [searchTerm, setSearchTerm] = useState(filters.search || ""); const [warehouseFilter, setWarehouseFilter] = useState(filters.warehouse_id || "all"); const [perPage, setPerPage] = useState(filters.per_page || "10"); // Sync state with props useEffect(() => { setSearchTerm(filters.search || ""); setWarehouseFilter(filters.warehouse_id || "all"); setPerPage(filters.per_page || "10"); }, [filters]); // Debounced Search Handler const debouncedSearch = useCallback( debounce((term: string, warehouse: string) => { router.get( route('inventory.count.index'), { ...filters, search: term, warehouse_id: warehouse === "all" ? "" : warehouse }, { preserveState: true, replace: true, preserveScroll: true } ); }, 500), [filters] ); const handleSearchChange = (term: string) => { setSearchTerm(term); debouncedSearch(term, warehouseFilter); }; const handleFilterChange = (value: string) => { setWarehouseFilter(value); router.get( route('inventory.count.index'), { ...filters, warehouse_id: value === "all" ? "" : value }, { preserveState: false, replace: true, preserveScroll: true } ); }; const handleClearSearch = () => { setSearchTerm(""); router.get( route('inventory.count.index'), { ...filters, search: "", warehouse_id: warehouseFilter === "all" ? "" : warehouseFilter }, { preserveState: true, replace: true, preserveScroll: true } ); }; const handlePerPageChange = (value: string) => { setPerPage(value); router.get( route('inventory.count.index'), { ...filters, per_page: value }, { preserveState: false, replace: true, preserveScroll: true } ); }; const handleCreate = (e: React.FormEvent) => { e.preventDefault(); post(route('inventory.count.store'), { onSuccess: () => { setIsCreateDialogOpen(false); reset(); }, }); }; const confirmDelete = (id: string) => { setDeleteId(id); }; const handleDelete = () => { if (deleteId) { destroy(route('inventory.count.destroy', [deleteId]), { onSuccess: () => setDeleteId(null), onError: () => setDeleteId(null), }); } }; const getStatusBadge = (status: string) => { switch (status) { case 'draft': return 草稿; case 'counting': return 盤點中; case 'completed': return 盤點完成; case 'no_adjust': return 盤點完成 (無需盤調); case 'adjusted': return 已盤調庫存; case 'cancelled': return 已取消; default: return {status}; } }; return (

庫存盤點管理

建立與管理倉庫盤點單,執行定期庫存核對。

{/* Toolbar */}
{/* Search */}
handleSearchChange(e.target.value)} className="pl-10 pr-10 h-9" /> {searchTerm && ( )}
{/* Warehouse Filter */} ({ label: w.name, value: w.id.toString() })) ]} placeholder="選擇倉庫" className="w-full md:w-[200px] h-9" /> {/* Action Buttons */}
建立新盤點單 建立後將自動對該倉庫庫存進行快照,請確認倉庫作業已暫停。
setData('warehouse_id', val)} options={warehouses.map((w: any) => ({ label: w.name, value: w.id.toString() }))} placeholder="請選擇倉庫" className="h-9" /> {errors.warehouse_id &&

{errors.warehouse_id}

}
setData('remarks', e.target.value)} />
# 單號 倉庫 狀態 快照時間 盤點進度 完成時間 建立人員 操作 {docs.data.length === 0 ? ( 尚無盤點紀錄 ) : ( docs.data.map((doc: any, index: number) => ( {(docs.current_page - 1) * docs.per_page + index + 1} {doc.doc_no} {doc.warehouse_name} {getStatusBadge(doc.status)} {doc.snapshot_date} {doc.counted_items} / {doc.total_items} {doc.completed_at || '-'} {doc.created_by}
{/* Action Button Logic: Prefer Edit if allowed and status is active, otherwise fallback to View if allowed */} {(() => { const isEditable = !['completed', 'no_adjust', 'adjusted'].includes(doc.status); const canEdit = can('inventory_count.edit'); const canView = can('inventory_count.view'); if (isEditable && canEdit) { return ( ); } if (canView) { return ( ); } return null; })()} {!['completed', 'no_adjust', 'adjusted'].includes(doc.status) && ( )}
)) )}
每頁顯示
共 {docs.total} 筆紀錄
!open && setDeleteId(null)}> 確定要作廢此盤點單嗎? 此動作無法復原。作廢後請重新建立盤點單。 取消 確認作廢
); }