Files
star-erp/resources/js/Pages/ShippingOrder/Show.tsx
sky121113 04f3891275
All checks were successful
Koori-ERP-Deploy-System / deploy-demo (push) Successful in 1m11s
Koori-ERP-Deploy-System / deploy-production (push) Has been skipped
feat: 實作出貨單模組並暫時導向通用製作中頁面,同步優化盤點與調撥功能的活動日誌顯示
2026-02-05 09:33:36 +08:00

237 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { ArrowLeft, Package, Clock, User, CheckCircle2, AlertCircle, Trash2, Edit } from "lucide-react";
import { Button } from "@/Components/ui/button";
import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout";
import { Head, Link, router } from "@inertiajs/react";
import { Badge } from "@/Components/ui/badge";
import { toast } from "sonner";
import ActivityLogSection from "@/Components/ActivityLog/ActivityLogSection";
interface Props {
order: any;
}
export default function ShippingOrderShow({ order }: Props) {
const isDraft = order.status === 'draft';
const isCompleted = order.status === 'completed';
const handlePost = () => {
if (confirm('確定要執行過帳嗎?這將會從倉庫中扣除庫存數量。')) {
router.post(route('delivery-notes.post', order.id), {}, {
onSuccess: () => toast.success('過帳成功'),
onError: (errors: any) => toast.error(errors.error || '過帳失敗')
});
}
};
const handleDelete = () => {
if (confirm('確定要刪除這張出貨單嗎?')) {
router.delete(route('delivery-notes.destroy', order.id));
}
};
const getStatusBadge = (status: string) => {
switch (status) {
case 'draft':
return <Badge variant="secondary" className="px-3 py-1">稿</Badge>;
case 'completed':
return <Badge className="bg-green-100 text-green-800 px-3 py-1"></Badge>;
case 'cancelled':
return <Badge variant="destructive" className="px-3 py-1"></Badge>;
default:
return <Badge>{status}</Badge>;
}
};
return (
<AuthenticatedLayout breadcrumbs={[
{ label: '供應鏈管理', href: '#' },
{ label: '出貨單管理', href: route('delivery-notes.index') },
{ label: `出貨單詳情 (${order.doc_no})`, isPage: true }
] as any}>
<Head title={`出貨單詳情 - ${order.doc_no}`} />
<div className="container mx-auto p-6 max-w-7xl">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4 mb-6">
<div>
<Link href={route('delivery-notes.index')}>
<Button variant="outline" size="sm" className="gap-2 mb-4">
<ArrowLeft className="h-4 w-4" />
</Button>
</Link>
<div className="flex items-center gap-3">
<h1 className="text-2xl font-bold">{order.doc_no}</h1>
{getStatusBadge(order.status)}
</div>
<p className="text-gray-500 mt-1">
: {new Date(order.created_at).toLocaleString()} | : {order.creator_name}
</p>
</div>
<div className="flex items-center gap-2">
{isDraft && (
<>
<Button variant="outline" className="gap-2" asChild>
<Link href={route('delivery-notes.edit', order.id)}>
<Edit className="h-4 w-4" />
</Link>
</Button>
<Button variant="destructive" className="gap-2" onClick={handleDelete}>
<Trash2 className="h-4 w-4" />
</Button>
<Button className="button-filled-primary gap-2" onClick={handlePost}>
<CheckCircle2 className="h-4 w-4" />
</Button>
</>
)}
{isCompleted && (
<div className="flex items-center gap-2 text-green-600 font-medium bg-green-50 px-4 py-2 rounded-lg border border-green-200">
<CheckCircle2 className="h-5 w-5" />
{new Date(order.posted_at).toLocaleString()}
</div>
)}
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div className="lg:col-span-2 space-y-6">
{/* 基本資訊 */}
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
<h2 className="text-lg font-bold mb-6 flex items-center gap-2 border-b pb-4">
<Info className="h-5 w-5 text-primary-main" />
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-12 gap-y-6">
<div>
<label className="text-sm text-gray-500 block"></label>
<div className="font-medium text-lg">{order.warehouse_name}</div>
</div>
<div>
<label className="text-sm text-gray-500 block"></label>
<div className="font-medium text-lg">{order.shipping_date}</div>
</div>
<div>
<label className="text-sm text-gray-500 block"></label>
<div className="font-medium text-lg">{order.customer_name || '-'}</div>
</div>
<div>
<label className="text-sm text-gray-500 block"></label>
<div className="font-medium text-lg">{isCompleted ? '已完成 (已扣庫存)' : '草稿 (暫存中)'}</div>
</div>
<div className="md:col-span-2">
<label className="text-sm text-gray-500 block"></label>
<div className="text-gray-700 mt-1 bg-gray-50 p-3 rounded">{order.remarks || '無備註'}</div>
</div>
</div>
</div>
{/* 品項明細 */}
<div className="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
<div className="p-6 border-b border-gray-100 flex items-center justify-between">
<h2 className="text-lg font-bold flex items-center gap-2">
<Package className="h-5 w-5 text-primary-main" />
</h2>
<Badge variant="outline">{order.items.length} </Badge>
</div>
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="bg-gray-50 text-gray-600">
<th className="px-6 py-4 text-left font-semibold"> / </th>
<th className="px-6 py-4 text-left font-semibold"></th>
<th className="px-6 py-4 text-right font-semibold"></th>
<th className="px-6 py-4 text-right font-semibold"></th>
<th className="px-6 py-4 text-right font-semibold"></th>
</tr>
</thead>
<tbody className="divide-y divide-gray-100">
{order.items.map((item: any) => (
<tr key={item.id} className="hover:bg-gray-50 transition-colors">
<td className="px-6 py-4">
<div className="font-medium text-gray-900">{item.product_name}</div>
<div className="text-xs text-gray-500 font-mono">{item.product_code}</div>
</td>
<td className="px-6 py-4">
<Badge variant="outline" className="font-mono">{item.batch_number || 'N/A'}</Badge>
</td>
<td className="px-6 py-4 text-right">
<span className="font-medium text-gray-900">{parseFloat(item.quantity).toLocaleString()}</span>
<span className="text-xs text-gray-500 ml-1">{item.unit_name}</span>
</td>
<td className="px-6 py-4 text-right text-gray-600">
${parseFloat(item.unit_price).toLocaleString()}
</td>
<td className="px-6 py-4 text-right font-bold text-gray-900">
${parseFloat(item.subtotal).toLocaleString()}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
{/* 活動日誌區塊 */}
<div className="mt-8">
<ActivityLogSection
targetType="App\Modules\Procurement\Models\ShippingOrder"
targetId={order.id}
/>
</div>
</div>
{/* 右側:金額摘要 */}
<div className="space-y-6">
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200 sticky top-6">
<h2 className="text-lg font-bold mb-6 flex items-center gap-2 border-b pb-4">
<CalculatorIcon className="h-5 w-5 text-primary-main" />
</h2>
<div className="space-y-4">
<div className="flex justify-between items-center py-2 border-b border-dashed">
<span className="text-gray-500"></span>
<span className="font-medium">${Number(order.total_amount).toLocaleString()}</span>
</div>
<div className="flex justify-between items-center py-2 border-b border-dashed">
<span className="text-gray-500"> (5%)</span>
<span className="font-medium">${Number(order.tax_amount).toLocaleString()}</span>
</div>
<div className="flex justify-between items-center py-4">
<span className="font-bold text-lg text-gray-900"></span>
<span className="font-black text-2xl text-primary-main">
${Number(order.grand_total).toLocaleString()}
</span>
</div>
{isDraft && (
<div className="mt-6 p-4 bg-amber-50 rounded-lg border border-amber-200 text-sm text-amber-800 flex gap-3">
<AlertCircle className="h-5 w-5 shrink-0" />
<div>
<p className="font-bold mb-1"></p>
<p>稿</p>
</div>
</div>
)}
</div>
</div>
</div>
</div>
</div>
</AuthenticatedLayout>
);
}
function CalculatorIcon({ className }: { className?: string }) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}>
<rect width="16" height="20" x="4" y="2" rx="2" />
<line x1="8" x2="16" y1="6" y2="6" />
<line x1="16" x2="16" y1="14" y2="18" />
<path d="M16 10h.01" />
<path d="M12 10h.01" />
<path d="M8 10h.01" />
<path d="M12 14h.01" />
<path d="M8 14h.01" />
<path d="M12 18h.01" />
<path d="M8 18h.01" />
</svg>
);
}