浏览代码

update posearch/podetail ui

master
CANCERYS\kw093 1周前
父节点
当前提交
42dd76c49d
共有 4 个文件被更改,包括 110 次插入42 次删除
  1. +30
    -28
      src/components/PoDetail/PoDetail.tsx
  2. +78
    -12
      src/components/PoSearch/PoSearch.tsx
  3. +1
    -1
      src/components/SearchBox/SearchBox.tsx
  4. +1
    -1
      src/i18n/zh/purchaseOrder.json

+ 30
- 28
src/components/PoDetail/PoDetail.tsx 查看文件

@@ -26,6 +26,8 @@ import {
TabsProps,
TextField,
Typography,
Checkbox,
FormControlLabel,
} from "@mui/material";
import { useTranslation } from "react-i18next";
// import InputDataGrid, { TableRow } from "../InputDataGrid/InputDataGrid";
@@ -69,7 +71,7 @@ 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";
//import { useRouter } from "next/navigation";



@@ -105,7 +107,7 @@ const PoSearchList: React.FC<{
}, [poList, searchTerm, t]);

return (
<Paper sx={{ p: 2, maxHeight: "400px", overflow: "auto" }}>
<Paper sx={{ p: 2, maxHeight: "400px", overflow: "auto", minWidth: "300px" }}>
<Typography variant="h6" gutterBottom>
{t("Purchase Orders")}
</Typography>
@@ -125,14 +127,15 @@ const PoSearchList: React.FC<{
),
}}
/>
<List dense>
<List dense sx={{ width: '100%' }}>
{filteredPoList.map((poItem, index) => (
<div key={poItem.id}>
<ListItem disablePadding>
<ListItem disablePadding sx={{ width: '100%' }}>
<ListItemButton
selected={selectedPoId === poItem.id}
onClick={() => onSelect(poItem)}
sx={{
width: '100%',
"&.Mui-selected": {
backgroundColor: "primary.light",
"&:hover": {
@@ -143,7 +146,7 @@ const PoSearchList: React.FC<{
>
<ListItemText
primary={
<Typography variant="body2" noWrap>
<Typography variant="body2" sx={{ wordBreak: 'break-all' }}>
{poItem.code}
</Typography>
}
@@ -191,42 +194,41 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {



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

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);
if (selectedIdsParam) {

const selectedIds = selectedIdsParam.split(',').map(id => parseInt(id));
const promises = selectedIds.map(id => fetchPoInClient(id));
const results = await Promise.all(promises);
setPoList(results.filter(Boolean));
} else {

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);
}
}, []);
}, [selectedIdsParam]);

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

const newSelectedIds = selectedIdsParam || selectedPo.id.toString();
router.push(`/po/edit?id=${selectedPo.id}&start=true&selectedIds=${newSelectedIds}`, { scroll: false });
},
[router]
[router, selectedIdsParam]
);

const fetchPoDetail = useCallback(async (poId: string) => {
@@ -511,9 +513,9 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
</Grid>

{/* area2: dn info */}
<Grid container spacing={2}>
<Grid container spacing={3} sx={{ maxWidth: 'fit-content' }}>
{/* left side select po */}
<Grid item xs={3}>
<Grid item xs={4}>
<PoSearchList
poList={poList}
selectedPoId={selectedPoId}
@@ -522,7 +524,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
</Grid>

{/* right side po info */}
<Grid item xs={9}>
<Grid item xs={8}>
<PoInfoCard po={purchaseOrder} />
{true ? (
<Stack spacing={2}>


+ 78
- 12
src/components/PoSearch/PoSearch.tsx 查看文件

@@ -17,6 +17,7 @@ import { fetchPoListClient, testing } from "@/app/api/po/actions";
import dayjs from "dayjs";
import { arrayToDateString, OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil";
import arraySupport from "dayjs/plugin/arraySupport";
import { Checkbox, Box } from "@mui/material";
dayjs.extend(arraySupport);

type Props = {
@@ -34,6 +35,8 @@ const PoSearch: React.FC<Props> = ({
warehouse,
totalCount: initTotalCount,
}) => {
const [selectedPoIds, setSelectedPoIds] = useState<number[]>([]);
const [selectAll, setSelectAll] = useState(false);
const [filteredPo, setFilteredPo] = useState<PoResult[]>(po);
const [filterArgs, setFilterArgs] = useState<Record<string, any>>({});
const { t } = useTranslation("purchaseOrder");
@@ -44,7 +47,8 @@ const PoSearch: React.FC<Props> = ({
const [totalCount, setTotalCount] = useState(initTotalCount);
const searchCriteria: Criterion<SearchParamNames>[] = useMemo(() => {
const searchCriteria: Criterion<SearchParamNames>[] = [
{ label: t("Po No."), paramName: "code", type: "text" },
{ label: t("Po No."), paramName: "code", type: "text" },
{ label: t("Supplier"), paramName: "supplier", type: "text" },
{ label: t("Order Date"), label2: t("Order Date To"), paramName: "orderDate", type: "dateRange" },
{
label: t("Escalated"),
@@ -69,15 +73,57 @@ const PoSearch: React.FC<Props> = ({

const onDetailClick = useCallback(
(po: PoResult) => {
router.push(`/po/edit?id=${po.id}&start=true`);
setSelectedPoIds([]);
setSelectAll(false);
router.push(`/po/edit?id=${po.id}&start=true&selectedIds=${po.id}`);
},
[router],
);

const onDeleteClick = useCallback((po: PoResult) => {}, []);
// handle single checkbox selection
const handleSelectPo = useCallback((poId: number, checked: boolean) => {
if (checked) {
setSelectedPoIds(prev => [...prev, poId]);
} else {
setSelectedPoIds(prev => prev.filter(id => id !== poId));
}
}, []);

// 处理全选
const handleSelectAll = useCallback((checked: boolean) => {
if (checked) {
setSelectedPoIds(filteredPo.map(po => po.id));
setSelectAll(true);
} else {
setSelectedPoIds([]);
setSelectAll(false);
}
}, [filteredPo]);

// navigate to PoDetail page
const handleGoToPoDetail = useCallback(() => {
if (selectedPoIds.length > 0) {
const selectedIdsParam = selectedPoIds.join(',');
const firstPoId = selectedPoIds[0];
router.push(`/po/edit?id=${firstPoId}&start=true&selectedIds=${selectedIdsParam}`);
}
}, [selectedPoIds, router]);

const columns = useMemo<Column<PoResult>[]>(
() => [
{
name: "id" as keyof PoResult,
label: "Select",
renderCell: (params) => (
<Checkbox
checked={selectedPoIds.includes(params.id)}
onChange={(e) => handleSelectPo(params.id, e.target.checked)}
onClick={(e) => e.stopPropagation()}
/>
),
width: 60,
},
{
name: "id",
label: t("Details"),
@@ -132,7 +178,7 @@ const PoSearch: React.FC<Props> = ({
},
},
],
[filteredPo],
[selectedPoIds, handleSelectPo, onDetailClick, t], // only keep necessary dependencies
);

const onReset = useCallback(() => {
@@ -177,6 +223,14 @@ const PoSearch: React.FC<Props> = ({
useEffect(() => {
newPageFetch(pagingController, filterArgs);
}, [newPageFetch, pagingController, filterArgs]);
// when filteredPo changes, update select all state
useEffect(() => {
if (filteredPo.length > 0 && selectedPoIds.length === filteredPo.length) {
setSelectAll(true);
} else {
setSelectAll(false);
}
}, [filteredPo, selectedPoIds]);
return (
<>
<Grid container>
@@ -201,6 +255,7 @@ const PoSearch: React.FC<Props> = ({
console.log(query);
setFilterArgs({
code: query.code,
supplier: query.supplier,
status: query.status === "All" ? "" : query.status,
escalated:
query.escalated === "All"
@@ -211,15 +266,8 @@ const PoSearch: React.FC<Props> = ({
orderDate: query.orderDate === "Invalid Date" ? "" : query.orderDate,
orderDateTo: query.orderDateTo === "Invalid Date" ? "" : query.orderDateTo,
});
// setFilteredPo((prev) =>
// po.filter((p) => {
// return (
// p.code.toLowerCase().includes(query.code.toLowerCase()) &&
// (query.status === "All" || t(`${p.status.toLowerCase()}`) === query.status) &&
// (query.escalated === "All" || (p.escalated === Boolean((query.escalated) === t("Escalated"))))
// )
// })
// );
setSelectedPoIds([]); // reset selected po ids
setSelectAll(false); // reset select all
}}
onReset={onReset}
/>
@@ -231,6 +279,24 @@ const PoSearch: React.FC<Props> = ({
totalCount={totalCount}
isAutoPaging={false}
/>
{/* add select all and view selected button */}
<Box sx={{ mb: 2, display: 'flex', alignItems: 'center', gap: 2 }}>
<Button
variant="outlined"
onClick={() => handleSelectAll(!selectAll)}
startIcon={<Checkbox checked={selectAll} />}
>
{t("Select All")} ({selectedPoIds.length} / {filteredPo.length})
</Button>
<Button
variant="contained"
onClick={handleGoToPoDetail}
disabled={selectedPoIds.length === 0}
color="primary"
>
{t("View Selected")} ({selectedPoIds.length})
</Button>
</Box>
</>
</>
);


+ 1
- 1
src/components/SearchBox/SearchBox.tsx 查看文件

@@ -433,7 +433,7 @@ function SearchBox<T extends string>({
);
})}
</Grid>
<CardActions sx={{ justifyContent: "flex-end" }}>
<CardActions sx={{ justifyContent: "flex-start" }}>
<Button
variant="text"
startIcon={<RestartAlt />}


+ 1
- 1
src/i18n/zh/purchaseOrder.json 查看文件

@@ -1,6 +1,6 @@
{
"Purchase Order": "採購訂單",
"Purchase Receipt": "採購來貨",
"Purchase Receipt": "處理採購來貨",
"Code": "編號",
"OrderDate": "下單日期",
"Order Date": "下單日期",


正在加载...
取消
保存