Pārlūkot izejas kodu

quick ui fix

master
kelvin.yau pirms 2 mēnešiem
vecāks
revīzija
ecc07c1a4d
1 mainītis faili ar 360 papildinājumiem un 97 dzēšanām
  1. +360
    -97
      src/components/FinishedGoodSearch/FGPickOrderCard.tsx

+ 360
- 97
src/components/FinishedGoodSearch/FGPickOrderCard.tsx Parādīt failu

@@ -1,115 +1,378 @@
"use client";

import { FGPickOrderResponse } from "@/app/api/pickOrder/actions";
import { Box, Card, CardContent, Grid, Stack, TextField, Button } from "@mui/material";
import { Box, Button, Grid, Stack, Typography, Select, MenuItem, FormControl, InputLabel, Card, CardContent } from "@mui/material";
import { useCallback, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import QrCodeIcon from '@mui/icons-material/QrCode';
import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil";
import { useSession } from "next-auth/react";
import { SessionWithTokens } from "@/config/authConfig";
import { fetchStoreLaneSummary, assignByLane, type StoreLaneSummary } from "@/app/api/pickOrder/actions";
import Swal from "sweetalert2";
import dayjs from "dayjs";
import FinishedGoodFloorLanePanel from "./FinishedGoodFloorLanePanel";

type Props = {
fgOrder: FGPickOrderResponse;
onQrCodeClick: (pickOrderId: number) => void;
interface Props {
onPickOrderAssigned?: () => void;
};
}

const FGPickOrderCard: React.FC<Props> = ({ fgOrder, onQrCodeClick, onPickOrderAssigned }) => {
const FinishedGoodFloorLanePanel: React.FC<Props> = ({ onPickOrderAssigned }) => {
const { t } = useTranslation("pickOrder");
const { data: session } = useSession() as { data: SessionWithTokens | null };
const currentUserId = session?.id ? parseInt(session.id) : undefined;

return (
<>
<Box sx={{ mb: 2 }}>
<FinishedGoodFloorLanePanel onPickOrderAssigned={onPickOrderAssigned} />
</Box>
<Card sx={{ display: "block" }}>
<CardContent component={Stack} spacing={4}>
<Box>
<Grid container spacing={2} columns={{ xs: 6, sm: 12 }}>
<Grid item xs={6}>
<TextField
label={t("Delivery Code")}
fullWidth
disabled={true}
value={fgOrder.deliveryNo}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("Pick Order Code")}
fullWidth
disabled={true}
value={fgOrder.pickOrderCode}
//helperText={fgOrder.pickOrderConsoCode}
/>
</Grid>
const [summary2F, setSummary2F] = useState<StoreLaneSummary | null>(null);
const [summary4F, setSummary4F] = useState<StoreLaneSummary | null>(null);
const [isLoadingSummary, setIsLoadingSummary] = useState(false);
const [isAssigning, setIsAssigning] = useState(false);
const [selectedDate, setSelectedDate] = useState<string>("today");

<Grid item xs={6}>
<TextField
label={t("Store ID")}
fullWidth
disabled={true}
value={fgOrder.storeId}
/>
</Grid>
const loadSummaries = useCallback(async () => {
setIsLoadingSummary(true);
try {
const [s2, s4] = await Promise.all([
fetchStoreLaneSummary("2/F"),
fetchStoreLaneSummary("4/F")
]);
setSummary2F(s2);
setSummary4F(s4);
} catch (error) {
console.error("Error loading summaries:", error);
} finally {
setIsLoadingSummary(false);
}
}, []);

<Grid item xs={6}>
<TextField
label={t("Shop Name")}
fullWidth
disabled={true}
value={fgOrder.shopName}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("Delivery Date")}
fullWidth
disabled={true}
value={dayjs(fgOrder.deliveryDate).format(OUTPUT_DATE_FORMAT)}
/>
</Grid>
<Grid item xs={12}>
<TextField
label={t("Shop Address")}
fullWidth
disabled={true}
value={fgOrder.shopAddress}
multiline
rows={2}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("Departure Time")}
fullWidth
disabled={true}
value={fgOrder.DepartureTime}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("Truck No.")}
fullWidth
disabled={true}
value={fgOrder.truckLanceCode}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("Ticket No.")}
fullWidth
disabled={true}
value={fgOrder.ticketNo}
/>
</Grid>
useEffect(() => {
loadSummaries();
}, [loadSummaries]);

