diff --git a/src/app/utils/formatUtil.ts b/src/app/utils/formatUtil.ts index 2b3708c..60ecdc6 100644 --- a/src/app/utils/formatUtil.ts +++ b/src/app/utils/formatUtil.ts @@ -151,3 +151,45 @@ export const calculateWeight = (qty: number, uom: Uom) => { export const returnWeightUnit = (uom: Uom) => { return uom.unit4 || uom.unit3 || uom.unit2 || uom.unit1; }; + +/** + * Formats departure time to HH:mm format + * Handles array format [hours, minutes] from API and string formats + */ +export const formatDepartureTime = (time: string | number[] | String | Number | null | undefined): string => { + if (!time) return "-"; + + // Handle array format [hours, minutes] from API + if (Array.isArray(time) && time.length >= 2) { + const hours = time[0]; + const minutes = time[1]; + if (typeof hours === 'number' && typeof minutes === 'number' && + hours >= 0 && hours <= 23 && minutes >= 0 && minutes <= 59) { + return `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}`; + } + } + + const timeStr = String(time).trim(); + if (!timeStr || timeStr === "-") return "-"; + + // If already in HH:mm format, return as is + if (/^\d{1,2}:\d{2}$/.test(timeStr)) { + const [hours, minutes] = timeStr.split(":"); + return `${hours.padStart(2, "0")}:${minutes.padStart(2, "0")}`; + } + + return timeStr; +}; + +/** + * Normalizes store ID to display format (2F or 4F) + */ +export const normalizeStoreId = (storeId: string | number | String | Number | null | undefined): string => { + if (!storeId) return "-"; + const storeIdStr = typeof storeId === 'string' || storeId instanceof String + ? String(storeId) + : String(storeId); + if (storeIdStr === "2" || storeIdStr === "2F") return "2F"; + if (storeIdStr === "4" || storeIdStr === "4F") return "4F"; + return storeIdStr; +}; \ No newline at end of file diff --git a/src/components/Shop/Shop.tsx b/src/components/Shop/Shop.tsx index 7c827ab..07b07cb 100644 --- a/src/components/Shop/Shop.tsx +++ b/src/components/Shop/Shop.tsx @@ -18,7 +18,7 @@ import { InputLabel, } from "@mui/material"; import { useState, useMemo, useCallback, useEffect } from "react"; -import { useRouter } from "next/navigation"; +import { useRouter, useSearchParams } from "next/navigation"; import { useTranslation } from "react-i18next"; import SearchBox, { Criterion } from "../SearchBox"; import SearchResults, { Column } from "../SearchResults"; @@ -43,6 +43,7 @@ type SearchParamNames = keyof SearchQuery; const Shop: React.FC = () => { const { t } = useTranslation("common"); const router = useRouter(); + const searchParams = useSearchParams(); const [activeTab, setActiveTab] = useState(0); const [rows, setRows] = useState([]); const [loading, setLoading] = useState(false); @@ -235,26 +236,33 @@ const Shop: React.FC = () => { name: "id", label: t("id"), type: "integer", + sx: { width: "100px", minWidth: "100px", maxWidth: "100px" }, renderCell: (item) => String(item.id ?? ""), }, { name: "code", label: t("Code"), + sx: { width: "150px", minWidth: "150px", maxWidth: "150px" }, renderCell: (item) => String(item.code ?? ""), }, { name: "name", label: t("Name"), + sx: { width: "200px", minWidth: "200px", maxWidth: "200px" }, renderCell: (item) => String(item.name ?? ""), }, { name: "addr3", label: t("Addr3"), + sx: { width: "200px", minWidth: "200px", maxWidth: "200px" }, renderCell: (item) => String((item as any).addr3 ?? ""), }, { name: "truckLanceStatus", label: t("TruckLance Status"), + align: "center", + headerAlign: "center", + sx: { width: "150px", minWidth: "150px", maxWidth: "150px" }, renderCell: (item) => { const status = item.truckLanceStatus; if (status === "complete") { @@ -269,7 +277,9 @@ const Shop: React.FC = () => { { name: "actions", label: t("Actions"), + align: "right", headerAlign: "right", + sx: { width: "150px", minWidth: "150px", maxWidth: "150px" }, renderCell: (item) => ( + ); } @@ -493,7 +439,7 @@ const ShopDetail: React.FC = () => { {t("Shop not found")} - + ); } @@ -504,7 +450,7 @@ const ShopDetail: React.FC = () => { {t("Shop Information")} - + @@ -682,22 +628,13 @@ const ShopDetail: React.FC = () => { ) : ( - (() => { - const storeId = truck.storeId; - if (storeId === null || storeId === undefined) return "-"; - const storeIdStr = typeof storeId === 'string' ? storeId : String(storeId); - // Convert numeric values to display format - if (storeIdStr === "2" || storeIdStr === "2F") return "2F"; - if (storeIdStr === "4" || storeIdStr === "4F") return "4F"; - return storeIdStr; - })() + normalizeStoreId(truck.storeId) )} {isEditing ? ( (() => { - const storeId = displayTruck?.storeId; - const storeIdStr = storeId ? (typeof storeId === 'string' ? storeId : String(storeId) === "2" ? "2F" : String(storeId) === "4" ? "4F" : String(storeId)) : "2F"; + const storeIdStr = normalizeStoreId(displayTruck?.storeId) || "2F"; const isEditable = storeIdStr === "4F"; return ( diff --git a/src/components/Shop/TruckLane.tsx b/src/components/Shop/TruckLane.tsx index 3f3d60b..c1ae0ce 100644 --- a/src/components/Shop/TruckLane.tsx +++ b/src/components/Shop/TruckLane.tsx @@ -36,46 +36,7 @@ import { useTranslation } from "react-i18next"; import { findAllUniqueTruckLaneCombinationsClient, createTruckWithoutShopClient } from "@/app/api/shop/client"; import type { Truck } from "@/app/api/shop/actions"; import SearchBox, { Criterion } from "../SearchBox"; - -// Utility function to format departureTime to HH:mm format -const formatDepartureTime = (time: string | number[] | null | undefined): string => { - if (!time) return "-"; - - // Handle array format [hours, minutes] from API - if (Array.isArray(time) && time.length >= 2) { - const hours = time[0]; - const minutes = time[1]; - if (typeof hours === 'number' && typeof minutes === 'number' && - hours >= 0 && hours <= 23 && minutes >= 0 && minutes <= 59) { - return `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}`; - } - } - - const timeStr = String(time).trim(); - if (!timeStr || timeStr === "-") return "-"; - - // If already in HH:mm format, return as is - if (/^\d{1,2}:\d{2}$/.test(timeStr)) { - const [hours, minutes] = timeStr.split(":"); - return `${hours.padStart(2, "0")}:${minutes.padStart(2, "0")}`; - } - - return timeStr; -}; - -// Utility function to convert HH:mm format to the format expected by backend -const parseDepartureTimeForBackend = (time: string): string => { - if (!time) return ""; - - const timeStr = String(time).trim(); - // If already in HH:mm format, return as is - if (/^\d{1,2}:\d{2}$/.test(timeStr)) { - return timeStr; - } - - // Try to format it - return formatDepartureTime(timeStr); -}; +import { formatDepartureTime, normalizeStoreId } from "@/app/utils/formatUtil"; type SearchQuery = { truckLanceCode: string; @@ -128,39 +89,34 @@ const TruckLane: React.FC = () => { }; fetchTruckLanes(); - }, []); + }, [t]); // Client-side filtered rows (contains-matching) const filteredRows = useMemo(() => { - const fKeys = Object.keys(filters || {}).filter((k) => String((filters as any)[k]).trim() !== ""); - const normalized = (truckData || []).filter((r) => { - // Apply contains matching for each active filter - for (const k of fKeys) { - const v = String((filters as any)[k] ?? "").trim(); + const fKeys = Object.keys(filters).filter((k) => String(filters[k] ?? "").trim() !== ""); + if (fKeys.length === 0) return truckData; + + return truckData.filter((truck) => { + for (const key of fKeys) { + const filterValue = String(filters[key] ?? "").trim().toLowerCase(); - if (k === "truckLanceCode") { - const rv = String((r as any).truckLanceCode ?? "").trim(); - if (!rv.toLowerCase().includes(v.toLowerCase())) return false; - } else if (k === "departureTime") { + if (key === "truckLanceCode") { + const truckCode = String(truck.truckLanceCode ?? "").trim().toLowerCase(); + if (!truckCode.includes(filterValue)) return false; + } else if (key === "departureTime") { const formattedTime = formatDepartureTime( - Array.isArray(r.departureTime) - ? r.departureTime - : (r.departureTime ? String(r.departureTime) : null) + Array.isArray(truck.departureTime) + ? truck.departureTime + : (truck.departureTime ? String(truck.departureTime) : null) ); - if (!formattedTime.toLowerCase().includes(v.toLowerCase())) return false; - } else if (k === "storeId") { - const rv = String((r as any).storeId ?? "").trim(); - const storeIdStr = typeof rv === 'string' ? rv : String(rv); - // Convert numeric values to display format for comparison - let displayStoreId = storeIdStr; - if (storeIdStr === "2" || storeIdStr === "2F") displayStoreId = "2F"; - if (storeIdStr === "4" || storeIdStr === "4F") displayStoreId = "4F"; - if (!displayStoreId.toLowerCase().includes(v.toLowerCase())) return false; + if (!formattedTime.toLowerCase().includes(filterValue)) return false; + } else if (key === "storeId") { + const displayStoreId = normalizeStoreId(truck.storeId); + if (!displayStoreId.toLowerCase().includes(filterValue)) return false; } } return true; }); - return normalized; }, [truckData, filters]); // Paginated rows @@ -235,12 +191,10 @@ const TruckLane: React.FC = () => { setSaving(true); setError(null); try { - const departureTime = parseDepartureTimeForBackend(newTruck.departureTime); - await createTruckWithoutShopClient({ store_id: newTruck.storeId, truckLanceCode: newTruck.truckLanceCode.trim(), - departureTime: departureTime, + departureTime: newTruck.departureTime.trim(), loadingSequence: 0, districtReference: null, remark: null, @@ -249,8 +203,8 @@ const TruckLane: React.FC = () => { // Refresh truck data after create const data = await findAllUniqueTruckLaneCombinationsClient() as Truck[]; const uniqueCodes = new Map(); - (data || []).forEach((truck) => { - const code = String(truck.truckLanceCode || "").trim(); + data.forEach((truck) => { + const code = String(truck.truckLanceCode ?? "").trim(); if (code && !uniqueCodes.has(code)) { uniqueCodes.set(code, truck); } @@ -258,9 +212,10 @@ const TruckLane: React.FC = () => { setTruckData(Array.from(uniqueCodes.values())); handleCloseAddDialog(); - } catch (err: any) { + } catch (err: unknown) { console.error("Failed to create truck:", err); - setError(err?.message ?? String(err) ?? t("Failed to create truck")); + const errorMessage = err instanceof Error ? err.message : String(err); + setError(errorMessage || t("Failed to create truck")); } finally { setSaving(false); } @@ -322,10 +277,18 @@ const TruckLane: React.FC = () => { - {t("TruckLance Code")} - {t("Departure Time")} - {t("Store ID")} - {t("Actions")} + + {t("TruckLance Code")} + + + {t("Departure Time")} + + + {t("Store ID")} + + + {t("Actions")} + @@ -338,40 +301,36 @@ const TruckLane: React.FC = () => { ) : ( - paginatedRows.map((truck, index) => { - const storeId = truck.storeId; - const storeIdStr = storeId ? (typeof storeId === 'string' ? storeId : String(storeId)) : "-"; - const displayStoreId = storeIdStr === "2" || storeIdStr === "2F" ? "2F" - : storeIdStr === "4" || storeIdStr === "4F" ? "4F" - : storeIdStr; - - return ( - - - {String(truck.truckLanceCode || "-")} - - - {formatDepartureTime( - Array.isArray(truck.departureTime) - ? truck.departureTime - : (truck.departureTime ? String(truck.departureTime) : null) - )} - - - {displayStoreId} - - - - - - ); - }) + paginatedRows.map((truck) => ( + + + {String(truck.truckLanceCode ?? "-")} + + + {formatDepartureTime( + Array.isArray(truck.departureTime) + ? truck.departureTime + : (truck.departureTime ? String(truck.departureTime) : null) + )} + + + {normalizeStoreId( + truck.storeId ? (typeof truck.storeId === 'string' || truck.storeId instanceof String + ? String(truck.storeId) + : String(truck.storeId)) : null + )} + + + + + + )) )}
diff --git a/src/components/Shop/TruckLaneDetail.tsx b/src/components/Shop/TruckLaneDetail.tsx index 85659f7..80398d7 100644 --- a/src/components/Shop/TruckLaneDetail.tsx +++ b/src/components/Shop/TruckLaneDetail.tsx @@ -33,34 +33,21 @@ import AddIcon from "@mui/icons-material/Add"; import { useState, useEffect } from "react"; import { useRouter, useSearchParams } from "next/navigation"; import { useTranslation } from "react-i18next"; -import { findAllUniqueTruckLaneCombinationsClient, findAllShopsByTruckLanceCodeClient, deleteTruckLaneClient, updateTruckShopDetailsClient, fetchAllShopsClient, findAllUniqueShopNamesAndCodesFromTrucksClient, findAllUniqueRemarksFromTrucksClient, findAllUniqueShopCodesFromTrucksClient, findAllUniqueShopNamesFromTrucksClient, createTruckClient, findAllByTruckLanceCodeAndDeletedFalseClient } from "@/app/api/shop/client"; +import { + findAllUniqueTruckLaneCombinationsClient, + findAllShopsByTruckLanceCodeClient, + deleteTruckLaneClient, + updateTruckShopDetailsClient, + fetchAllShopsClient, + findAllUniqueShopNamesAndCodesFromTrucksClient, + findAllUniqueRemarksFromTrucksClient, + findAllUniqueShopCodesFromTrucksClient, + findAllUniqueShopNamesFromTrucksClient, + createTruckClient, + findAllByTruckLanceCodeAndDeletedFalseClient, +} from "@/app/api/shop/client"; import type { Truck, ShopAndTruck, Shop } from "@/app/api/shop/actions"; - -// Utility function to format departureTime to HH:mm format -const formatDepartureTime = (time: string | number[] | null | undefined): string => { - if (!time) return "-"; - - // Handle array format [hours, minutes] from API - if (Array.isArray(time) && time.length >= 2) { - const hours = time[0]; - const minutes = time[1]; - if (typeof hours === 'number' && typeof minutes === 'number' && - hours >= 0 && hours <= 23 && minutes >= 0 && minutes <= 59) { - return `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}`; - } - } - - const timeStr = String(time).trim(); - if (!timeStr || timeStr === "-") return "-"; - - // If already in HH:mm format, return as is - if (/^\d{1,2}:\d{2}$/.test(timeStr)) { - const [hours, minutes] = timeStr.split(":"); - return `${hours.padStart(2, "0")}:${minutes.padStart(2, "0")}`; - } - - return timeStr; -}; +import { formatDepartureTime, normalizeStoreId } from "@/app/utils/formatUtil"; const TruckLaneDetail: React.FC = () => { const { t } = useTranslation("common"); @@ -95,59 +82,35 @@ const TruckLaneDetail: React.FC = () => { severity: "success", }); + // Fetch autocomplete data on mount useEffect(() => { - // Fetch unique shop names and codes from truck table - const fetchShopNamesFromTrucks = async () => { + const fetchAutocompleteData = async () => { try { - const shopData = await findAllUniqueShopNamesAndCodesFromTrucksClient() as Array<{ name: string; code: string }>; - + const [shopData, remarks, codes, names] = await Promise.all([ + findAllUniqueShopNamesAndCodesFromTrucksClient() as Promise>, + findAllUniqueRemarksFromTrucksClient() as Promise, + findAllUniqueShopCodesFromTrucksClient() as Promise, + findAllUniqueShopNamesFromTrucksClient() as Promise, + ]); + // Convert to Shop format (id will be 0 since we don't have shop IDs from truck table) const shopList: Shop[] = shopData.map((shop) => ({ - id: 0, // No shop ID available from truck table + id: 0, name: shop.name || "", code: shop.code || "", addr3: "", })); + setAllShops(shopList); - } catch (err: any) { - console.error("Failed to load shop names from trucks:", err); - } - }; - - // Fetch unique remarks from truck table - const fetchRemarksFromTrucks = async () => { - try { - const remarks = await findAllUniqueRemarksFromTrucksClient() as string[]; setUniqueRemarks(remarks || []); - } catch (err: any) { - console.error("Failed to load remarks from trucks:", err); - } - }; - - // Fetch unique shop codes from truck table - const fetchShopCodesFromTrucks = async () => { - try { - const codes = await findAllUniqueShopCodesFromTrucksClient() as string[]; setUniqueShopCodes(codes || []); - } catch (err: any) { - console.error("Failed to load shop codes from trucks:", err); - } - }; - - // Fetch unique shop names from truck table - const fetchShopNamesFromTrucksOnly = async () => { - try { - const names = await findAllUniqueShopNamesFromTrucksClient() as string[]; setUniqueShopNames(names || []); - } catch (err: any) { - console.error("Failed to load shop names from trucks:", err); + } catch (err) { + console.error("Failed to load autocomplete data:", err); } }; - fetchShopNamesFromTrucks(); - fetchRemarksFromTrucks(); - fetchShopCodesFromTrucks(); - fetchShopNamesFromTrucksOnly(); + fetchAutocompleteData(); }, []); useEffect(() => { @@ -409,7 +372,7 @@ const TruckLaneDetail: React.FC = () => { }; const handleBack = () => { - router.push("/settings/shop"); + router.push("/settings/shop?tab=1"); }; const handleOpenAddShopDialog = () => { @@ -515,11 +478,10 @@ const TruckLaneDetail: React.FC = () => { setError(null); try { // Get storeId from truckData - const storeId = truckData.storeId; - const storeIdStr = storeId ? (typeof storeId === 'string' ? storeId : String(storeId)) : "2F"; - const displayStoreId = storeIdStr === "2" || storeIdStr === "2F" ? "2F" - : storeIdStr === "4" || storeIdStr === "4F" ? "4F" - : storeIdStr; + const storeIdValue = truckData.storeId ? (typeof truckData.storeId === 'string' || truckData.storeId instanceof String + ? String(truckData.storeId) + : String(truckData.storeId)) : "2F"; + const displayStoreId = normalizeStoreId(storeIdValue) || "2F"; // Get departureTime from truckData let departureTimeStr = ""; @@ -648,11 +610,11 @@ const TruckLaneDetail: React.FC = () => { ); } - const storeId = truckData.storeId; - const storeIdStr = storeId ? (typeof storeId === 'string' ? storeId : String(storeId)) : "-"; - const displayStoreId = storeIdStr === "2" || storeIdStr === "2F" ? "2F" - : storeIdStr === "4" || storeIdStr === "4F" ? "4F" - : storeIdStr; + const displayStoreId = normalizeStoreId( + truckData.storeId ? (typeof truckData.storeId === 'string' || truckData.storeId instanceof String + ? String(truckData.storeId) + : String(truckData.storeId)) : null + ); return ( @@ -854,9 +816,10 @@ const TruckLaneDetail: React.FC = () => { {editingRowIndex === index ? ( (() => { - const storeId = truckData.storeId; - const storeIdStr = storeId ? (typeof storeId === 'string' ? storeId : String(storeId)) : ""; - const isEditable = storeIdStr === "4F" || storeIdStr === "4"; + const storeIdValue = truckData.storeId ? (typeof truckData.storeId === 'string' || truckData.storeId instanceof String + ? String(truckData.storeId) + : String(truckData.storeId)) : null; + const isEditable = normalizeStoreId(storeIdValue) === "4F"; return ( { /> {(() => { - const storeId = truckData?.storeId; - const storeIdStr = storeId ? (typeof storeId === 'string' ? storeId : String(storeId)) : ""; - const isEditable = storeIdStr === "4F" || storeIdStr === "4"; + const storeIdValue = truckData?.storeId ? (typeof truckData.storeId === 'string' || truckData.storeId instanceof String + ? String(truckData.storeId) + : String(truckData.storeId)) : null; + const isEditable = normalizeStoreId(storeIdValue) === "4F"; return isEditable ? ( { ); }; -export default TruckLaneDetail; - - +export default TruckLaneDetail; \ No newline at end of file