|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869 |
- "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,
- Select,
- MenuItem,
- FormControl,
- InputLabel,
- Autocomplete,
- } from "@mui/material";
- import DeleteIcon from "@mui/icons-material/Delete";
- import EditIcon from "@mui/icons-material/Edit";
- import SaveIcon from "@mui/icons-material/Save";
- import CancelIcon from "@mui/icons-material/Cancel";
- import AddIcon from "@mui/icons-material/Add";
- import { useRouter, useSearchParams } from "next/navigation";
- import { useState, useEffect } from "react";
- import { useSession } from "next-auth/react";
- import { useTranslation } from "react-i18next";
- import type { Shop, ShopAndTruck, Truck } from "@/app/api/shop/actions";
- import {
- fetchAllShopsClient,
- findTruckLaneByShopIdClient,
- updateTruckLaneClient,
- deleteTruckLaneClient,
- createTruckClient
- } 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);
- };
-
- 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<ShopDetailData | null>(null);
- const [truckData, setTruckData] = useState<Truck[]>([]);
- const [editedTruckData, setEditedTruckData] = useState<Truck[]>([]);
- const [loading, setLoading] = useState<boolean>(true);
- const [error, setError] = useState<string | null>(null);
- const [editingRowIndex, setEditingRowIndex] = useState<number | null>(null);
- const [saving, setSaving] = useState<boolean>(false);
- const [addDialogOpen, setAddDialogOpen] = useState<boolean>(false);
- const [newTruck, setNewTruck] = useState({
- truckLanceCode: "",
- departureTime: "",
- loadingSequence: 0,
- districtReference: 0,
- storeId: "2F",
- remark: "",
- });
- const [uniqueRemarks, setUniqueRemarks] = useState<string[]>([]);
- const [snackbarOpen, setSnackbarOpen] = useState<boolean>(false);
- const [snackbarMessage, setSnackbarMessage] = useState<string>("");
-
- 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 || []);
- setEditedTruckData(trucks || []);
- setEditingRowIndex(null);
-
- // Extract unique remarks from trucks for this shop
- const remarks = trucks
- ?.map(t => t.remark)
- .filter((remark): remark is string => remark != null && String(remark).trim() !== "")
- .map(r => String(r).trim())
- .filter((value, index, self) => self.indexOf(value) === index) || [];
- setUniqueRemarks(remarks);
- } 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 handleEdit = (index: number) => {
- setEditingRowIndex(index);
- const updated = [...truckData];
- updated[index] = { ...updated[index] };
- // Normalize departureTime to HH:mm format for editing
- if (updated[index].departureTime) {
- const timeValue = updated[index].departureTime;
- const formatted = formatDepartureTime(
- Array.isArray(timeValue) ? timeValue : (timeValue ? String(timeValue) : null)
- );
- if (formatted !== "-") {
- updated[index].departureTime = formatted;
- }
- }
- // Ensure remark is initialized as string (not null/undefined)
- if (updated[index].remark == null) {
- updated[index].remark = "";
- }
- setEditedTruckData(updated);
- setError(null);
- };
-
- const handleCancel = (index: number) => {
- setEditingRowIndex(null);
- setEditedTruckData([...truckData]);
- setError(null);
- };
-
- const handleSave = async (index: number) => {
- if (!shopId) {
- setError(t("Shop ID is required"));
- return;
- }
-
- const truck = editedTruckData[index];
- if (!truck || !truck.id) {
- setError(t("Invalid shop data"));
- return;
- }
-
- setSaving(true);
- setError(null);
- try {
- // Use the departureTime from editedTruckData which is already in HH:mm format from the input field
- // If it's already a valid HH:mm string, use it directly; otherwise format it
- let departureTime = String(truck.departureTime || "").trim();
- if (!departureTime || departureTime === "-") {
- departureTime = "";
- } else if (!/^\d{1,2}:\d{2}$/.test(departureTime)) {
- // Only convert if it's not already in HH:mm format
- departureTime = parseDepartureTimeForBackend(departureTime);
- }
-
- // Convert storeId to string format (2F or 4F)
- 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
- let remarkValue: string | null = null;
- if (storeIdStr === "4F") {
- const remark = truck.remark;
- if (remark != null && String(remark).trim() !== "") {
- remarkValue = String(remark).trim();
- }
- }
-
- await updateTruckLaneClient({
- id: truck.id,
- truckLanceCode: String(truck.truckLanceCode || ""),
- departureTime: departureTime,
- loadingSequence: Number(truck.loadingSequence) || 0,
- districtReference: Number(truck.districtReference) || 0,
- storeId: storeIdStr,
- remark: remarkValue,
- });
-
- // Refresh truck data after update
- const shopIdNum = parseInt(shopId, 10);
- const trucks = await findTruckLaneByShopIdClient(shopIdNum) as Truck[];
- setTruckData(trucks || []);
- setEditedTruckData(trucks || []);
- setEditingRowIndex(null);
-
- // Update unique remarks
- const remarks = trucks
- ?.map(t => t.remark)
- .filter((remark): remark is string => remark != null && String(remark).trim() !== "")
- .map(r => String(r).trim())
- .filter((value, index, self) => self.indexOf(value) === index) || [];
- setUniqueRemarks(remarks);
- } catch (err: any) {
- console.error("Failed to save truck data:", err);
- setError(err?.message ?? String(err) ?? t("Failed to save truck data"));
- } finally {
- setSaving(false);
- }
- };
-
- const handleTruckFieldChange = (index: number, field: keyof Truck, value: string | number) => {
- const updated = [...editedTruckData];
- updated[index] = {
- ...updated[index],
- [field]: value,
- };
- setEditedTruckData(updated);
- };
-
- const handleDelete = async (truckId: number) => {
- if (!window.confirm(t("Are you sure you want to delete this truck lane?"))) {
- return;
- }
-
- if (!shopId) {
- setError(t("Shop ID is required"));
- return;
- }
-
- setSaving(true);
- setError(null);
- try {
- await deleteTruckLaneClient({ id: truckId });
-
- // Refresh truck data after delete
- const shopIdNum = parseInt(shopId, 10);
- const trucks = await findTruckLaneByShopIdClient(shopIdNum) as Truck[];
- setTruckData(trucks || []);
- setEditedTruckData(trucks || []);
- setEditingRowIndex(null);
- } 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 = () => {
- setNewTruck({
- truckLanceCode: "",
- departureTime: "",
- loadingSequence: 0,
- districtReference: 0,
- storeId: "2F",
- remark: "",
- });
- setAddDialogOpen(true);
- setError(null);
- };
-
- const handleCloseAddDialog = () => {
- setAddDialogOpen(false);
- setNewTruck({
- truckLanceCode: "",
- departureTime: "",
- loadingSequence: 0,
- districtReference: 0,
- storeId: "2F",
- remark: "",
- });
- };
-
- const handleCreateTruck = async () => {
- // Validate all required fields
- const missingFields: string[] = [];
-
- if (!shopId || !shopDetail) {
- missingFields.push(t("Shop Information"));
- }
-
- if (!newTruck.truckLanceCode.trim()) {
- missingFields.push(t("TruckLance Code"));
- }
-
- if (!newTruck.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;
- }
-
- setSaving(true);
- setError(null);
- try {
- const departureTime = parseDepartureTimeForBackend(newTruck.departureTime);
-
- await createTruckClient({
- store_id: newTruck.storeId,
- truckLanceCode: newTruck.truckLanceCode.trim(),
- departureTime: departureTime,
- shopId: shopDetail!.id,
- shopName: String(shopDetail!.name),
- shopCode: String(shopDetail!.code),
- loadingSequence: newTruck.loadingSequence,
- districtReference: newTruck.districtReference,
- remark: newTruck.storeId === "4F" ? (newTruck.remark?.trim() || null) : null,
- });
-
- // Refresh truck data after create
- const shopIdNum = parseInt(shopId || "0", 10);
- const trucks = await findTruckLaneByShopIdClient(shopIdNum) as Truck[];
- setTruckData(trucks || []);
- setEditedTruckData(trucks || []);
-
- // Update unique remarks
- const remarks = trucks
- ?.map(t => t.remark)
- .filter((remark): remark is string => remark != null && String(remark).trim() !== "")
- .map(r => String(r).trim())
- .filter((value, index, self) => self.indexOf(value) === index) || [];
- setUniqueRemarks(remarks);
-
- 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 (
- <Box sx={{ display: "flex", justifyContent: "center", p: 4 }}>
- <CircularProgress />
- </Box>
- );
- }
- if (error) {
- return (
- <Box>
- <Alert severity="error" sx={{ mb: 2 }}>
- {error}
- </Alert>
- <Button onClick={() => router.push("/settings/shop?tab=0")}>{t("Back")}</Button>
- </Box>
- );
- }
-
- if (!shopDetail) {
- return (
- <Box>
- <Alert severity="warning" sx={{ mb: 2 }}>
- {t("Shop not found")}
- </Alert>
- <Button onClick={() => router.push("/settings/shop?tab=0")}>{t("Back")}</Button>
- </Box>
- );
- }
-
- return (
- <Box>
- <Card sx={{ mb: 2 }}>
- <CardContent>
- <Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center", mb: 2 }}>
- <Typography variant="h6">{t("Shop Information")}</Typography>
- <Button onClick={() => router.push("/settings/shop?tab=0")}>{t("Back")}</Button>
- </Box>
-
- <Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
- <Box>
- <Typography variant="subtitle2" color="text.secondary" fontWeight="bold">{t("Shop ID")}</Typography>
- <Typography variant="body1" fontWeight="medium">{shopDetail.id}</Typography>
- </Box>
- <Box>
- <Typography variant="subtitle2" color="text.secondary">{t("Name")}</Typography>
- <Typography variant="body1">{shopDetail.name}</Typography>
- </Box>
- <Box>
- <Typography variant="subtitle2" color="text.secondary">{t("Code")}</Typography>
- <Typography variant="body1">{shopDetail.code}</Typography>
- </Box>
- <Box>
- <Typography variant="subtitle2" color="text.secondary">{t("Addr1")}</Typography>
- <Typography variant="body1">{shopDetail.addr1 || "-"}</Typography>
- </Box>
- <Box>
- <Typography variant="subtitle2" color="text.secondary">{t("Addr2")}</Typography>
- <Typography variant="body1">{shopDetail.addr2 || "-"}</Typography>
- </Box>
- <Box>
- <Typography variant="subtitle2" color="text.secondary">{t("Addr3")}</Typography>
- <Typography variant="body1">{shopDetail.addr3 || "-"}</Typography>
- </Box>
- <Box>
- <Typography variant="subtitle2" color="text.secondary">{t("Contact No")}</Typography>
- <Typography variant="body1">{shopDetail.contactNo || "-"}</Typography>
- </Box>
- <Box>
- <Typography variant="subtitle2" color="text.secondary">{t("Contact Email")}</Typography>
- <Typography variant="body1">{shopDetail.contactEmail || "-"}</Typography>
- </Box>
- <Box>
- <Typography variant="subtitle2" color="text.secondary">{t("Contact Name")}</Typography>
- <Typography variant="body1">{shopDetail.contactName || "-"}</Typography>
- </Box>
- </Box>
- </CardContent>
- </Card>
-
- <Card>
- <CardContent>
- <Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center", mb: 2 }}>
- <Typography variant="h6">{t("Truck Information")}</Typography>
- <Button
- variant="contained"
- startIcon={<AddIcon />}
- onClick={handleOpenAddDialog}
- disabled={editingRowIndex !== null || saving}
- >
- {t("Add Truck Lane")}
- </Button>
- </Box>
- <TableContainer component={Paper}>
- <Table>
- <TableHead>
- <TableRow>
- <TableCell>{t("TruckLance Code")}</TableCell>
- <TableCell>{t("Departure Time")}</TableCell>
- <TableCell>{t("Loading Sequence")}</TableCell>
- <TableCell>{t("District Reference")}</TableCell>
- <TableCell>{t("Store ID")}</TableCell>
- <TableCell>{t("Remark")}</TableCell>
- <TableCell>{t("Actions")}</TableCell>
- </TableRow>
- </TableHead>
- <TableBody>
- {truckData.length === 0 ? (
- <TableRow>
- <TableCell colSpan={7} align="center">
- <Typography variant="body2" color="text.secondary">
- {t("No Truck data available")}
- </Typography>
- </TableCell>
- </TableRow>
- ) : (
- truckData.map((truck, index) => {
- const isEditing = editingRowIndex === index;
- const displayTruck = isEditing ? editedTruckData[index] : truck;
-
- return (
- <TableRow key={truck.id ?? `truck-${index}`}>
- <TableCell>
- {isEditing ? (
- <TextField
- size="small"
- value={String(displayTruck?.truckLanceCode || "")}
- onChange={(e) => handleTruckFieldChange(index, "truckLanceCode", e.target.value)}
- fullWidth
- />
- ) : (
- String(truck.truckLanceCode || "-")
- )}
- </TableCell>
- <TableCell>
- {isEditing ? (
- <TextField
- size="small"
- type="time"
- value={(() => {
- const timeValue = displayTruck?.departureTime;
- const formatted = formatDepartureTime(
- Array.isArray(timeValue) ? timeValue : (timeValue ? String(timeValue) : null)
- );
- return formatted !== "-" ? formatted : "";
- })()}
- onChange={(e) => handleTruckFieldChange(index, "departureTime", e.target.value)}
- fullWidth
- InputLabelProps={{
- shrink: true,
- }}
- inputProps={{
- step: 300, // 5 minutes
- }}
- />
- ) : (
- formatDepartureTime(
- Array.isArray(truck.departureTime) ? truck.departureTime : (truck.departureTime ? String(truck.departureTime) : null)
- )
- )}
- </TableCell>
- <TableCell>
- {isEditing ? (
- <TextField
- size="small"
- type="number"
- value={displayTruck?.loadingSequence ?? 0}
- onChange={(e) => handleTruckFieldChange(index, "loadingSequence", parseInt(e.target.value) || 0)}
- fullWidth
- />
- ) : (
- truck.loadingSequence !== null && truck.loadingSequence !== undefined ? String(truck.loadingSequence) : "-"
- )}
- </TableCell>
- <TableCell>
- {isEditing ? (
- <TextField
- size="small"
- type="number"
- value={displayTruck?.districtReference ?? 0}
- onChange={(e) => handleTruckFieldChange(index, "districtReference", parseInt(e.target.value) || 0)}
- fullWidth
- />
- ) : (
- truck.districtReference !== null && truck.districtReference !== undefined ? String(truck.districtReference) : "-"
- )}
- </TableCell>
- <TableCell>
- {isEditing ? (
- <FormControl size="small" fullWidth>
- <Select
- value={(() => {
- const storeId = displayTruck?.storeId;
- if (!storeId) return "2F";
- const storeIdStr = typeof storeId === 'string' ? storeId : String(storeId);
- // Convert numeric values to string format
- if (storeIdStr === "2" || storeIdStr === "2F") return "2F";
- if (storeIdStr === "4" || storeIdStr === "4F") return "4F";
- return storeIdStr;
- })()}
- onChange={(e) => {
- const newStoreId = e.target.value;
- handleTruckFieldChange(index, "storeId", newStoreId);
- // Clear remark if storeId changes from 4F to something else
- if (newStoreId !== "4F") {
- handleTruckFieldChange(index, "remark", "");
- }
- }}
- >
- <MenuItem value="2F">2F</MenuItem>
- <MenuItem value="4F">4F</MenuItem>
- </Select>
- </FormControl>
- ) : (
- normalizeStoreId(truck.storeId)
- )}
- </TableCell>
- <TableCell>
- {isEditing ? (
- (() => {
- const storeIdStr = normalizeStoreId(displayTruck?.storeId) || "2F";
- const isEditable = storeIdStr === "4F";
-
- return (
- <Autocomplete
- freeSolo
- size="small"
- disabled={!isEditable}
- options={uniqueRemarks}
- value={displayTruck?.remark ? String(displayTruck.remark) : ""}
- onChange={(event, newValue) => {
- if (isEditable) {
- const remarkValue = typeof newValue === 'string' ? newValue : (newValue || "");
- handleTruckFieldChange(index, "remark", remarkValue);
- }
- }}
- onInputChange={(event, newInputValue, reason) => {
- // Only update on user input, not when clearing or selecting
- if (isEditable && (reason === 'input' || reason === 'clear')) {
- handleTruckFieldChange(index, "remark", newInputValue);
- }
- }}
- renderInput={(params) => (
- <TextField
- {...params}
- fullWidth
- placeholder={isEditable ? t("Enter or select remark") : t("Not editable for this Store ID")}
- disabled={!isEditable}
- />
- )}
- />
- );
- })()
- ) : (
- String(truck.remark || "-")
- )}
- </TableCell>
- <TableCell>
- <Stack direction="row" spacing={0.5}>
- {isEditing ? (
- <>
- <IconButton
- color="primary"
- size="small"
- onClick={() => handleSave(index)}
- disabled={saving}
- title={t("Save changes")}
- >
- <SaveIcon />
- </IconButton>
- <IconButton
- color="default"
- size="small"
- onClick={() => handleCancel(index)}
- disabled={saving}
- title={t("Cancel editing")}
- >
- <CancelIcon />
- </IconButton>
- </>
- ) : (
- <>
- <IconButton
- color="primary"
- size="small"
- onClick={() => handleEdit(index)}
- disabled={editingRowIndex !== null}
- title={t("Edit truck lane")}
- >
- <EditIcon />
- </IconButton>
- {truck.id && (
- <IconButton
- color="error"
- size="small"
- onClick={() => handleDelete(truck.id!)}
- disabled={saving || editingRowIndex !== null}
- title={t("Delete truck lane")}
- >
- <DeleteIcon />
- </IconButton>
- )}
- </>
- )}
- </Stack>
- </TableCell>
- </TableRow>
- );
- })
- )}
- </TableBody>
- </Table>
- </TableContainer>
- </CardContent>
- </Card>
-
- {/* Add Truck Dialog */}
- <Dialog open={addDialogOpen} onClose={handleCloseAddDialog} maxWidth="sm" fullWidth>
- <DialogTitle>{t("Add New Truck Lane")}</DialogTitle>
- <DialogContent>
- <Box sx={{ pt: 2 }}>
- <Grid container spacing={2}>
- <Grid item xs={12}>
- <TextField
- label={t("TruckLance Code")}
- fullWidth
- required
- value={newTruck.truckLanceCode}
- onChange={(e) => setNewTruck({ ...newTruck, truckLanceCode: e.target.value })}
- disabled={saving}
- />
- </Grid>
- <Grid item xs={12}>
- <TextField
- label={t("Departure Time")}
- type="time"
- fullWidth
- required
- value={newTruck.departureTime}
- onChange={(e) => setNewTruck({ ...newTruck, departureTime: e.target.value })}
- disabled={saving}
- InputLabelProps={{
- shrink: true,
- }}
- inputProps={{
- step: 300, // 5 minutes
- }}
- />
- </Grid>
- <Grid item xs={6}>
- <TextField
- label={t("Loading Sequence")}
- type="number"
- fullWidth
- value={newTruck.loadingSequence}
- onChange={(e) => setNewTruck({ ...newTruck, loadingSequence: parseInt(e.target.value) || 0 })}
- disabled={saving}
- />
- </Grid>
- <Grid item xs={6}>
- <TextField
- label={t("District Reference")}
- type="number"
- fullWidth
- value={newTruck.districtReference}
- onChange={(e) => setNewTruck({ ...newTruck, districtReference: parseInt(e.target.value) || 0 })}
- disabled={saving}
- />
- </Grid>
- <Grid item xs={6}>
- <FormControl fullWidth>
- <InputLabel>{t("Store ID")}</InputLabel>
- <Select
- value={newTruck.storeId}
- label={t("Store ID")}
- onChange={(e) => {
- const newStoreId = e.target.value;
- setNewTruck({
- ...newTruck,
- storeId: newStoreId,
- remark: newStoreId === "4F" ? newTruck.remark : ""
- });
- }}
- disabled={saving}
- >
- <MenuItem value="2F">2F</MenuItem>
- <MenuItem value="4F">4F</MenuItem>
- </Select>
- </FormControl>
- </Grid>
- {newTruck.storeId === "4F" && (
- <Grid item xs={12}>
- <Autocomplete
- freeSolo
- options={uniqueRemarks}
- value={newTruck.remark || ""}
- onChange={(event, newValue) => {
- setNewTruck({ ...newTruck, remark: newValue || "" });
- }}
- onInputChange={(event, newInputValue) => {
- setNewTruck({ ...newTruck, remark: newInputValue });
- }}
- renderInput={(params) => (
- <TextField
- {...params}
- label={t("Remark")}
- fullWidth
- placeholder={t("Enter or select remark")}
- disabled={saving}
- />
- )}
- />
- </Grid>
- )}
- </Grid>
- </Box>
- </DialogContent>
- <DialogActions>
- <Button onClick={handleCloseAddDialog} disabled={saving}>
- {t("Cancel")}
- </Button>
- <Button
- onClick={handleCreateTruck}
- variant="contained"
- startIcon={<SaveIcon />}
- disabled={saving}
- >
- {saving ? t("Submitting...") : t("Save")}
- </Button>
- </DialogActions>
- </Dialog>
-
- {/* Snackbar for notifications */}
- <Snackbar
- open={snackbarOpen}
- autoHideDuration={6000}
- onClose={() => setSnackbarOpen(false)}
- anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
- >
- <Alert
- onClose={() => setSnackbarOpen(false)}
- severity="warning"
- sx={{ width: '100%' }}
- >
- {snackbarMessage}
- </Alert>
- </Snackbar>
- </Box>
- );
- };
-
- export default ShopDetail;
-
|