CANCERYS\kw093 2 тижднів тому
джерело
коміт
26b5a43d1b
2 змінених файлів з 252 додано та 109 видалено
  1. +232
    -85
      src/components/PoDetail/PoDetail.tsx
  2. +20
    -24
      src/components/PoDetail/PoInfoCard.tsx

+ 232
- 85
src/components/PoDetail/PoDetail.tsx Переглянути файл

@@ -65,6 +65,14 @@ import { getCustomWidth } from "@/app/utils/commonUtil";
import PoInfoCard from "./PoInfoCard";
import { decimalFormatter, integerFormatter } from "@/app/utils/formatUtil";



import { fetchPoListClient } from "@/app/api/po/actions";
import { List, ListItem, ListItemButton, ListItemText, Divider } from "@mui/material";
import { useRouter } from "next/navigation";



type Props = {
po: PoResult;
qc: QcItemWithChecks[];
@@ -77,6 +85,89 @@ type EntryError =
}
| undefined;
// type PolRow = TableRow<Partial<StockInLine>, EntryError>;
const PoSearchList: React.FC<{
poList: PoResult[];
selectedPoId: number;
onSelect: (po: PoResult) => void;
}> = ({ poList, selectedPoId, onSelect }) => {
const { t } = useTranslation("purchaseOrder");
const [searchTerm, setSearchTerm] = useState('');

const filteredPoList = useMemo(() => {
if (searchTerm.trim() === '') {
return poList;
}
return poList.filter(poItem =>
poItem.code.toLowerCase().includes(searchTerm.toLowerCase()) ||
poItem.supplier?.toLowerCase().includes(searchTerm.toLowerCase()) ||
t(`${poItem.status.toLowerCase()}`).toLowerCase().includes(searchTerm.toLowerCase())
);
}, [poList, searchTerm, t]);

return (
<Paper sx={{ p: 2, maxHeight: "400px", overflow: "auto" }}>
<Typography variant="h6" gutterBottom>
{t("Purchase Orders")}
</Typography>
<TextField
label={t("Search")}
variant="outlined"
size="small"
fullWidth
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
sx={{ mb: 2 }}
InputProps={{
startAdornment: (
<Typography variant="body2" color="text.secondary" sx={{ mr: 1 }}>
</Typography>
),
}}
/>
<List dense>
{filteredPoList.map((poItem, index) => (
<div key={poItem.id}>
<ListItem disablePadding>
<ListItemButton
selected={selectedPoId === poItem.id}
onClick={() => onSelect(poItem)}
sx={{
"&.Mui-selected": {
backgroundColor: "primary.light",
"&:hover": {
backgroundColor: "primary.light",
},
},
}}
>
<ListItemText
primary={
<Typography variant="body2" noWrap>
{poItem.code}
</Typography>
}
secondary={
<Typography variant="caption" color="text.secondary">
{t(`${poItem.status.toLowerCase()}`)}
</Typography>
}
/>
</ListItemButton>
</ListItem>
{index < filteredPoList.length - 1 && <Divider />}
</div>
))}
</List>
{searchTerm && (
<Typography variant="caption" color="text.secondary" sx={{ mt: 1, display: "block" }}>
{t("Found")} {filteredPoList.length} {t("of")} {poList.length} {t("items")}
</Typography>
)}
</Paper>
);
};

const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
const cameras = useContext(CameraContext);
console.log(cameras);
@@ -93,6 +184,90 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
const searchParams = useSearchParams();
// const [currPoStatus, setCurrPoStatus] = useState(purchaseOrder.status);





const router = useRouter();
const [poList, setPoList] = useState<PoResult[]>([]);
const [selectedPoId, setSelectedPoId] = useState(po.id);
const currentPoId = searchParams.get('id');
const [searchTerm, setSearchTerm] = useState('');

const filteredPoList = useMemo(() => {
if (searchTerm.trim() === '') {
return poList;
}
return poList.filter(poItem =>
poItem.code.toLowerCase().includes(searchTerm.toLowerCase()) ||
poItem.supplier?.toLowerCase().includes(searchTerm.toLowerCase()) ||
t(`${poItem.status.toLowerCase()}`).toLowerCase().includes(searchTerm.toLowerCase())
);
}, [poList, searchTerm, t]);

