Quellcode durchsuchen

Fix tab issue

master
Tommy\2Fi-Staff vor 1 Monat
Ursprung
Commit
8f75d90d87
5 geänderte Dateien mit 270 neuen und 332 gelöschten Zeilen
  1. +42
    -0
      src/app/utils/formatUtil.ts
  2. +107
    -69
      src/components/Shop/Shop.tsx
  3. +7
    -70
      src/components/Shop/ShopDetail.tsx
  4. +67
    -108
      src/components/Shop/TruckLane.tsx
  5. +47
    -85
      src/components/Shop/TruckLaneDetail.tsx

+ 42
- 0
src/app/utils/formatUtil.ts Datei anzeigen

@@ -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;
};

+ 107
- 69
src/components/Shop/Shop.tsx Datei anzeigen

@@ -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<number>(0);
const [rows, setRows] = useState<ShopRow[]>([]);
const [loading, setLoading] = useState<boolean>(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) => (
<Button
size="small"
@@ -282,6 +292,17 @@ const Shop: React.FC = () => {
},
];

// Initialize activeTab from URL parameter
useEffect(() => {
const tabParam = searchParams.get("tab");
if (tabParam !== null) {
const tabIndex = parseInt(tabParam, 10);
if (!isNaN(tabIndex) && (tabIndex === 0 || tabIndex === 1)) {
setActiveTab(tabIndex);
}
}
}, [searchParams]);

