feat(Inventory): 實作批號溯源完整功能與 UI 呈現,包含文字敘述卡片與更完整的關聯屬性
This commit is contained in:
154
resources/js/Pages/Inventory/Traceability/Index.tsx
Normal file
154
resources/js/Pages/Inventory/Traceability/Index.tsx
Normal file
@@ -0,0 +1,154 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Head, router } from '@inertiajs/react';
|
||||
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
|
||||
import { PageProps } from '@/types/global';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/Components/ui/card';
|
||||
import { Button } from '@/Components/ui/button';
|
||||
import { Input } from '@/Components/ui/input';
|
||||
import { Label } from '@/Components/ui/label';
|
||||
import { TrendingUp, Search, RotateCcw } from 'lucide-react';
|
||||
import { RadioGroup, RadioGroupItem } from '@/Components/ui/radio-group';
|
||||
import { cn } from '@/lib/utils';
|
||||
import TreeView, { TraceabilityNode } from './Components/TreeView';
|
||||
import { TraceabilitySummary } from './Components/TraceabilitySummary';
|
||||
import { Can } from '@/Components/Permission/Can';
|
||||
|
||||
interface Props extends PageProps {
|
||||
search: {
|
||||
batch_number: string | null;
|
||||
direction: 'backward' | 'forward';
|
||||
};
|
||||
result: TraceabilityNode | null;
|
||||
}
|
||||
|
||||
export default function TraceabilityIndex({ search, result }: Props) {
|
||||
const [batchNumber, setBatchNumber] = useState(search.batch_number || '');
|
||||
const [direction, setDirection] = useState<'backward' | 'forward'>(search.direction || 'backward');
|
||||
const [isSearching, setIsSearching] = useState(false);
|
||||
|
||||
const handleSearch = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!batchNumber.trim()) return;
|
||||
|
||||
setIsSearching(true);
|
||||
router.get(
|
||||
route('inventory.traceability.index'),
|
||||
{ batch_number: batchNumber.trim(), direction },
|
||||
{
|
||||
preserveState: true,
|
||||
preserveScroll: true,
|
||||
onFinish: () => setIsSearching(false)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<AuthenticatedLayout
|
||||
breadcrumbs={[
|
||||
{ label: '報表管理', href: '#' },
|
||||
{ label: '批號溯源', href: route('inventory.traceability.index'), isPage: true },
|
||||
]}
|
||||
>
|
||||
<Head title="批號溯源 - Star ERP" />
|
||||
|
||||
<div className="container mx-auto p-6 max-w-7xl">
|
||||
<div className="mb-6">
|
||||
<div className="mb-4">
|
||||
<h1 className="text-2xl font-bold text-grey-0 flex items-center gap-2">
|
||||
<TrendingUp className="h-6 w-6 text-primary-main" />
|
||||
批號溯源
|
||||
</h1>
|
||||
<p className="text-gray-500 mt-1">
|
||||
透過批號追蹤產品的生產履歷,支援從成品追溯原料供應商(逆向),或從原料追查銷售去向(順向)。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Can permission="inventory.traceability.view">
|
||||
<Card className="mb-6 bg-white shadow-sm border-gray-200">
|
||||
<CardHeader className="pb-3 border-b border-gray-100 bg-gray-50/50">
|
||||
<CardTitle className="text-lg flex items-center gap-2 text-gray-800">
|
||||
<Search className="h-5 w-5 text-primary-main" />
|
||||
查詢條件
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-6">
|
||||
<form onSubmit={handleSearch} className="flex flex-col md:flex-row gap-6 items-start md:items-end">
|
||||
<div className="flex-1 w-full space-y-1.5">
|
||||
<Label htmlFor="batchNumber" className="text-sm font-medium text-grey-1">查詢批號</Label>
|
||||
<Input
|
||||
id="batchNumber"
|
||||
type="text"
|
||||
placeholder="請輸入欲查詢的批號 (例如:PROD-TW-20240101-01)"
|
||||
value={batchNumber}
|
||||
onChange={(e) => setBatchNumber(e.target.value)}
|
||||
className="max-w-md w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<Label className="text-gray-700 font-medium">追蹤方向</Label>
|
||||
<RadioGroup
|
||||
value={direction}
|
||||
onValueChange={(val: 'backward' | 'forward') => setDirection(val)}
|
||||
className="flex space-x-6"
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center space-x-2 px-4 py-2 rounded-lg border cursor-pointer transition-colors",
|
||||
direction === 'backward' ? "bg-primary-lightest border-primary-light" : "bg-gray-50 border-gray-200 hover:bg-gray-100"
|
||||
)}
|
||||
>
|
||||
<RadioGroupItem value="backward" id="backward" />
|
||||
<Label htmlFor="backward" className="cursor-pointer flex items-center gap-1.5 font-medium">
|
||||
<RotateCcw className="h-4 w-4 text-primary-main" />
|
||||
逆向溯源 (成品 ➔ 原料)
|
||||
</Label>
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center space-x-2 px-4 py-2 rounded-lg border cursor-pointer transition-colors",
|
||||
direction === 'forward' ? "bg-primary-lightest border-primary-light" : "bg-gray-50 border-gray-200 hover:bg-gray-100"
|
||||
)}
|
||||
>
|
||||
<RadioGroupItem value="forward" id="forward" />
|
||||
<Label htmlFor="forward" className="cursor-pointer flex items-center gap-1.5 font-medium">
|
||||
<TrendingUp className="h-4 w-4 text-primary-main" />
|
||||
順向追蹤 (原料 ➔ 去向)
|
||||
</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isSearching || !batchNumber.trim()}
|
||||
className="button-filled-primary min-w-[120px]"
|
||||
>
|
||||
{isSearching ? '查詢中...' : '開始查詢'}
|
||||
</Button>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{search.batch_number && (
|
||||
<div className="bg-white rounded-xl border border-gray-200 shadow-sm overflow-hidden p-6 md:p-8">
|
||||
{result ? (
|
||||
<>
|
||||
<TraceabilitySummary data={result} direction={search.direction || 'backward'} />
|
||||
<TreeView data={result} />
|
||||
</>
|
||||
) : (
|
||||
<div className="py-16 flex flex-col items-center justify-center text-gray-500">
|
||||
<Search className="h-12 w-12 text-gray-300 mb-4" />
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-1">找不到符合的批號資料</h3>
|
||||
<p>請確認您輸入的批號「{search.batch_number}」是否正確存在於系統中。</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Can>
|
||||
</div>
|
||||
</AuthenticatedLayout>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user