first commit
This commit is contained in:
119
resources/js/hooks/usePurchaseOrderForm.ts
Normal file
119
resources/js/hooks/usePurchaseOrderForm.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* 採購單表單管理 Hook
|
||||
*/
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import type { PurchaseOrder, PurchaseOrderItem, Supplier, PurchaseOrderStatus } from "@/types/purchase-order";
|
||||
import { calculateSubtotal } from "@/utils/purchase-order";
|
||||
|
||||
interface UsePurchaseOrderFormProps {
|
||||
order?: PurchaseOrder;
|
||||
suppliers: Supplier[];
|
||||
}
|
||||
|
||||
export function usePurchaseOrderForm({ order, suppliers }: UsePurchaseOrderFormProps) {
|
||||
const [supplierId, setSupplierId] = useState("");
|
||||
const [expectedDate, setExpectedDate] = useState("");
|
||||
const [items, setItems] = useState<PurchaseOrderItem[]>([]);
|
||||
const [notes, setNotes] = useState("");
|
||||
const [status, setStatus] = useState<PurchaseOrderStatus>("draft");
|
||||
const [warehouseId, setWarehouseId] = useState<string | number>("");
|
||||
|
||||
// 載入編輯訂單資料
|
||||
useEffect(() => {
|
||||
if (order) {
|
||||
setSupplierId(order.supplierId);
|
||||
setExpectedDate(order.expectedDate);
|
||||
setItems(order.items);
|
||||
setNotes(order.remark || "");
|
||||
setStatus(order.status);
|
||||
setWarehouseId(order.warehouse_id || "");
|
||||
}
|
||||
}, [order]);
|
||||
|
||||
const resetForm = () => {
|
||||
setSupplierId("");
|
||||
setExpectedDate("");
|
||||
setItems([]);
|
||||
setNotes("");
|
||||
setStatus("draft");
|
||||
setWarehouseId("");
|
||||
};
|
||||
|
||||
const selectedSupplier = suppliers.find((s) => String(s.id) === String(supplierId));
|
||||
const isOrderSent = order && order.status !== "draft";
|
||||
|
||||
// 新增商品項目
|
||||
const addItem = () => {
|
||||
if (!selectedSupplier) return;
|
||||
|
||||
setItems([
|
||||
...items,
|
||||
{
|
||||
productId: "",
|
||||
productName: "",
|
||||
quantity: 0,
|
||||
unit: "",
|
||||
unitPrice: 0,
|
||||
subtotal: 0,
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
// 移除商品項目
|
||||
const removeItem = (index: number) => {
|
||||
setItems(items.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
// 更新商品項目
|
||||
const updateItem = (index: number, field: keyof PurchaseOrderItem, value: string | number) => {
|
||||
const newItems = [...items];
|
||||
newItems[index] = { ...newItems[index], [field]: value };
|
||||
|
||||
// 當選擇商品時,自動填入商品資訊
|
||||
if (field === "productId" && selectedSupplier) {
|
||||
const product = selectedSupplier.commonProducts.find((p) => p.productId === value);
|
||||
if (product) {
|
||||
newItems[index].productName = product.productName;
|
||||
newItems[index].unit = product.unit;
|
||||
newItems[index].unitPrice = product.lastPrice;
|
||||
newItems[index].previousPrice = product.lastPrice;
|
||||
}
|
||||
}
|
||||
|
||||
// 計算小計
|
||||
if (field === "quantity" || field === "unitPrice") {
|
||||
newItems[index].subtotal = calculateSubtotal(
|
||||
Number(newItems[index].quantity),
|
||||
Number(newItems[index].unitPrice)
|
||||
);
|
||||
}
|
||||
|
||||
setItems(newItems);
|
||||
};
|
||||
|
||||
return {
|
||||
// State
|
||||
supplierId,
|
||||
expectedDate,
|
||||
items,
|
||||
notes,
|
||||
status,
|
||||
selectedSupplier,
|
||||
isOrderSent,
|
||||
warehouseId,
|
||||
|
||||
// Setters
|
||||
setSupplierId,
|
||||
setExpectedDate,
|
||||
setNotes,
|
||||
setStatus,
|
||||
setWarehouseId,
|
||||
|
||||
// Methods
|
||||
addItem,
|
||||
removeItem,
|
||||
updateItem,
|
||||
resetForm,
|
||||
};
|
||||
}
|
||||
105
resources/js/hooks/useVendors.ts
Normal file
105
resources/js/hooks/useVendors.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* 廠商管理相關的業務邏輯 Hook
|
||||
*/
|
||||
|
||||
import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import type { Supplier, SupplyProduct } from "../types/vendor";
|
||||
import type { Product } from "../types/product";
|
||||
|
||||
export function useVendors(initialSuppliers: Supplier[], allProducts: Product[]) {
|
||||
const [suppliers, setSuppliers] = useState<Supplier[]>(initialSuppliers);
|
||||
|
||||
const addSupplier = (supplier: Omit<Supplier, "id">) => {
|
||||
const newSupplier: Supplier = {
|
||||
...supplier,
|
||||
id: `sup-${Date.now()}`,
|
||||
commonProducts: [],
|
||||
supplyProducts: [],
|
||||
};
|
||||
setSuppliers((prev) => [...prev, newSupplier]);
|
||||
toast.success("廠商已新增");
|
||||
};
|
||||
|
||||
const updateSupplier = (id: string, updatedSupplier: Omit<Supplier, "id">) => {
|
||||
setSuppliers((prev) =>
|
||||
prev.map((s) => (s.id === id ? { ...s, ...updatedSupplier } : s))
|
||||
);
|
||||
toast.success("廠商資料已更新");
|
||||
};
|
||||
|
||||
const deleteSupplier = (id: string) => {
|
||||
setSuppliers((prev) => prev.filter((s) => s.id !== id));
|
||||
toast.success("廠商已刪除");
|
||||
};
|
||||
|
||||
// 新增供貨商品
|
||||
const addSupplyProduct = (supplierId: string, productId: string, lastPrice?: number) => {
|
||||
const product = allProducts.find(p => p.id === productId);
|
||||
if (!product) return;
|
||||
|
||||
setSuppliers((prev) =>
|
||||
prev.map((s) => {
|
||||
if (s.id === supplierId) {
|
||||
const newSupplyProduct: SupplyProduct = {
|
||||
id: `sp-${Date.now()}`,
|
||||
productId: product.id,
|
||||
productName: product.name,
|
||||
unit: product.unit,
|
||||
lastPrice,
|
||||
};
|
||||
return {
|
||||
...s,
|
||||
supplyProducts: [...s.supplyProducts, newSupplyProduct],
|
||||
};
|
||||
}
|
||||
return s;
|
||||
})
|
||||
);
|
||||
toast.success("供貨商品已新增");
|
||||
};
|
||||
|
||||
// 更新供貨商品
|
||||
const updateSupplyProduct = (supplierId: string, productId: string, lastPrice?: number) => {
|
||||
setSuppliers((prev) =>
|
||||
prev.map((s) => {
|
||||
if (s.id === supplierId) {
|
||||
return {
|
||||
...s,
|
||||
supplyProducts: s.supplyProducts.map((sp) =>
|
||||
sp.productId === productId ? { ...sp, lastPrice } : sp
|
||||
),
|
||||
};
|
||||
}
|
||||
return s;
|
||||
})
|
||||
);
|
||||
toast.success("供貨商品已更新");
|
||||
};
|
||||
|
||||
// 移除供貨商品
|
||||
const removeSupplyProduct = (supplierId: string, productId: string) => {
|
||||
setSuppliers((prev) =>
|
||||
prev.map((s) => {
|
||||
if (s.id === supplierId) {
|
||||
return {
|
||||
...s,
|
||||
supplyProducts: s.supplyProducts.filter((sp) => sp.productId !== productId),
|
||||
};
|
||||
}
|
||||
return s;
|
||||
})
|
||||
);
|
||||
toast.success("供貨商品已移除");
|
||||
};
|
||||
|
||||
return {
|
||||
suppliers,
|
||||
addSupplier,
|
||||
updateSupplier,
|
||||
deleteSupplier,
|
||||
addSupplyProduct,
|
||||
updateSupplyProduct,
|
||||
removeSupplyProduct,
|
||||
};
|
||||
}
|
||||
23
resources/js/hooks/useWarehouseFilter.ts
Normal file
23
resources/js/hooks/useWarehouseFilter.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 倉庫篩選 Hook
|
||||
* 處理倉庫的搜尋和類別篩選邏輯
|
||||
*/
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { Warehouse } from "../types/warehouse";
|
||||
|
||||
export const useWarehouseFilter = (
|
||||
warehouses: Warehouse[],
|
||||
searchTerm: string,
|
||||
typeFilter: string
|
||||
) => {
|
||||
return useMemo(() => {
|
||||
return warehouses.filter((warehouse) => {
|
||||
const matchesSearch = warehouse.name
|
||||
.toLowerCase()
|
||||
.includes(searchTerm.toLowerCase());
|
||||
const matchesType = typeFilter === "all" || warehouse.type === typeFilter;
|
||||
return matchesSearch && matchesType;
|
||||
});
|
||||
}, [warehouses, searchTerm, typeFilter]);
|
||||
};
|
||||
Reference in New Issue
Block a user