const fetchPoList = useCallback(async () => {
try {
const result = await fetchPoListClient({ limit: 20, offset: 0 });
if (result && result.records) {
setPoList(result.records);
}
} catch (error) {
console.error("Failed to fetch PO list:", error);
}
}, []);

const handlePoSelect = useCallback(
(selectedPo: PoResult) => {
setSelectedPoId(selectedPo.id);
router.push(`/po/edit?id=${selectedPo.id}&start=true`, { scroll: false });
},
[router]
);

const fetchPoDetail = useCallback(async (poId: string) => {
try {
const result = await fetchPoInClient(parseInt(poId));
if (result) {
setPurchaseOrder(result);
setRows(result.pol || []);
if (result.pol && result.pol.length > 0) {
setRow(result.pol[0]);
setStockInLine(result.pol[0].stockInLine);
setProcessedQty(result.pol[0].processed);
}
}
} catch (error) {
console.error("Failed to fetch PO detail:", error);
}
}, []);

useEffect(() => {
if (currentPoId && currentPoId !== selectedPoId.toString()) {
setSelectedPoId(parseInt(currentPoId));
fetchPoDetail(currentPoId);
}
}, [currentPoId, selectedPoId, fetchPoDetail]);

useEffect(() => {
fetchPoList();
}, [fetchPoList]);

useEffect(() => {
if (currentPoId) {
setSelectedPoId(parseInt(currentPoId));
}
}, [currentPoId]);