</Grid>
const handleAssignByLane = useCallback(async (
storeId: string,
truckDepartureTime: string,
truckLanceCode: string
) => {
if (!currentUserId) {
console.error("Missing user id in session");
return;
}
setIsAssigning(true);
try {
const res = await assignByLane(currentUserId, storeId, truckLanceCode, truckDepartureTime);
if (res.code === "SUCCESS") {
console.log("✅ Successfully assigned pick order from lane", truckLanceCode);
window.dispatchEvent(new CustomEvent('pickOrderAssigned'));
loadSummaries(); // 刷新按钮状态
onPickOrderAssigned?.();
} else if (res.code === "USER_BUSY") {
Swal.fire({
icon: "warning",
title: t("Warning"),
text: t("You already have a pick order in progess. Please complete it first before taking next pick order."),
confirmButtonText: t("Confirm"),
confirmButtonColor: "#8dba00"
});
window.dispatchEvent(new CustomEvent('pickOrderAssigned'));
} else if (res.code === "NO_ORDERS") {
Swal.fire({
icon: "info",
title: t("Info"),
text: t("No available pick order(s) for this lane."),
confirmButtonText: t("Confirm"),
confirmButtonColor: "#8dba00"
});
} else {
console.log("ℹ️ Assignment result:", res.message);
}
} catch (error) {
console.error("❌ Error assigning by lane:", error);
Swal.fire({
icon: "error",
title: t("Error"),
text: t("Error occurred during assignment."),
confirmButtonText: t("Confirm"),
confirmButtonColor: "#8dba00"
});
} finally {
setIsAssigning(false);
}
}, [currentUserId, t, loadSummaries, onPickOrderAssigned]);

const getDateLabel = (offset: number) => {
return dayjs().add(offset, 'day').format('YYYY-MM-DD');
};

// Flatten rows to create one box per lane
const flattenRows = (rows: any[]) => {
const flattened: any[] = [];
rows.forEach(row => {
row.lanes.forEach((lane: any) => {
flattened.push({
truckDepartureTime: row.truckDepartureTime,
lane: lane
});
});
});
return flattened;
};

