"use client"; import { Box, Button, Card, CardContent, Stack, Typography, Alert, CircularProgress, Chip, Tabs, Tab, Select, MenuItem, FormControl, InputLabel, } from "@mui/material"; import { useState, useMemo, useCallback, useEffect } from "react"; import { useRouter } from "next/navigation"; import { useTranslation } from "react-i18next"; import SearchBox, { Criterion } from "../SearchBox"; import SearchResults, { Column } from "../SearchResults"; import { defaultPagingController } from "../SearchResults/SearchResults"; import { fetchAllShopsClient } from "@/app/api/shop/client"; import type { Shop, ShopAndTruck } from "@/app/api/shop/actions"; import TruckLane from "./TruckLane"; type ShopRow = Shop & { actions?: string; truckLanceStatus?: "complete" | "missing" | "no-truck"; }; type SearchQuery = { id: string; name: string; code: string; }; type SearchParamNames = keyof SearchQuery; const Shop: React.FC = () => { const { t } = useTranslation("common"); const router = useRouter(); const [activeTab, setActiveTab] = useState(0); const [rows, setRows] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [filters, setFilters] = useState>({}); const [statusFilter, setStatusFilter] = useState("all"); const [pagingController, setPagingController] = useState(defaultPagingController); // client-side filtered rows (contains-matching + status filter) const filteredRows = useMemo(() => { const fKeys = Object.keys(filters || {}).filter((k) => String((filters as any)[k]).trim() !== ""); let normalized = (rows || []).filter((r) => { // apply contains matching for each active filter for (const k of fKeys) { const v = String((filters as any)[k] ?? "").trim(); const rv = String((r as any)[k] ?? "").trim(); // Use exact matching for id field, contains matching for others if (k === "id") { const numValue = Number(v); const rvNum = Number(rv); if (!isNaN(numValue) && !isNaN(rvNum)) { if (numValue !== rvNum) return false; } else { if (v !== rv) return false; } } else { if (!rv.toLowerCase().includes(v.toLowerCase())) return false; } } return true; }); // Apply status filter if (statusFilter !== "all") { normalized = normalized.filter((r) => { return r.truckLanceStatus === statusFilter; }); } return normalized; }, [rows, filters, statusFilter]); // Check if a shop has missing truckLanceCode data const checkTruckLanceStatus = useCallback((shopTrucks: ShopAndTruck[]): "complete" | "missing" | "no-truck" => { if (!shopTrucks || shopTrucks.length === 0) { return "no-truck"; } // Check if shop has any actual truck lanes (not just null entries from LEFT JOIN) // A shop with no trucks will have entries with null truckLanceCode const hasAnyTruckLane = shopTrucks.some((truck) => { const truckLanceCode = (truck as any).truckLanceCode; return truckLanceCode != null && String(truckLanceCode).trim() !== ""; }); if (!hasAnyTruckLane) { return "no-truck"; } // Check each truckLanceCode entry for missing data for (const truck of shopTrucks) { // Skip entries without truckLanceCode (they're from LEFT JOIN when no trucks exist) const truckLanceCode = (truck as any).truckLanceCode; if (!truckLanceCode || String(truckLanceCode).trim() === "") { continue; // Skip this entry, it's not a real truck lane } // Check truckLanceCode: must exist and not be empty (already validated above) const hasTruckLanceCode = truckLanceCode != null && String(truckLanceCode).trim() !== ""; // Check departureTime: must exist and not be empty // Can be array format [hours, minutes] or string format const departureTime = (truck as any).departureTime || (truck as any).DepartureTime; let hasDepartureTime = false; if (departureTime != null) { if (Array.isArray(departureTime) && departureTime.length >= 2) { // Array format [hours, minutes] hasDepartureTime = true; } else { // String format const timeStr = String(departureTime).trim(); hasDepartureTime = timeStr !== "" && timeStr !== "-"; } } // Check loadingSequence: must exist and not be 0 const loadingSeq = (truck as any).loadingSequence || (truck as any).LoadingSequence; const loadingSeqNum = loadingSeq != null && loadingSeq !== undefined ? Number(loadingSeq) : null; const hasLoadingSequence = loadingSeqNum !== null && !isNaN(loadingSeqNum) && loadingSeqNum !== 0; // Check districtReference: must exist and not be 0 const districtRef = (truck as any).districtReference; const districtRefNum = districtRef != null && districtRef !== undefined ? Number(districtRef) : null; const hasDistrictReference = districtRefNum !== null && !isNaN(districtRefNum) && districtRefNum !== 0; // Check storeId: must exist and not be 0 (can be string "2F"/"4F" or number) // Actual field name in JSON is store_id (underscore, lowercase) const storeId = (truck as any).store_id || (truck as any).storeId || (truck as any).Store_id; let storeIdValid = false; if (storeId != null && storeId !== undefined && storeId !== "") { const storeIdStr = String(storeId).trim(); // If it's "2F" or "4F", it's valid (not 0) if (storeIdStr === "2F" || storeIdStr === "4F") { storeIdValid = true; } else { const storeIdNum = Number(storeId); // If it's a valid number and not 0, it's valid if (!isNaN(storeIdNum) && storeIdNum !== 0) { storeIdValid = true; } } } // If any required field is missing or equals 0, return "missing" if (!hasTruckLanceCode || !hasDepartureTime || !hasLoadingSequence || !hasDistrictReference || !storeIdValid) { return "missing"; } } return "complete"; }, []); const fetchAllShops = async (params?: Record) => { setLoading(true); setError(null); try { const data = await fetchAllShopsClient(params) as ShopAndTruck[]; console.log("Fetched shops data:", data); // Group data by shop ID (one shop can have multiple TruckLanceCode entries) const shopMap = new Map(); (data || []).forEach((item: ShopAndTruck) => { const shopId = item.id; if (!shopMap.has(shopId)) { shopMap.set(shopId, { shop: { id: item.id, name: item.name, code: item.code, addr3: item.addr3 ?? "", }, trucks: [], }); } shopMap.get(shopId)!.trucks.push(item); }); // Convert to ShopRow array with truckLanceStatus const mapped: ShopRow[] = Array.from(shopMap.values()).map(({ shop, trucks }) => ({ ...shop, truckLanceStatus: checkTruckLanceStatus(trucks), })); setRows(mapped); } catch (err: any) { console.error("Failed to load shops:", err); setError(err?.message ?? String(err)); } finally { setLoading(false); } }; // SearchBox onSearch will call this const handleSearch = (inputs: Record) => { setFilters(inputs); const params: Record = {}; Object.entries(inputs || {}).forEach(([k, v]) => { if (v != null && String(v).trim() !== "") params[k] = String(v).trim(); }); if (Object.keys(params).length === 0) fetchAllShops(); else fetchAllShops(params); }; const handleViewDetail = useCallback( (shop: ShopRow) => { router.push(`/settings/shop/detail?id=${shop.id}`); }, [router] ); const criteria: Criterion[] = [ { type: "text", label: t("id"), paramName: "id" }, { type: "text", label: t("code"), paramName: "code" }, { type: "text", label: t("Shop Name"), paramName: "name" }, ]; const columns: Column[] = [ { name: "id", label: t("id"), type: "integer", renderCell: (item) => String(item.id ?? ""), }, { name: "code", label: t("Code"), renderCell: (item) => String(item.code ?? ""), }, { name: "name", label: t("Name"), renderCell: (item) => String(item.name ?? ""), }, { name: "addr3", label: t("Addr3"), renderCell: (item) => String((item as any).addr3 ?? ""), }, { name: "truckLanceStatus", label: t("TruckLance Status"), renderCell: (item) => { const status = item.truckLanceStatus; if (status === "complete") { return ; } else if (status === "missing") { return ; } else { return ; } }, }, { name: "actions", label: t("Actions"), headerAlign: "right", renderCell: (item) => ( ), }, ]; useEffect(() => { if (activeTab === 0) { fetchAllShops(); } }, [activeTab]); const handleTabChange = (event: React.SyntheticEvent, newValue: number) => { setActiveTab(newValue); }; return ( {activeTab === 0 && ( []} onSearch={handleSearch} onReset={() => { setRows([]); setFilters({}); }} /> )} {activeTab === 0 && ( {t("Shop")} {t("Filter by Status")} {error && ( {error} )} {loading ? ( ) : ( )} )} {activeTab === 1 && ( )} ); }; export default Shop;