feat(inventory): 實作過期與瑕疵庫存總計顯示,並強化庫存明細過期提示

This commit is contained in:
2026-02-05 15:50:14 +08:00
parent ba3c10ac13
commit a518d390bd
16 changed files with 751 additions and 574 deletions

View File

@@ -135,6 +135,12 @@ export default function InventoryTable({
<span className="text-sm text-gray-500">
{hasInventory ? `${group.batches.length} 個批號` : '無庫存'}
</span>
{group.batches.some(b => b.expiryDate && new Date(b.expiryDate) < new Date()) && (
<Badge className="bg-red-50 text-red-600 border-red-200">
<AlertTriangle className="mr-1 h-3 w-3" />
</Badge>
)}
</div>
<div className="flex items-center gap-4">
<div className="text-sm">
@@ -217,7 +223,23 @@ export default function InventoryTable({
<TableCell>${batch.total_value?.toLocaleString()}</TableCell>
</Can>
<TableCell>
{batch.expiryDate ? formatDate(batch.expiryDate) : "-"}
{batch.expiryDate ? (
<div className="flex items-center gap-2">
<span className={new Date(batch.expiryDate) < new Date() ? "text-red-600 font-medium" : ""}>
{formatDate(batch.expiryDate)}
</span>
{new Date(batch.expiryDate) < new Date() && (
<Tooltip>
<TooltipTrigger asChild>
<AlertTriangle className="h-4 w-4 text-red-500 cursor-help" />
</TooltipTrigger>
<TooltipContent>
<p></p>
</TooltipContent>
</Tooltip>
)}
</div>
) : "-"}
</TableCell>
<TableCell>
{batch.lastInboundDate ? formatDate(batch.lastInboundDate) : "-"}
@@ -280,7 +302,7 @@ export default function InventoryTable({
})}
</div>
</TooltipProvider>
</div >
</TooltipProvider >
);
}

View File

@@ -23,6 +23,7 @@ import {
DialogHeader,
DialogTitle,
} from "@/Components/ui/dialog";
import { Can } from "@/Components/Permission/Can";
interface WarehouseCardProps {
warehouse: Warehouse;
@@ -59,9 +60,12 @@ export default function WarehouseCard({
>
{/* 警告橫幅 */}
{hasWarning && (
<div className="absolute top-0 left-0 right-0 bg-orange-500 text-white px-4 py-1 flex items-center gap-2 text-sm">
<AlertTriangle className="h-4 w-4" />
<span></span>
<div className="absolute top-0 left-0 right-0 bg-orange-500 text-white px-4 py-1 flex items-center justify-between text-sm">
<div className="flex items-center gap-2">
<AlertTriangle className="h-4 w-4" />
<span></span>
</div>
<span className="font-bold">{stats.lowStockCount} </span>
</div>
)}
@@ -81,12 +85,16 @@ export default function WarehouseCard({
</button>
</div>
<div className="flex gap-2 mt-1">
<Badge variant="outline" className="text-xs font-normal">
<Badge
variant={warehouse.type === 'quarantine' ? "secondary" : "outline"}
className={`text-xs font-normal ${warehouse.type === 'quarantine' ? 'bg-red-100 text-red-700 border-red-200' : ''}`}
>
{WAREHOUSE_TYPE_LABELS[warehouse.type || 'standard'] || '標準倉'}
{warehouse.type === 'quarantine' ? ' (不計入可用)' : ' (計入可用)'}
</Badge>
{warehouse.type === 'transit' && warehouse.license_plate && (
<Badge variant="secondary" className="text-xs font-normal bg-yellow-100 text-yellow-800 border-yellow-200">
{warehouse.license_plate}
{warehouse.license_plate} {warehouse.driver_name && `(${warehouse.driver_name})`}
</Badge>
)}
</div>
@@ -100,46 +108,36 @@ export default function WarehouseCard({
{/* 統計區塊 - 狀態標籤 */}
<div className="space-y-3">
{/* 銷售狀態與可用性說明 */}
<div className="flex items-center justify-between">
<span className="text-sm text-gray-500"></span>
{warehouse.type === 'quarantine' ? (
<Badge variant="secondary" className="bg-red-100 text-red-700 border-red-200">
</Badge>
) : (
<Badge variant="default" className="bg-green-600">
</Badge>
{/* 帳面庫存總計 (金額) */}
<Can permission="inventory.view_cost">
<div className="flex items-center justify-between p-3 rounded-lg bg-primary-50/50 border border-primary-100">
<div className="flex items-center gap-2 text-primary-700">
<Package className="h-4 w-4" />
<span className="text-sm font-medium"></span>
</div>
<div className="text-sm font-bold text-primary-main">
${Number(stats.totalValue || 0).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
</div>
</div>
</Can>
{/* 過期與瑕疵總計 (金額) */}
<Can permission="inventory.view_cost">
{Number(stats.abnormalValue || 0) > 0 && (
<div className="flex items-center justify-between p-3 rounded-lg bg-red-50/50 border border-red-100 mt-3">
<div className="flex items-center gap-2 text-red-700">
<AlertTriangle className="h-4 w-4" />
<span className="text-sm font-medium"></span>
</div>
<div className="text-sm font-bold text-red-600">
${Number(stats.abnormalValue || 0).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
</div>
</div>
)}
</div>
</Can>
{/* 低庫存警告狀態 */}
<div className="flex items-center justify-between p-3 rounded-lg bg-gray-50">
<div className="flex items-center gap-2 text-gray-600">
<AlertTriangle className="h-4 w-4" />
<span className="text-sm"></span>
</div>
<div>
{hasWarning ? (
<Badge className="bg-orange-500 text-white hover:bg-orange-600 border-none px-2 py-0.5">
{stats.lowStockCount}
</Badge>
) : (
<Badge variant="secondary" className="bg-green-100 text-green-700 hover:bg-green-100 border-green-200">
</Badge>
)}
</div>
</div>
{/* 移動倉司機資訊 */}
{warehouse.type === 'transit' && warehouse.driver_name && (
<div className="flex items-center justify-between pt-2 border-t border-gray-100">
<span className="text-sm text-gray-500"></span>
<span className="text-sm font-medium text-gray-900">{warehouse.driver_name}</span>
</div>
)}
</div>
</div>