"use client"; import { Box, Card, CardContent, Typography, CircularProgress, Alert, Button, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, TextField, Stack, IconButton, Dialog, DialogTitle, DialogContent, DialogActions, Grid, Snackbar, Autocomplete, } from "@mui/material"; import DeleteIcon from "@mui/icons-material/Delete"; import SaveIcon from "@mui/icons-material/Save"; import AddIcon from "@mui/icons-material/Add"; import CheckIcon from "@mui/icons-material/Check"; import CloseIcon from "@mui/icons-material/Close"; import { useRouter, useSearchParams } from "next/navigation"; import { useState, useEffect } from "react"; import { useSession } from "next-auth/react"; import { useTranslation } from "react-i18next"; import type { ShopAndTruck, Truck } from "@/app/api/shop/actions"; import { fetchAllShopsClient, findTruckLaneByShopIdClient, deleteTruckLaneClient, createTruckClient, findAllUniqueTruckLaneCombinationsClient, } from "@/app/api/shop/client"; import type { SessionWithTokens } from "@/config/authConfig"; import { formatDepartureTime, normalizeStoreId } from "@/app/utils/formatUtil"; type ShopDetailData = { id: number; name: String; code: String; addr1: String; addr2: String; addr3: String; contactNo: number; type: String; contactEmail: String; contactName: String; }; // 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); }; /** Label for truck lane picker: code + optional remark (unique combo from API). */ const getTruckLaneOptionLabel = (lane: Truck): string => { const code = String(lane.truckLanceCode ?? "").trim(); const remark = lane.remark != null && String(lane.remark).trim() !== "" ? String(lane.remark).trim() : null; return remark ? `${code} — ${remark}` : code || "-"; }; const isSameTruckLaneOption = (a: Truck | null, b: Truck | null): boolean => { if (a === b) return true; if (!a || !b) return false; const sameCode = String(a.truckLanceCode ?? "") === String(b.truckLanceCode ?? ""); const ra = a.remark != null ? String(a.remark) : ""; const rb = b.remark != null ? String(b.remark) : ""; return sameCode && ra === rb; }; /** Build HH:mm string from API departureTime for backend parsing. */ const departureTimeToStringForSave = (timeValue: Truck["departureTime"]): string => { const formatted = formatDepartureTime( Array.isArray(timeValue) ? timeValue : timeValue ? String(timeValue) : null ); if (!formatted || formatted === "-") return ""; return parseDepartureTimeForBackend(formatted); }; const ShopDetail: React.FC = () => { const { t } = useTranslation("common"); const router = useRouter(); const searchParams = useSearchParams(); const shopId = searchParams.get("id"); const { data: session, status: sessionStatus } = useSession() as { data: SessionWithTokens | null; status: string }; const [shopDetail, setShopDetail] = useState(null); const [truckData, setTruckData] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [saving, setSaving] = useState(false); const [confirmingDeleteIndex, setConfirmingDeleteIndex] = useState(null); const [addDialogOpen, setAddDialogOpen] = useState(false); const [availableTruckLanes, setAvailableTruckLanes] = useState([]); const [selectedTruckLane, setSelectedTruckLane] = useState(null); const [addLoadingSequence, setAddLoadingSequence] = useState(0); const [loadingTruckLanes, setLoadingTruckLanes] = useState(false); const [snackbarOpen, setSnackbarOpen] = useState(false); const [snackbarMessage, setSnackbarMessage] = useState(""); useEffect(() => { // Wait for session to be ready before making API calls if (sessionStatus === "loading") { return; // Still loading session } // If session is unauthenticated, don't make API calls (middleware will handle redirect) if (sessionStatus === "unauthenticated" || !session) { setError(t("Please log in to view shop details")); setLoading(false); return; } const fetchShopDetail = async () => { if (!shopId) { setError(t("Shop ID is required")); setLoading(false); return; } // Convert shopId to number for proper filtering const shopIdNum = parseInt(shopId, 10); if (isNaN(shopIdNum)) { setError(t("Invalid Shop ID")); setLoading(false); return; } setLoading(true); setError(null); try { // Fetch shop information - try with ID parameter first, then filter if needed let shopDataResponse = await fetchAllShopsClient({ id: shopIdNum }) as ShopAndTruck[]; // If no results with ID parameter, fetch all and filter client-side if (!shopDataResponse || shopDataResponse.length === 0) { shopDataResponse = await fetchAllShopsClient() as ShopAndTruck[]; } // Filter to find the shop with matching ID (in case API doesn't filter properly) const shopData = shopDataResponse?.find((item) => item.id === shopIdNum); if (shopData) { // Set shop detail info setShopDetail({ id: shopData.id ?? 0, name: shopData.name ?? "", code: shopData.code ?? "", addr1: shopData.addr1 ?? "", addr2: shopData.addr2 ?? "", addr3: shopData.addr3 ?? "", contactNo: shopData.contactNo ?? 0, type: shopData.type ?? "", contactEmail: shopData.contactEmail ?? "", contactName: shopData.contactName ?? "", }); } else { setError(t("Shop not found")); setLoading(false); return; } // Fetch truck information using the Truck interface with numeric ID const trucks = await findTruckLaneByShopIdClient(shopIdNum) as Truck[]; setTruckData(trucks || []); } catch (err: any) { console.error("Failed to load shop detail:", err); // Handle errors gracefully - don't trigger auto-logout const errorMessage = err?.message ?? String(err) ?? t("Failed to load shop details"); setError(errorMessage); } finally { setLoading(false); } }; fetchShopDetail(); }, [shopId, sessionStatus, session]); const handleDelete = async (truckId: number) => { if (!shopId) { setError(t("Shop ID is required")); return; } setSaving(true); setError(null); try { await deleteTruckLaneClient({ id: truckId }); setConfirmingDeleteIndex(null); const shopIdNum = parseInt(shopId, 10); const trucks = await findTruckLaneByShopIdClient(shopIdNum) as Truck[]; setTruckData(trucks || []); } catch (err: any) { console.error("Failed to delete truck lane:", err); setError(err?.message ?? String(err) ?? t("Failed to delete truck lane")); } finally { setSaving(false); } }; const handleOpenAddDialog = async () => { setSelectedTruckLane(null); setAddLoadingSequence(0); setAddDialogOpen(true); setError(null); setLoadingTruckLanes(true); try { const lanes = (await findAllUniqueTruckLaneCombinationsClient()) as Truck[]; setAvailableTruckLanes(lanes || []); } catch (err: any) { console.error("Failed to load truck lanes:", err); setAvailableTruckLanes([]); setSnackbarMessage( err?.message ?? String(err) ?? t("Failed to load truck lanes") ); setSnackbarOpen(true); } finally { setLoadingTruckLanes(false); } }; const handleCloseAddDialog = () => { setAddDialogOpen(false); setSelectedTruckLane(null); setAddLoadingSequence(0); setAvailableTruckLanes([]); }; const handleCreateTruck = async () => { const missingFields: string[] = []; if (!shopId || !shopDetail) { missingFields.push(t("Shop Information")); } if (!selectedTruckLane) { missingFields.push(t("TruckLance Code")); } const departureTime = selectedTruckLane ? departureTimeToStringForSave(selectedTruckLane.departureTime) : ""; if (!departureTime) { missingFields.push(t("Departure Time")); } if (missingFields.length > 0) { const message = `${t("Please fill in the following required fields:")} ${missingFields.join(", ")}`; setSnackbarMessage(message); setSnackbarOpen(true); return; } const lane = selectedTruckLane!; const storeIdStr = normalizeStoreId(lane.storeId) || "2F"; const remarkValue = storeIdStr === "4F" && lane.remark != null && String(lane.remark).trim() !== "" ? String(lane.remark).trim() : null; setSaving(true); setError(null); try { await createTruckClient({ store_id: storeIdStr, truckLanceCode: String(lane.truckLanceCode || "").trim(), departureTime: departureTime, shopId: shopDetail!.id, shopName: String(shopDetail!.name), shopCode: String(shopDetail!.code), loadingSequence: addLoadingSequence, districtReference: lane.districtReference != null && String(lane.districtReference).trim() !== "" ? String(lane.districtReference) : null, remark: remarkValue, }); // Refresh truck data after create const shopIdNum = parseInt(shopId || "0", 10); const trucks = await findTruckLaneByShopIdClient(shopIdNum) as Truck[]; setTruckData(trucks || []); handleCloseAddDialog(); } catch (err: any) { console.error("Failed to create truck:", err); setError(err?.message ?? String(err) ?? t("Failed to create truck")); } finally { setSaving(false); } }; if (loading) { return ( ); } if (error) { return ( {error} ); } if (!shopDetail) { return ( {t("Shop not found")} ); } return ( {t("Shop Information")} {t("Shop ID")} {shopDetail.id} {t("Name")} {shopDetail.name} {t("Code")} {shopDetail.code} {t("Addr1")} {shopDetail.addr1 || "-"} {t("Addr2")} {shopDetail.addr2 || "-"} {t("Addr3")} {shopDetail.addr3 || "-"} {t("Contact No")} {shopDetail.contactNo || "-"} {t("Contact Email")} {shopDetail.contactEmail || "-"} {t("Contact Name")} {shopDetail.contactName || "-"} {t("Truck Information")} {t("TruckLance Code")} {t("Departure Time")} {t("Loading Sequence")} {t("District Reference")} {t("Store ID")} {t("Remark")} {t("Actions")} {truckData.length === 0 ? ( {t("No Truck data available")} ) : ( truckData.map((truck, index) => ( {String(truck.truckLanceCode || "-")} {formatDepartureTime( Array.isArray(truck.departureTime) ? truck.departureTime : truck.departureTime ? String(truck.departureTime) : null )} {truck.loadingSequence !== null && truck.loadingSequence !== undefined ? String(truck.loadingSequence) : "-"} {truck.districtReference !== null && truck.districtReference !== undefined ? String(truck.districtReference) : "-"} {normalizeStoreId(truck.storeId)} {String(truck.remark || "-")} {truck.id && ( confirmingDeleteIndex === index ? ( <> handleDelete(truck.id!)} disabled={saving} title={t("Confirm delete")} > setConfirmingDeleteIndex(null)} disabled={saving} title={t("Cancel")} > ) : ( setConfirmingDeleteIndex(index)} disabled={saving || confirmingDeleteIndex !== null} title={t("Delete truck lane")} > ) )} )) )}
{/* Add Truck Dialog */} {t("Add New Truck Lane")} { setSelectedTruckLane(newValue); }} getOptionLabel={(option) => getTruckLaneOptionLabel(option)} isOptionEqualToValue={(option, value) => isSameTruckLaneOption(option, value)} disabled={saving || loadingTruckLanes} renderInput={(params) => ( )} /> setAddLoadingSequence(parseInt(e.target.value, 10) || 0)} disabled={saving} /> {/* Snackbar for notifications */} setSnackbarOpen(false)} anchorOrigin={{ vertical: 'top', horizontal: 'center' }} > setSnackbarOpen(false)} severity="warning" sx={{ width: '100%' }} > {snackbarMessage}
); }; export default ShopDetail;