return (
<Card sx={{ mb: 2 }}>
<CardContent>
{/* Date Selector Dropdown */}
<Box sx={{ maxWidth: 300, mb: 2 }}>
<FormControl fullWidth size="small">
<InputLabel id="date-select-label">{t("Select Date")}</InputLabel>
<Select
labelId="date-select-label"
id="date-select"
value={selectedDate}
label={t("Select Date")}
onChange={(e) => setSelectedDate(e.target.value)}
>
<MenuItem value="today">
{t("Today")} ({getDateLabel(0)})
</MenuItem>
<MenuItem value="tomorrow">
{t("Tomorrow")} ({getDateLabel(1)})
</MenuItem>
<MenuItem value="dayAfterTomorrow">
{t("Day After Tomorrow")} ({getDateLabel(2)})
</MenuItem>
</Select>
</FormControl>
</Box>

{/* Grid containing both floors */}
<Grid container spacing={2}>
{/* 2/F 楼层面板 */}
<Grid item xs={12}>
<Stack direction="row" spacing={2} alignItems="flex-start">
{/* Floor Label */}
<Box
sx={{
border: '2px solid #1976d2',
borderRadius: 1,
backgroundColor: '#e3f2fd',
px: 2,
py: 1,
minWidth: 80,
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<Typography
variant="h6"
sx={{
fontWeight: 600,
color: '#1976d2'
}}
>
2/F
</Typography>
</Box>
{/* Content Box */}
<Box
sx={{
border: '1px solid #e0e0e0',
borderRadius: 1,
p: 1,
backgroundColor: '#fafafa',
flex: 1
}}
>
{isLoadingSummary ? (
<Typography variant="caption">Loading...</Typography>
) : !summary2F?.rows || summary2F.rows.length === 0 ? (
<Typography
variant="body2"
color="text.secondary"
sx={{
fontWeight: 600,
fontSize: '1rem',
textAlign: 'center',
py: 1
}}
>
{t("No entries available")}
</Typography>
) : (
<Grid container spacing={1}>
{flattenRows(summary2F.rows).slice(0, 4).map((item, idx) => (
<Grid item xs={12} sm={6} md={3} key={idx}>
<Stack
direction="row"
spacing={1}
alignItems="center"
sx={{
border: '1px solid #e0e0e0',
borderRadius: 0.5,
p: 1,
backgroundColor: '#fff',
height: '100%'
}}
>
{/* Time on the left */}
<Typography
variant="body2"
sx={{
fontWeight: 600,
fontSize: '1rem',
minWidth: 50,
whiteSpace: 'nowrap'
}}
>
{item.truckDepartureTime}
</Typography>
{/* Single Button on the right */}
<Button
variant="outlined"
size="medium"
disabled={item.lane.unassigned === 0 || isAssigning}
onClick={() => handleAssignByLane("2/F", item.truckDepartureTime, item.lane.truckLanceCode)}
sx={{
flex: 1,
fontSize: '1.1rem',
py: 1,
px: 1.5,
borderWidth: 1,
borderColor: '#ccc',
fontWeight: 500,
'&:hover': {
borderColor: '#999',
backgroundColor: '#f5f5f5'
}
}}
>
{`${item.lane.truckLanceCode} (${item.lane.unassigned}/${item.lane.total})`}
</Button>
</Stack>
</Grid>
))}
</Grid>
)}
</Box>
</Stack>
</Grid>

{/* 4/F 楼层面板 */}
<Grid item xs={12}>
<Stack direction="row" spacing={2} alignItems="flex-start">
{/* Floor Label */}
<Box
sx={{
border: '2px solid #1976d2',
borderRadius: 1,
backgroundColor: '#e3f2fd',
px: 2,
py: 1,
minWidth: 80,
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<Typography
variant="h6"
sx={{
fontWeight: 600,
color: '#1976d2'
}}
>
4/F
</Typography>
</Box>
{/* Content Box */}
<Box
sx={{
border: '1px solid #e0e0e0',
borderRadius: 1,
p: 1,
backgroundColor: '#fafafa',
flex: 1
}}
>
{isLoadingSummary ? (
<Typography variant="caption">Loading...</Typography>
) : !summary4F?.rows || summary4F.rows.length === 0 ? (
<Typography
variant="body2"
color="text.secondary"
sx={{
fontWeight: 600,
fontSize: '1rem',
textAlign: 'center',
py: 1
}}
>
{t("No entries available")}
</Typography>
) : (
<Grid container spacing={1}>
{flattenRows(summary4F.rows).slice(0, 4).map((item, idx) => (
<Grid item xs={12} sm={6} md={3} key={idx}>
<Stack
direction="row"
spacing={1}
alignItems="center"
sx={{
border: '1px solid #e0e0e0',
borderRadius: 0.5,
p: 1,
backgroundColor: '#fff',
height: '100%'
}}
>
{/* Time on the left */}
<Typography
variant="body2"
sx={{
fontWeight: 600,
fontSize: '1rem',
minWidth: 50,
whiteSpace: 'nowrap'
}}
>
{item.truckDepartureTime}
</Typography>
{/* Single Button on the right */}
<Button
variant="outlined"
size="medium"
disabled={item.lane.unassigned === 0 || isAssigning}
onClick={() => handleAssignByLane("4/F", item.truckDepartureTime, item.lane.truckLanceCode)}
sx={{
flex: 1,
fontSize: '1.1rem',
py: 1,
px: 1.5,
borderWidth: 1,
borderColor: '#ccc',
fontWeight: 500,
'&:hover': {
borderColor: '#999',
backgroundColor: '#f5f5f5'
}
}}
>
{`${item.lane.truckLanceCode} (${item.lane.unassigned}/${item.lane.total})`}
</Button>
</Stack>
</Grid>
))}
</Grid>
)}
</Box>
</Stack>
</Grid>
</Grid>
</CardContent>
</Card>
</>
);
};

export default FGPickOrderCard;
export default FinishedGoodFloorLanePanel;

Notiek ielāde…
Atcelt
Saglabāt