From 8ea1ce1515f91fa871e6592863ae4973ce889d9f Mon Sep 17 00:00:00 2001 From: sky121113 Date: Wed, 7 Jan 2026 13:06:49 +0800 Subject: [PATCH] =?UTF-8?q?=E9=BA=B5=E5=8C=85=E5=B1=91=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/js/Layouts/AuthenticatedLayout.tsx | 14 ++- resources/js/Pages/Product/Index.tsx | 12 +-- resources/js/Pages/PurchaseOrder/Create.tsx | 4 +- resources/js/Pages/PurchaseOrder/Index.tsx | 3 +- resources/js/Pages/PurchaseOrder/Show.tsx | 3 +- resources/js/Pages/Vendor/Index.tsx | 3 +- resources/js/Pages/Vendor/Show.tsx | 3 +- resources/js/Pages/Warehouse/AddInventory.tsx | 3 +- .../js/Pages/Warehouse/EditInventory.tsx | 3 +- resources/js/Pages/Warehouse/Index.tsx | 3 +- resources/js/Pages/Warehouse/Inventory.tsx | 3 +- .../js/Pages/Warehouse/InventoryHistory.tsx | 3 +- .../Pages/Warehouse/SafetyStockSettings.tsx | 3 +- resources/js/utils/breadcrumb.ts | 100 ++++++++++++++++++ 14 files changed, 137 insertions(+), 23 deletions(-) create mode 100644 resources/js/utils/breadcrumb.ts diff --git a/resources/js/Layouts/AuthenticatedLayout.tsx b/resources/js/Layouts/AuthenticatedLayout.tsx index 28989bd..f9e2c66 100644 --- a/resources/js/Layouts/AuthenticatedLayout.tsx +++ b/resources/js/Layouts/AuthenticatedLayout.tsx @@ -17,6 +17,7 @@ import { Toaster } from "sonner"; import { useState, useEffect } from "react"; import { Link, usePage } from "@inertiajs/react"; import { cn } from "@/lib/utils"; +import BreadcrumbNav, { BreadcrumbItemType } from "@/Components/shared/BreadcrumbNav"; interface MenuItem { id: string; @@ -26,7 +27,13 @@ interface MenuItem { children?: MenuItem[]; } -export default function AuthenticatedLayout({ children }: { children: React.ReactNode }) { +export default function AuthenticatedLayout({ + children, + breadcrumbs +}: { + children: React.ReactNode, + breadcrumbs?: BreadcrumbItemType[] +}) { const { url } = usePage(); const [isCollapsed, setIsCollapsed] = useState(() => { if (typeof window !== "undefined") { @@ -313,6 +320,11 @@ export default function AuthenticatedLayout({ children }: { children: React.Reac "pt-16" // Always allow space for header )}>
+
+ {breadcrumbs && breadcrumbs.length > 1 && ( + + )} +
{children}
diff --git a/resources/js/Pages/Product/Index.tsx b/resources/js/Pages/Product/Index.tsx index 257c8c2..e93c090 100644 --- a/resources/js/Pages/Product/Index.tsx +++ b/resources/js/Pages/Product/Index.tsx @@ -16,7 +16,7 @@ import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout"; import { Head, router } from "@inertiajs/react"; import { debounce } from "lodash"; import Pagination from "@/Components/shared/Pagination"; -import BreadcrumbNav from "@/Components/shared/BreadcrumbNav"; +import { getBreadcrumbs } from "@/utils/breadcrumb"; export interface Category { id: number; @@ -173,19 +173,11 @@ export default function ProductManagement({ products, categories, filters }: Pag }; return ( - +
{/* Header */}
-

商品資料管理

管理小小冰室原物料與成品資料

diff --git a/resources/js/Pages/PurchaseOrder/Create.tsx b/resources/js/Pages/PurchaseOrder/Create.tsx index 770475f..7e4f0f4 100644 --- a/resources/js/Pages/PurchaseOrder/Create.tsx +++ b/resources/js/Pages/PurchaseOrder/Create.tsx @@ -21,7 +21,6 @@ import type { PurchaseOrder, Supplier } from "@/types/purchase-order"; import type { Warehouse } from "@/types/requester"; import { usePurchaseOrderForm } from "@/hooks/usePurchaseOrderForm"; import { - validatePurchaseOrder, filterValidItems, calculateTotalAmount, getTodayDate, @@ -29,6 +28,7 @@ import { } from "@/utils/purchase-order"; import { STATUS_OPTIONS } from "@/constants/purchase-order"; import { toast } from "sonner"; +import { getCreateBreadcrumbs, getEditBreadcrumbs } from "@/utils/breadcrumb"; interface Props { order?: PurchaseOrder; @@ -152,7 +152,7 @@ export default function CreatePurchaseOrder({ const hasSupplier = !!supplierId; return ( - +
{/* Header */} diff --git a/resources/js/Pages/PurchaseOrder/Index.tsx b/resources/js/Pages/PurchaseOrder/Index.tsx index 7aa0425..c07be75 100644 --- a/resources/js/Pages/PurchaseOrder/Index.tsx +++ b/resources/js/Pages/PurchaseOrder/Index.tsx @@ -13,6 +13,7 @@ import { type DateRange } from "@/Components/PurchaseOrder/DateFilter"; import type { PurchaseOrder } from "@/types/purchase-order"; import { debounce } from "lodash"; import Pagination from "@/Components/shared/Pagination"; +import { getBreadcrumbs } from "@/utils/breadcrumb"; interface Props { orders: { @@ -86,7 +87,7 @@ export default function PurchaseOrderIndex({ orders, filters, warehouses }: Prop }; return ( - +
diff --git a/resources/js/Pages/PurchaseOrder/Show.tsx b/resources/js/Pages/PurchaseOrder/Show.tsx index 02688fd..fa67e60 100644 --- a/resources/js/Pages/PurchaseOrder/Show.tsx +++ b/resources/js/Pages/PurchaseOrder/Show.tsx @@ -11,6 +11,7 @@ import PurchaseOrderStatusBadge from "@/Components/PurchaseOrder/PurchaseOrderSt import CopyButton from "@/Components/shared/CopyButton"; import type { PurchaseOrder } from "@/types/purchase-order"; import { formatCurrency, formatDateTime } from "@/utils/format"; +import { getShowBreadcrumbs } from "@/utils/breadcrumb"; interface Props { order: PurchaseOrder; @@ -18,7 +19,7 @@ interface Props { export default function ViewPurchaseOrderPage({ order }: Props) { return ( - +
{/* Header */} diff --git a/resources/js/Pages/Vendor/Index.tsx b/resources/js/Pages/Vendor/Index.tsx index 836edaf..e2c0544 100644 --- a/resources/js/Pages/Vendor/Index.tsx +++ b/resources/js/Pages/Vendor/Index.tsx @@ -7,6 +7,7 @@ import VendorDialog from "@/Components/Vendor/VendorDialog"; import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout"; import { Head, router } from "@inertiajs/react"; import { debounce } from "lodash"; +import { getBreadcrumbs } from "@/utils/breadcrumb"; export interface Vendor { id: number; @@ -124,7 +125,7 @@ export default function VendorManagement({ vendors, filters }: PageProps) { }; return ( - +
{/* Header */} diff --git a/resources/js/Pages/Vendor/Show.tsx b/resources/js/Pages/Vendor/Show.tsx index ebfa2b6..90839eb 100644 --- a/resources/js/Pages/Vendor/Show.tsx +++ b/resources/js/Pages/Vendor/Show.tsx @@ -23,6 +23,7 @@ import AddSupplyProductDialog from "@/Components/Vendor/AddSupplyProductDialog"; import EditSupplyProductDialog from "@/Components/Vendor/EditSupplyProductDialog"; import type { Vendor } from "@/Pages/Vendor/Index"; import type { SupplyProduct } from "@/types/vendor"; +import { getShowBreadcrumbs } from "@/utils/breadcrumb"; interface Pivot { last_price: number | null; @@ -103,7 +104,7 @@ export default function VendorShow({ vendor, products }: ShowProps) { }; return ( - +
{/* 返回按鈕 */} diff --git a/resources/js/Pages/Warehouse/AddInventory.tsx b/resources/js/Pages/Warehouse/AddInventory.tsx index 88cc7db..16318be 100644 --- a/resources/js/Pages/Warehouse/AddInventory.tsx +++ b/resources/js/Pages/Warehouse/AddInventory.tsx @@ -28,6 +28,7 @@ import { Head, Link, router } from "@inertiajs/react"; import { Warehouse, InboundItem, InboundReason } from "@/types/warehouse"; import { getCurrentDateTime } from "@/utils/format"; import { toast } from "sonner"; +import { getInventoryBreadcrumbs } from "@/utils/breadcrumb"; interface Product { id: string; @@ -151,7 +152,7 @@ export default function AddInventoryPage({ warehouse, products }: Props) { }; return ( - +
{/* 頁面標題與導航 - 已於先前任務優化 */} diff --git a/resources/js/Pages/Warehouse/EditInventory.tsx b/resources/js/Pages/Warehouse/EditInventory.tsx index 0621d6a..090a886 100644 --- a/resources/js/Pages/Warehouse/EditInventory.tsx +++ b/resources/js/Pages/Warehouse/EditInventory.tsx @@ -19,6 +19,7 @@ import { AlertDialogTitle, } from "@/Components/ui/alert-dialog"; import TransactionTable, { Transaction } from "@/Components/Warehouse/Inventory/TransactionTable"; +import { getShowBreadcrumbs } from "@/utils/breadcrumb"; interface Props { @@ -72,7 +73,7 @@ export default function EditInventory({ warehouse, inventory, transactions = [] }; return ( - +
{/* 頁面標題與麵包屑 */} diff --git a/resources/js/Pages/Warehouse/Index.tsx b/resources/js/Pages/Warehouse/Index.tsx index b545e29..0883c0d 100644 --- a/resources/js/Pages/Warehouse/Index.tsx +++ b/resources/js/Pages/Warehouse/Index.tsx @@ -11,6 +11,7 @@ import WarehouseEmptyState from "@/Components/Warehouse/WarehouseEmptyState"; import { Warehouse } from "@/types/warehouse"; import Pagination from "@/Components/shared/Pagination"; import { toast } from "sonner"; +import { getBreadcrumbs } from "@/utils/breadcrumb"; interface PageProps { warehouses: { @@ -100,7 +101,7 @@ export default function WarehouseIndex({ warehouses, filters }: PageProps) { }; return ( - +
{/* 頁面標題 */} diff --git a/resources/js/Pages/Warehouse/Inventory.tsx b/resources/js/Pages/Warehouse/Inventory.tsx index 8c5d8cb..d436e8a 100644 --- a/resources/js/Pages/Warehouse/Inventory.tsx +++ b/resources/js/Pages/Warehouse/Inventory.tsx @@ -8,6 +8,7 @@ import InventoryToolbar from "@/Components/Warehouse/Inventory/InventoryToolbar" import InventoryTable from "@/Components/Warehouse/Inventory/InventoryTable"; import { calculateLowStockCount } from "@/utils/inventory"; import { toast } from "sonner"; +import { getInventoryBreadcrumbs } from "@/utils/breadcrumb"; import { AlertDialog, AlertDialogAction, @@ -85,7 +86,7 @@ export default function WarehouseInventoryPage({ }; return ( - +
{/* 頁面標題與導航 */} diff --git a/resources/js/Pages/Warehouse/InventoryHistory.tsx b/resources/js/Pages/Warehouse/InventoryHistory.tsx index d908c66..7be85af 100644 --- a/resources/js/Pages/Warehouse/InventoryHistory.tsx +++ b/resources/js/Pages/Warehouse/InventoryHistory.tsx @@ -4,6 +4,7 @@ import { Button } from "@/Components/ui/button"; import { ArrowLeft } from "lucide-react"; import { Warehouse } from "@/types/warehouse"; import TransactionTable, { Transaction } from "@/Components/Warehouse/Inventory/TransactionTable"; +import { getInventoryBreadcrumbs } from "@/utils/breadcrumb"; interface Props { warehouse: Warehouse; @@ -18,7 +19,7 @@ interface Props { export default function InventoryHistory({ warehouse, inventory, transactions }: Props) { return ( - +
{/* Header */} diff --git a/resources/js/Pages/Warehouse/SafetyStockSettings.tsx b/resources/js/Pages/Warehouse/SafetyStockSettings.tsx index 76fcf09..d603a4d 100644 --- a/resources/js/Pages/Warehouse/SafetyStockSettings.tsx +++ b/resources/js/Pages/Warehouse/SafetyStockSettings.tsx @@ -13,6 +13,7 @@ import SafetyStockList from "@/Components/Warehouse/SafetyStock/SafetyStockList" import AddSafetyStockDialog from "@/Components/Warehouse/SafetyStock/AddSafetyStockDialog"; import EditSafetyStockDialog from "@/Components/Warehouse/SafetyStock/EditSafetyStockDialog"; import { toast } from "sonner"; +import { getInventoryBreadcrumbs } from "@/utils/breadcrumb"; interface Props { warehouse: Warehouse; @@ -83,7 +84,7 @@ export default function SafetyStockPage({ } return ( - +
{/* 頁面標題與導航 */} diff --git a/resources/js/utils/breadcrumb.ts b/resources/js/utils/breadcrumb.ts new file mode 100644 index 0000000..f7edd25 --- /dev/null +++ b/resources/js/utils/breadcrumb.ts @@ -0,0 +1,100 @@ +import { BreadcrumbItemType } from "@/Components/shared/BreadcrumbNav"; + +/** + * 麵包屑定義對應表 + * 根據側邊欄層級結構定義基礎麵包屑 + */ +export const BREADCRUMB_MAP: Record = { + dashboard: [ + { label: "首頁", isPage: true } + ], + products: [ + { label: "首頁", href: "/" }, + { label: "商品與庫存管理" }, + { label: "商品資料管理", isPage: true } + ], + warehouses: [ + { label: "首頁", href: "/" }, + { label: "商品與庫存管理" }, + { label: "倉庫管理", isPage: true } + ], + vendors: [ + { label: "首頁", href: "/" }, + { label: "廠商管理" }, + { label: "廠商資料管理", isPage: true } + ], + purchaseOrders: [ + { label: "首頁", href: "/" }, + { label: "採購管理" }, + { label: "管理採購單", isPage: true } + ], +}; + +/** + * 組合麵包屑工具 + * @param base 基礎路徑名稱 (key of BREADCRUMB_MAP) + * @param extra 額外的路徑項目 (例如編輯、詳情頁) + */ +export function getBreadcrumbs(base: keyof typeof BREADCRUMB_MAP, extra?: BreadcrumbItemType[]): BreadcrumbItemType[] { + const baseItems = JSON.parse(JSON.stringify(BREADCRUMB_MAP[base] || [])); + + if (extra && extra.length > 0) { + // 如果有額外路徑,基礎路徑的最後一項不應標記為 isPage + if (baseItems.length > 0) { + baseItems[baseItems.length - 1].isPage = false; + } + return [...baseItems, ...extra]; + } + + return baseItems; +} + +/** + * 取得「新增」操作的麵包屑 + */ +export function getCreateBreadcrumbs(base: keyof typeof BREADCRUMB_MAP): BreadcrumbItemType[] { + return getBreadcrumbs(base, [{ label: "新增", isPage: true }]); +} + +/** + * 取得「編輯」操作的麵包屑 + */ +export function getEditBreadcrumbs(base: keyof typeof BREADCRUMB_MAP): BreadcrumbItemType[] { + return getBreadcrumbs(base, [{ label: "編輯", isPage: true }]); +} + +/** + * 取得「詳情」操作的麵包屑 + */ +export function getShowBreadcrumbs(base: keyof typeof BREADCRUMB_MAP, suffix: string = "詳情"): BreadcrumbItemType[] { + return getBreadcrumbs(base, [{ label: suffix, isPage: true }]); +} + +/** + * 取得「庫存管理」子頁面的麵包屑 + * 層級:倉庫管理 > 庫存詳情 (倉庫名) > [功能] + */ +export function getInventoryBreadcrumbs(warehouseId: string | number, warehouseName: string, subPageLabel?: string): BreadcrumbItemType[] { + const baseItems: BreadcrumbItemType[] = [ + ...JSON.parse(JSON.stringify(BREADCRUMB_MAP.warehouses || [])) + ]; + + // 修改「倉庫管理」不作為最後一頁 + if (baseItems.length > 0) { + baseItems[baseItems.length - 1].isPage = false; + } + + const inventoryDetailItem: BreadcrumbItemType = { + label: `庫存管理 (${warehouseName})`, + href: `/warehouses/${warehouseId}/inventory`, + isPage: !subPageLabel + }; + + const finalItems = [...baseItems, inventoryDetailItem]; + + if (subPageLabel) { + finalItems.push({ label: subPageLabel, isPage: true }); + } + + return finalItems; +}