feat(inventory): 統一庫存調整與調撥模組 UI,實作多選、搜尋與明細欄位重構
All checks were successful
Koori-ERP-Deploy-System / deploy-demo (push) Successful in 1m4s
Koori-ERP-Deploy-System / deploy-production (push) Has been skipped

This commit is contained in:
2026-01-29 14:37:21 +08:00
parent 2efaded77b
commit 7619dc24f7
5 changed files with 1013 additions and 576 deletions

View File

@@ -15,7 +15,6 @@ import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/Components/ui/dialog";
@@ -138,7 +137,7 @@ export default function Index({ docs, warehouses, filters }: { docs: DocsPaginat
const { data, setData, post, processing, reset } = useForm({
count_doc_id: null as string | null,
warehouse_id: '',
reason: '',
reason: '手動調整庫存',
remarks: '',
});
@@ -375,13 +374,16 @@ export default function Index({ docs, warehouses, filters }: { docs: DocsPaginat
<div className="py-4 space-y-6">
{/* Option 1: Scan/Select from Count Docs */}
<div className="space-y-4">
<Label className="text-sm font-semibold text-grey-700"> ()</Label>
<div className="space-y-4 p-4 rounded-xl bg-primary-lightest/50 border border-primary-light/20 shadow-sm">
<Label className="text-sm font-bold text-primary-main flex items-center gap-2">
<ClipboardCheck className="h-4 w-4" />
()
</Label>
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-grey-400" />
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-grey-3" />
<Input
placeholder="掃描盤點單號或搜尋..."
className="pl-9 h-11 border-primary-100 focus:ring-primary-main"
className="pl-9 h-9"
value={scanSearch}
onChange={(e) => {
setScanSearch(e.target.value);
@@ -390,26 +392,26 @@ export default function Index({ docs, warehouses, filters }: { docs: DocsPaginat
/>
</div>
<div className="max-height-[300px] overflow-y-auto rounded-md border border-grey-100 bg-grey-50">
<div className="max-h-[200px] overflow-y-auto rounded-lg border-2 border-grey-3 bg-white">
{loadingPending ? (
<div className="p-8 text-center text-sm text-grey-400">...</div>
<div className="p-8 text-center text-sm text-grey-3">...</div>
) : pendingCounts.length === 0 ? (
<div className="p-8 text-center text-sm text-grey-400">
調 ()
<div className="p-8 text-center text-sm text-grey-3">
調 ()
</div>
) : (
<div className="divide-y divide-grey-100">
<div className="divide-y divide-grey-4">
{pendingCounts.map((c: any) => (
<div
key={c.id}
className="p-3 hover:bg-white flex items-center justify-between cursor-pointer group transition-colors"
className="p-3 hover:bg-primary-lightest flex items-center justify-between cursor-pointer group transition-colors"
onClick={() => handleCreate(c.id)}
>
<div>
<p className="font-bold text-grey-900 group-hover:text-primary-main">{c.doc_no}</p>
<p className="text-xs text-grey-500">{c.warehouse_name} | : {c.completed_at}</p>
<p className="font-bold text-grey-0 group-hover:text-primary-main">{c.doc_no}</p>
<p className="text-xs text-grey-2">{c.warehouse_name} | : {c.completed_at}</p>
</div>
<Button size="sm" variant="outline" className="button-outlined-primary">
<Button size="sm" variant="outline" className="button-outlined-primary h-7 text-xs">
</Button>
</div>
@@ -419,47 +421,48 @@ export default function Index({ docs, warehouses, filters }: { docs: DocsPaginat
</div>
</div>
<div className="relative">
<div className="absolute inset-0 flex items-center"><span className="w-full border-t border-grey-200" /></div>
<div className="relative flex justify-center text-xs uppercase"><span className="bg-white px-2 text-grey-400 font-medium"></span></div>
<div className="relative flex items-center py-2">
<div className="flex-grow border-t border-grey-4"></div>
<span className="flex-shrink mx-4 text-xs font-semibold text-grey-3 uppercase tracking-wider"></span>
<div className="flex-grow border-t border-grey-4"></div>
</div>
{/* Option 2: Manual (Optional, though less common in this flow) */}
<div className="space-y-4">
<Label className="text-sm font-semibold text-grey-700">調</Label>
<div className="space-y-4 px-1">
<Label className="text-sm font-bold text-grey-0">調</Label>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label className="text-xs"></Label>
<Label className="text-xs font-semibold text-grey-1"></Label>
<SearchableSelect
options={warehouses.map(w => ({ value: w.id, label: w.name }))}
value={data.warehouse_id}
onValueChange={(val) => setData('warehouse_id', val)}
placeholder="選擇倉庫"
className="h-9"
/>
</div>
<div className="space-y-2">
<Label className="text-xs">調</Label>
<Label className="text-xs font-semibold text-grey-1">調</Label>
<Input
placeholder="例如: 報廢, 破損..."
value={data.reason}
onChange={(e) => setData('reason', e.target.value)}
className="h-10"
className="h-9"
/>
</div>
</div>
<div className="flex justify-end gap-2 pt-2">
<Button variant="outline" className="button-outlined-primary" onClick={() => setIsDialogOpen(false)}></Button>
<Button
className="button-filled-primary"
disabled={processing || !data.warehouse_id || !data.reason}
onClick={() => handleCreate()}
>
</Button>
</div>
</div>
</div>
<DialogFooter className="bg-gray-50 -mx-6 -mb-6 p-4 rounded-b-lg gap-2">
<Button variant="outline" className="button-outlined-primary" onClick={() => setIsDialogOpen(false)}></Button>
<Button
className="button-filled-primary"
disabled={processing || !data.warehouse_id || !data.reason}
onClick={() => handleCreate()}
>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</AuthenticatedLayout>