|
|
@@ -24,6 +24,11 @@ import { |
|
|
DialogActions, |
|
|
DialogActions, |
|
|
Grid, |
|
|
Grid, |
|
|
Snackbar, |
|
|
Snackbar, |
|
|
|
|
|
Select, |
|
|
|
|
|
MenuItem, |
|
|
|
|
|
FormControl, |
|
|
|
|
|
InputLabel, |
|
|
|
|
|
Autocomplete, |
|
|
} from "@mui/material"; |
|
|
} from "@mui/material"; |
|
|
import DeleteIcon from "@mui/icons-material/Delete"; |
|
|
import DeleteIcon from "@mui/icons-material/Delete"; |
|
|
import EditIcon from "@mui/icons-material/Edit"; |
|
|
import EditIcon from "@mui/icons-material/Edit"; |
|
|
@@ -143,8 +148,10 @@ const ShopDetail: React.FC = () => { |
|
|
departureTime: "", |
|
|
departureTime: "", |
|
|
loadingSequence: 0, |
|
|
loadingSequence: 0, |
|
|
districtReference: 0, |
|
|
districtReference: 0, |
|
|
storeId: 2, |
|
|
|
|
|
|
|
|
storeId: "2F", |
|
|
|
|
|
remark: "", |
|
|
}); |
|
|
}); |
|
|
|
|
|
const [uniqueRemarks, setUniqueRemarks] = useState<string[]>([]); |
|
|
const [snackbarOpen, setSnackbarOpen] = useState<boolean>(false); |
|
|
const [snackbarOpen, setSnackbarOpen] = useState<boolean>(false); |
|
|
const [snackbarMessage, setSnackbarMessage] = useState<string>(""); |
|
|
const [snackbarMessage, setSnackbarMessage] = useState<string>(""); |
|
|
|
|
|
|
|
|
@@ -215,6 +222,14 @@ const ShopDetail: React.FC = () => { |
|
|
setTruckData(trucks || []); |
|
|
setTruckData(trucks || []); |
|
|
setEditedTruckData(trucks || []); |
|
|
setEditedTruckData(trucks || []); |
|
|
setEditingRowIndex(null); |
|
|
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) { |
|
|
} catch (err: any) { |
|
|
console.error("Failed to load shop detail:", err); |
|
|
console.error("Failed to load shop detail:", err); |
|
|
// Handle errors gracefully - don't trigger auto-logout |
|
|
// Handle errors gracefully - don't trigger auto-logout |
|
|
@@ -232,6 +247,20 @@ const ShopDetail: React.FC = () => { |
|
|
setEditingRowIndex(index); |
|
|
setEditingRowIndex(index); |
|
|
const updated = [...truckData]; |
|
|
const updated = [...truckData]; |
|
|
updated[index] = { ...updated[index] }; |
|
|
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); |
|
|
setEditedTruckData(updated); |
|
|
setError(null); |
|
|
setError(null); |
|
|
}; |
|
|
}; |
|
|
@@ -257,8 +286,28 @@ const ShopDetail: React.FC = () => { |
|
|
setSaving(true); |
|
|
setSaving(true); |
|
|
setError(null); |
|
|
setError(null); |
|
|
try { |
|
|
try { |
|
|
// Convert departureTime to proper format if needed |
|
|
|
|
|
const departureTime = parseDepartureTimeForBackend(String(truck.departureTime || "")); |
|
|
|
|
|
|
|
|
// 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 = truck.storeId ? (typeof truck.storeId === 'string' ? truck.storeId : String(truck.storeId) === "2" ? "2F" : String(truck.storeId) === "4" ? "4F" : String(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({ |
|
|
await updateTruckLaneClient({ |
|
|
id: truck.id, |
|
|
id: truck.id, |
|
|
@@ -266,6 +315,8 @@ const ShopDetail: React.FC = () => { |
|
|
departureTime: departureTime, |
|
|
departureTime: departureTime, |
|
|
loadingSequence: Number(truck.loadingSequence) || 0, |
|
|
loadingSequence: Number(truck.loadingSequence) || 0, |
|
|
districtReference: Number(truck.districtReference) || 0, |
|
|
districtReference: Number(truck.districtReference) || 0, |
|
|
|
|
|
storeId: storeIdStr, |
|
|
|
|
|
remark: remarkValue, |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
// Refresh truck data after update |
|
|
// Refresh truck data after update |
|
|
@@ -274,6 +325,14 @@ const ShopDetail: React.FC = () => { |
|
|
setTruckData(trucks || []); |
|
|
setTruckData(trucks || []); |
|
|
setEditedTruckData(trucks || []); |
|
|
setEditedTruckData(trucks || []); |
|
|
setEditingRowIndex(null); |
|
|
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) { |
|
|
} catch (err: any) { |
|
|
console.error("Failed to save truck data:", err); |
|
|
console.error("Failed to save truck data:", err); |
|
|
setError(err?.message ?? String(err) ?? "Failed to save truck data"); |
|
|
setError(err?.message ?? String(err) ?? "Failed to save truck data"); |
|
|
@@ -326,7 +385,8 @@ const ShopDetail: React.FC = () => { |
|
|
departureTime: "", |
|
|
departureTime: "", |
|
|
loadingSequence: 0, |
|
|
loadingSequence: 0, |
|
|
districtReference: 0, |
|
|
districtReference: 0, |
|
|
storeId: 2, |
|
|
|
|
|
|
|
|
storeId: "2F", |
|
|
|
|
|
remark: "", |
|
|
}); |
|
|
}); |
|
|
setAddDialogOpen(true); |
|
|
setAddDialogOpen(true); |
|
|
setError(null); |
|
|
setError(null); |
|
|
@@ -339,7 +399,8 @@ const ShopDetail: React.FC = () => { |
|
|
departureTime: "", |
|
|
departureTime: "", |
|
|
loadingSequence: 0, |
|
|
loadingSequence: 0, |
|
|
districtReference: 0, |
|
|
districtReference: 0, |
|
|
storeId: 2, |
|
|
|
|
|
|
|
|
storeId: "2F", |
|
|
|
|
|
remark: "", |
|
|
}); |
|
|
}); |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
@@ -380,6 +441,7 @@ const ShopDetail: React.FC = () => { |
|
|
shopCode: String(shopDetail!.code), |
|
|
shopCode: String(shopDetail!.code), |
|
|
loadingSequence: newTruck.loadingSequence, |
|
|
loadingSequence: newTruck.loadingSequence, |
|
|
districtReference: newTruck.districtReference, |
|
|
districtReference: newTruck.districtReference, |
|
|
|
|
|
remark: newTruck.storeId === "4F" ? (newTruck.remark?.trim() || null) : null, |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
// Refresh truck data after create |
|
|
// Refresh truck data after create |
|
|
@@ -387,6 +449,15 @@ const ShopDetail: React.FC = () => { |
|
|
const trucks = await findTruckLaneByShopIdClient(shopIdNum) as Truck[]; |
|
|
const trucks = await findTruckLaneByShopIdClient(shopIdNum) as Truck[]; |
|
|
setTruckData(trucks || []); |
|
|
setTruckData(trucks || []); |
|
|
setEditedTruckData(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(); |
|
|
handleCloseAddDialog(); |
|
|
} catch (err: any) { |
|
|
} catch (err: any) { |
|
|
console.error("Failed to create truck:", err); |
|
|
console.error("Failed to create truck:", err); |
|
|
@@ -501,13 +572,14 @@ const ShopDetail: React.FC = () => { |
|
|
<TableCell>Loading Sequence</TableCell> |
|
|
<TableCell>Loading Sequence</TableCell> |
|
|
<TableCell>District Reference</TableCell> |
|
|
<TableCell>District Reference</TableCell> |
|
|
<TableCell>Store ID</TableCell> |
|
|
<TableCell>Store ID</TableCell> |
|
|
|
|
|
<TableCell>Remark</TableCell> |
|
|
<TableCell>Actions</TableCell> |
|
|
<TableCell>Actions</TableCell> |
|
|
</TableRow> |
|
|
</TableRow> |
|
|
</TableHead> |
|
|
</TableHead> |
|
|
<TableBody> |
|
|
<TableBody> |
|
|
{truckData.length === 0 ? ( |
|
|
{truckData.length === 0 ? ( |
|
|
<TableRow> |
|
|
<TableRow> |
|
|
<TableCell colSpan={6} align="center"> |
|
|
|
|
|
|
|
|
<TableCell colSpan={7} align="center"> |
|
|
<Typography variant="body2" color="text.secondary"> |
|
|
<Typography variant="body2" color="text.secondary"> |
|
|
No Truck data available |
|
|
No Truck data available |
|
|
</Typography> |
|
|
</Typography> |
|
|
@@ -587,15 +659,81 @@ const ShopDetail: React.FC = () => { |
|
|
</TableCell> |
|
|
</TableCell> |
|
|
<TableCell> |
|
|
<TableCell> |
|
|
{isEditing ? ( |
|
|
{isEditing ? ( |
|
|
<TextField |
|
|
|
|
|
size="small" |
|
|
|
|
|
type="number" |
|
|
|
|
|
value={displayTruck?.storeId ?? 0} |
|
|
|
|
|
onChange={(e) => handleTruckFieldChange(index, "storeId", parseInt(e.target.value) || 0)} |
|
|
|
|
|
fullWidth |
|
|
|
|
|
/> |
|
|
|
|
|
|
|
|
<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> |
|
|
|
|
|
) : ( |
|
|
|
|
|
(() => { |
|
|
|
|
|
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; |
|
|
|
|
|
})() |
|
|
|
|
|
)} |
|
|
|
|
|
</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 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 ? "Enter or select remark" : "Not editable for this Store ID"} |
|
|
|
|
|
disabled={!isEditable} |
|
|
|
|
|
/> |
|
|
|
|
|
)} |
|
|
|
|
|
/> |
|
|
|
|
|
); |
|
|
|
|
|
})() |
|
|
) : ( |
|
|
) : ( |
|
|
truck.storeId !== null && truck.storeId !== undefined ? String(truck.storeId) : "-" |
|
|
|
|
|
|
|
|
String(truck.remark || "-") |
|
|
)} |
|
|
)} |
|
|
</TableCell> |
|
|
</TableCell> |
|
|
<TableCell> |
|
|
<TableCell> |
|
|
@@ -711,15 +849,50 @@ const ShopDetail: React.FC = () => { |
|
|
/> |
|
|
/> |
|
|
</Grid> |
|
|
</Grid> |
|
|
<Grid item xs={6}> |
|
|
<Grid item xs={6}> |
|
|
<TextField |
|
|
|
|
|
label="Store ID" |
|
|
|
|
|
type="number" |
|
|
|
|
|
fullWidth |
|
|
|
|
|
value={newTruck.storeId} |
|
|
|
|
|
onChange={(e) => setNewTruck({ ...newTruck, storeId: parseInt(e.target.value) || 2 })} |
|
|
|
|
|
disabled={saving} |
|
|
|
|
|
/> |
|
|
|
|
|
|
|
|
<FormControl fullWidth> |
|
|
|
|
|
<InputLabel>Store ID</InputLabel> |
|
|
|
|
|
<Select |
|
|
|
|
|
value={newTruck.storeId} |
|
|
|
|
|
label="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> |
|
|
</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="Remark" |
|
|
|
|
|
fullWidth |
|
|
|
|
|
placeholder="Enter or select remark" |
|
|
|
|
|
disabled={saving} |
|
|
|
|
|
/> |
|
|
|
|
|
)} |
|
|
|
|
|
/> |
|
|
|
|
|
</Grid> |
|
|
|
|
|
)} |
|
|
</Grid> |
|
|
</Grid> |
|
|
</Box> |
|
|
</Box> |
|
|
</DialogContent> |
|
|
</DialogContent> |
|
|
|