useEffect(() => {
if (activeTab === 0) {
fetchAllShops();
@@ -290,82 +311,99 @@ const Shop: React.FC = () => {

const handleTabChange = (event: React.SyntheticEvent, newValue: number) => {
setActiveTab(newValue);
// Update URL to reflect the selected tab
const url = new URL(window.location.href);
url.searchParams.set("tab", String(newValue));
router.push(url.pathname + url.search);
};

return (
<Box>
<Card sx={{ mb: 2 }}>
<CardContent>
<Tabs
value={activeTab}
onChange={handleTabChange}
sx={{
mb: 3,
borderBottom: 1,
borderColor: 'divider'
}}
>
<Tab label={t("Shop")} />
<Tab label={t("Truck Lane")} />
</Tabs>
{/* Header section with title */}
<Box sx={{
p: 2,
borderBottom: '1px solid #e0e0e0'
}}>
<Typography variant="h4">
店鋪路線管理
</Typography>
</Box>

{/* Tabs section */}
<Box sx={{
borderBottom: '1px solid #e0e0e0'
}}>
<Tabs
value={activeTab}
onChange={handleTabChange}
>
<Tab label={t("Shop")} />
<Tab label={t("Truck Lane")} />
</Tabs>
</Box>

{activeTab === 0 && (
<SearchBox
criteria={criteria as Criterion<string>[]}
onSearch={handleSearch}
onReset={() => {
setRows([]);
setFilters({});
}}
/>
)}
</CardContent>
</Card>
{/* Content section */}
<Box sx={{ p: 2 }}>
{activeTab === 0 && (
<>
<Card sx={{ mb: 2 }}>
<CardContent>
<SearchBox
criteria={criteria as Criterion<string>[]}
onSearch={handleSearch}
onReset={() => {
setRows([]);
setFilters({});
}}
/>
</CardContent>
</Card>

{activeTab === 0 && (
<Card>
<CardContent>
<Stack direction="row" justifyContent="space-between" alignItems="center" sx={{ mb: 2 }}>
<Typography variant="h6">{t("Shop")}</Typography>
<FormControl size="small" sx={{ minWidth: 200 }}>
<InputLabel>{t("Filter by Status")}</InputLabel>
<Select
value={statusFilter}
label={t("Filter by Status")}
onChange={(e) => setStatusFilter(e.target.value)}
>
<MenuItem value="all">{t("All")}</MenuItem>
<MenuItem value="complete">{t("Complete")}</MenuItem>
<MenuItem value="missing">{t("Missing Data")}</MenuItem>
<MenuItem value="no-truck">{t("No TruckLance")}</MenuItem>
</Select>
</FormControl>
</Stack>
{error && (
<Alert severity="error" sx={{ mb: 2 }}>
{error}
</Alert>
)}
<Card>
<CardContent>
<Stack direction="row" justifyContent="space-between" alignItems="center" sx={{ mb: 2 }}>
<Typography variant="h6">{t("Shop")}</Typography>
<FormControl size="small" sx={{ minWidth: 200 }}>
<InputLabel>{t("Filter by Status")}</InputLabel>
<Select
value={statusFilter}
label={t("Filter by Status")}
onChange={(e) => setStatusFilter(e.target.value)}
>
<MenuItem value="all">{t("All")}</MenuItem>
<MenuItem value="complete">{t("Complete")}</MenuItem>
<MenuItem value="missing">{t("Missing Data")}</MenuItem>
<MenuItem value="no-truck">{t("No TruckLance")}</MenuItem>
</Select>
</FormControl>
</Stack>
{error && (
<Alert severity="error" sx={{ mb: 2 }}>
{error}
</Alert>
)}

{loading ? (
<Box sx={{ display: "flex", justifyContent: "center", p: 4 }}>
<CircularProgress />
</Box>
) : (
<SearchResults
items={filteredRows}
columns={columns}
pagingController={pagingController}
setPagingController={setPagingController}
/>
)}
</CardContent>
</Card>
)}
{loading ? (
<Box sx={{ display: "flex", justifyContent: "center", p: 4 }}>
<CircularProgress />
</Box>
) : (
<SearchResults
items={filteredRows}
columns={columns}
pagingController={pagingController}
setPagingController={setPagingController}
/>
)}
</CardContent>
</Card>
</>
)}

{activeTab === 1 && (
<TruckLane />
)}
{activeTab === 1 && (
<TruckLane />
)}
</Box>
</Box>
);
};


+ 7
- 70
src/components/Shop/ShopDetail.tsx Datei anzeigen

@@ -48,6 +48,7 @@ import {
createTruckClient
} from "@/app/api/shop/client";
import type { SessionWithTokens } from "@/config/authConfig";
import { formatDepartureTime, normalizeStoreId } from "@/app/utils/formatUtil";

type ShopDetailData = {
id: number;
@@ -62,61 +63,6 @@ type ShopDetailData = {
contactName: String;
};

// 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")}`;
}
// Handle decimal format (e.g., "17,0" or "17.0" representing hours)
const decimalMatch = timeStr.match(/^(\d+)[,.](\d+)$/);
if (decimalMatch) {
const hours = parseInt(decimalMatch[1], 10);
const minutes = Math.round(parseFloat(`0.${decimalMatch[2]}`) * 60);
return `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}`;
}
// Handle single number as hours (e.g., "17" -> "17:00")
const hoursOnly = parseInt(timeStr, 10);
if (!isNaN(hoursOnly) && hoursOnly >= 0 && hoursOnly <= 23) {
return `${hoursOnly.toString().padStart(2, "0")}:00`;
}
// Try to parse as ISO time string or other formats
try {
// If it's already a valid time string, try to extract hours and minutes
const parts = timeStr.split(/[:,\s]/);
if (parts.length >= 2) {
const h = parseInt(parts[0], 10);
const m = parseInt(parts[1], 10);
if (!isNaN(h) && !isNaN(m) && h >= 0 && h <= 23 && m >= 0 && m <= 59) {
return `${h.toString().padStart(2, "0")}:${m.toString().padStart(2, "0")}`;
}
}
} catch (e) {
// If parsing fails, return original string
}
return timeStr;
};

// Utility function to convert HH:mm format to the format expected by backend
const parseDepartureTimeForBackend = (time: string): string => {
if (!time) return "";
@@ -299,7 +245,7 @@ const ShopDetail: React.FC = () => {
}
// Convert storeId to string format (2F or 4F)
const storeIdStr = truck.storeId ? (typeof truck.storeId === 'string' ? truck.storeId : String(truck.storeId) === "2" ? "2F" : String(truck.storeId) === "4" ? "4F" : String(truck.storeId)) : "2F";
const storeIdStr = normalizeStoreId(truck.storeId) || "2F";
// Get remark value - use the remark from editedTruckData (user input)
// Only send remark if storeId is "4F", otherwise send null
@@ -482,7 +428,7 @@ const ShopDetail: React.FC = () => {
<Alert severity="error" sx={{ mb: 2 }}>
{error}
</Alert>
<Button onClick={() => router.back()}>{t("Back")}</Button>
<Button onClick={() => router.push("/settings/shop?tab=0")}>{t("Back")}</Button>
</Box>
);
}
@@ -493,7 +439,7 @@ const ShopDetail: React.FC = () => {
<Alert severity="warning" sx={{ mb: 2 }}>
{t("Shop not found")}
</Alert>
<Button onClick={() => router.back()}>{t("Back")}</Button>
<Button onClick={() => router.push("/settings/shop?tab=0")}>{t("Back")}</Button>
</Box>
);
}
@@ -504,7 +450,7 @@ const ShopDetail: React.FC = () => {
<CardContent>
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center", mb: 2 }}>
<Typography variant="h6">{t("Shop Information")}</Typography>
<Button onClick={() => router.back()}>{t("Back")}</Button>
<Button onClick={() => router.push("/settings/shop?tab=0")}>{t("Back")}</Button>
</Box>
<Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
@@ -682,22 +628,13 @@ const ShopDetail: React.FC = () => {
</Select>
</FormControl>
) : (
(() => {
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)
)}
</TableCell>
<TableCell>
{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 (


+ 67
- 108
src/components/Shop/TruckLane.tsx Datei anzeigen

@@ -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<string, Truck>();
(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 = () => {
<Table>
<TableHead>
<TableRow>
<TableCell>{t("TruckLance Code")}</TableCell>
<TableCell>{t("Departure Time")}</TableCell>
<TableCell>{t("Store ID")}</TableCell>
<TableCell align="right">{t("Actions")}</TableCell>
<TableCell sx={{ width: "250px", minWidth: "250px", maxWidth: "250px" }}>
{t("TruckLance Code")}
</TableCell>
<TableCell sx={{ width: "200px", minWidth: "200px", maxWidth: "200px" }}>
{t("Departure Time")}
</TableCell>
<TableCell sx={{ width: "150px", minWidth: "150px", maxWidth: "150px" }}>
{t("Store ID")}
</TableCell>
<TableCell align="right" sx={{ width: "150px", minWidth: "150px", maxWidth: "150px" }}>
{t("Actions")}
</TableCell>
</TableRow>
</TableHead>
<TableBody>
@@ -338,40 +301,36 @@ const TruckLane: React.FC = () => {
</TableCell>
</TableRow>
) : (
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 (
<TableRow key={truck.id ?? `truck-${index}`}>
<TableCell>
{String(truck.truckLanceCode || "-")}
</TableCell>
<TableCell>
{formatDepartureTime(
Array.isArray(truck.departureTime)
? truck.departureTime
: (truck.departureTime ? String(truck.departureTime) : null)
)}
</TableCell>
<TableCell>
{displayStoreId}
</TableCell>
<TableCell align="right">
<Button
size="small"
variant="outlined"
onClick={() => handleViewDetail(truck)}
>
{t("View Detail")}
</Button>
</TableCell>
</TableRow>
);
})
paginatedRows.map((truck) => (
<TableRow key={truck.id ?? `truck-${truck.truckLanceCode}`}>
<TableCell sx={{ width: "250px", minWidth: "250px", maxWidth: "250px" }}>
{String(truck.truckLanceCode ?? "-")}
</TableCell>
<TableCell sx={{ width: "200px", minWidth: "200px", maxWidth: "200px" }}>
{formatDepartureTime(
Array.isArray(truck.departureTime)
? truck.departureTime
: (truck.departureTime ? String(truck.departureTime) : null)
)}
</TableCell>
<TableCell sx={{ width: "150px", minWidth: "150px", maxWidth: "150px" }}>
{normalizeStoreId(
truck.storeId ? (typeof truck.storeId === 'string' || truck.storeId instanceof String
? String(truck.storeId)
: String(truck.storeId)) : null
)}
</TableCell>
<TableCell align="right" sx={{ width: "150px", minWidth: "150px", maxWidth: "150px" }}>
<Button
size="small"
variant="outlined"
onClick={() => handleViewDetail(truck)}
>
{t("View Detail")}
</Button>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>


+ 47
- 85
src/components/Shop/TruckLaneDetail.tsx Datei anzeigen

@@ -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<Array<{ name: string; code: string }>>,
findAllUniqueRemarksFromTrucksClient() as Promise<string[]>,
findAllUniqueShopCodesFromTrucksClient() as Promise<string[]>,
findAllUniqueShopNamesFromTrucksClient() as Promise<string[]>,
]);

// 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 (
<Box>
@@ -854,9 +816,10 @@ const TruckLaneDetail: React.FC = () => {
<TableCell>
{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 (
<Autocomplete
freeSolo
@@ -1037,9 +1000,10 @@ const TruckLaneDetail: React.FC = () => {
/>
</Grid>
{(() => {
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 ? (
<Grid item xs={12}>
<Autocomplete
@@ -1095,6 +1059,4 @@ const TruckLaneDetail: React.FC = () => {
);
};

export default TruckLaneDetail;


export default TruckLaneDetail;

Laden…
Abbrechen
Speichern