const removeParam = (paramToRemove: string) => {
const newParams = new URLSearchParams(searchParams.toString());
newParams.delete(paramToRemove);
@@ -315,116 +490,84 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {

return (
<>
<Stack
spacing={2}
// component="form"
// onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)}
>
<Stack spacing={2}>
{/* Area1: title */}
<Grid container xs={12} justifyContent="start">
<Grid item>
<Typography mb={2} variant="h4">
{/* {purchaseOrder.code} - {currPoStatus} */}
{purchaseOrder.code} -{" "}
{t(`${purchaseOrder.status.toLowerCase()}`)}
</Typography>
</Grid>
</Grid>
{true ? (<Grid container xs={12} justifyContent="start">
<Grid item xs={3}>
<TextField
label={t("dnNo")}
type="text" // Use type="text" to allow validation in the change handler
variant="outlined"
// value={secondReceiveQty}
// onChange={handleChange}
InputProps={{
inputProps: {
min: 0, // Optional: set a minimum value
step: 1 // Optional: set the step for the number input
}
}}
/>
</Grid>

{/* area2: dn info */}
<Grid container spacing={2}>
{/* left side select po */}
<Grid item xs={3}>
<TextField
label={t("dnDate")}
type="text" // Use type="text" to allow validation in the change handler
variant="outlined"
defaultValue={"11/08/2025"}
// value={secondReceiveQty}
// onChange={handleChange}
InputProps={{
inputProps: {
min: 0, // Optional: set a minimum value
step: 1 // Optional: set the step for the number input
}
}}
/>
{/* <Button
onClick={buttonData.onClick}
disabled={buttonData.disabled}
color={buttonData.buttonColor as ButtonProps["color"]}
startIcon={buttonData.buttonIcon}
>
{buttonData.buttonText}
</Button> */}
</Grid>
<Grid
item
xs={6}
display="flex"
// justifyContent="center"
alignItems="center"
>
<Button variant={"contained"} onClick={onOpenScanner}>提交</Button>
</Grid>
</Grid>) : undefined}
<Grid container xs={12} justifyContent="space-between">

{/* <Grid item xs={4}> */}
{/* scanner */}
{/* </Grid> */}
<Grid
item
xs={4}
display="flex"
justifyContent="end"
alignItems="end"
>
<QrModal
open={isOpenScanner}
onClose={onCloseScanner}
warehouse={warehouse}
<PoSearchList
poList={poList}
selectedPoId={selectedPoId}
onSelect={handlePoSelect}
/>
{/* <Button onClick={onOpenScanner}>{t("Accept submit")}</Button> */}
{/* <Button onClick={onOpenScanner}>{t("bind")}</Button> */}
</Grid>
</Grid>
{/* tab 1 */}
<Grid rowSpacing={2} container sx={{ display: tabIndex === 0 ? "block" : "none" }}>
<Grid item xs={12}>

{/* right side po info */}
<Grid item xs={9}>
<PoInfoCard po={purchaseOrder} />
{true ? (
<Stack spacing={2}>
<TextField
label={t("dnNo")}
type="text"
variant="outlined"
fullWidth
InputProps={{
inputProps: {
min: 0,
step: 1
}
}}
/>
<TextField
label={t("dnDate")}
type="text"
variant="outlined"
defaultValue={"11/08/2025"}
fullWidth
InputProps={{
inputProps: {
min: 0,
step: 1
}
}}
/>
<Button variant="contained" onClick={onOpenScanner} fullWidth>
提交
</Button>
</Stack>
) : undefined}
</Grid>
</Grid>



{/* Area4: Main Table */}
<Grid container xs={12} justifyContent="start">
<Grid item xs={12}>
<TableContainer component={Paper} sx={{ width: 'fit-content', overflow: 'auto' }}>
{/* <TableContainer component={Paper} sx={{width: getCustomWidth(), overflow: 'auto' }}> */}
<Table aria-label="collapsible table" stickyHeader>
<TableHead>
<TableRow>
{/* <TableCell /> for the collapse button */}
<TableCell sx={{ width: '125px' }}>{t("itemNo")}</TableCell>
<TableCell align="left" sx={{ width: '125px' }}>{t("itemName")}</TableCell>
<TableCell align="right">{t("qty")}</TableCell>
<TableCell align="right">{t("processed")}</TableCell>
<TableCell align="left">{t("uom")}</TableCell>
<TableCell align="right">{t("total weight")}</TableCell>
{/* <TableCell align="left">{t("weight unit")}</TableCell> */}
<TableCell align="right">{`${t("price")} (HKD)`}</TableCell>
{/* <TableCell align="left">{t("expiryDate")}</TableCell> */}
<TableCell align="left"sx={{ width: '75px' }}>{t("status")}</TableCell>
{/* start == true && firstInQty == null ? no hide : hide*/}
<TableCell align="left" sx={{ width: '75px' }}>{t("status")}</TableCell>
{renderFieldCondition(FIRST_IN_FIELD) ? <TableCell align="right">{t("receivedQty")}</TableCell> : undefined}
{/* start == true && firstInQty == null ? hide and disabled : no hide*/}
{renderFieldCondition(SECOND_IN_FIELD) ? <TableCell align="center" sx={{ width: '150px' }}>{t("dnQty")}(以訂單單位計算)</TableCell> : undefined}
<TableCell align="center" sx={{ width: '100px' }}></TableCell>
</TableRow>
@@ -437,6 +580,10 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
</Table>
</TableContainer>
</Grid>
</Grid>

{/* area5: selected item info */}
<Grid container xs={12} justifyContent="start">
<Grid item xs={12}>
<Typography variant="h6">已選擇: {row.itemNo}-{row.itemName}</Typography>
</Grid>
@@ -486,4 +633,4 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
</>
);
};
export default PoDetail;
export default PoDetail;

+ 20
- 24
src/components/PoDetail/PoInfoCard.tsx Переглянути файл

@@ -32,30 +32,25 @@ const PoInfoCard: React.FC<Props> = async (
<Card sx={{ display: "block" }}>
<CardContent component={Stack} spacing={4}>
<Box>
<Grid container spacing={2} columns={{ xs: 6, sm: 12 }}>
<Grid item xs={6}>
<TextField
// {
// ...register("status")
// }
label={t("Supplier")}
fullWidth
disabled={true}
value={po.supplier}
/>
</Grid>
<Grid item xs={6} />
<Grid item xs={6}>
<TextField
// {
// ...register("code")
// }
label={t("Order Date")}
fullWidth
disabled={true}
value={arrayToDateString(po.orderDate)}
/>
</Grid>
<Stack spacing={2}>
{/* 第一行:供应商 - 占据全宽 */}
<TextField
label={t("Supplier")}
fullWidth
disabled={true}
value={po.supplier}
/>
{/* 第二行:订单日期和预计到货日期 - 各占一半 */}
<Grid container spacing={2}>
<Grid item xs={6}>
<TextField
label={t("Order Date")}
fullWidth
disabled={true}
value={arrayToDateString(po.orderDate)}
/>
</Grid>
<Grid item xs={6}>
<TextField
// {
@@ -68,6 +63,7 @@ const PoInfoCard: React.FC<Props> = async (
/>
</Grid>
</Grid>
</Stack>
</Box>
</CardContent>
</Card>


Завантаження…
Відмінити
Зберегти