소스 검색

po+ do

production_process
MSI\derek 2 달 전
부모
커밋
f173f2617f
8개의 변경된 파일356개의 추가작업 그리고 128개의 파일을 삭제
  1. +0
    -3
      src/app/(main)/do/page.tsx
  2. +14
    -0
      src/app/api/do/actions.tsx
  3. +1
    -0
      src/app/api/do/index.tsx
  4. +3
    -0
      src/components/DoDetail/DoDetail.tsx
  5. +270
    -86
      src/components/DoSearch/DoSearch.tsx
  6. +33
    -32
      src/components/PoSearch/PoSearch.tsx
  7. +5
    -5
      src/components/PoSearch/PoSearchWrapper.tsx
  8. +30
    -2
      src/components/SearchBox/SearchBox.tsx

+ 0
- 3
src/app/(main)/do/page.tsx 파일 보기

@@ -19,9 +19,6 @@ const DeliveryOrder: React.FC = async () => {
flexWrap={"wrap"}
rowGap={2}
>
<Typography variant="h4" marginInlineEnd={2}>
{t("Delivery Order")}
</Typography>
</Stack>
<Suspense fallback={<DoSearch.Loading />}>
<DoSearch />


+ 14
- 0
src/app/api/do/actions.tsx 파일 보기

@@ -0,0 +1,14 @@
"use server";
import { BASE_API_URL } from "@/config/api";
// import { ServerFetchError, serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil";
import { revalidateTag } from "next/cache";
import { cache } from "react";
import { serverFetchJson } from "@/app/utils/fetchUtil";
import { QcItemResult } from "../settings/qcItem";
import { RecordsRes } from "../utils";
import { DoResult } from ".";
import { GridRowId, GridRowSelectionModel } from "@mui/x-data-grid";

export interface CreateConsoDoInput {
ids: GridRowSelectionModel
}

+ 1
- 0
src/app/api/do/index.tsx 파일 보기

@@ -6,6 +6,7 @@ export interface DoResult {
id: number,
code: string,
orderDate: string,
estimatedArrivalDate: string,
status: string,
shopName: string,
}


+ 3
- 0
src/components/DoDetail/DoDetail.tsx 파일 보기

@@ -0,0 +1,3 @@
"use client"

// const doDetail =

+ 270
- 86
src/components/DoSearch/DoSearch.tsx 파일 보기

@@ -1,6 +1,6 @@
"use client";

import { DoResult } from "@/app/api/do"
import { DoResult } from "@/app/api/do";
import { useRouter } from "next/navigation";
import { useCallback, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
@@ -11,105 +11,289 @@ import { arrayToDateString, arrayToDayjs } from "@/app/utils/formatUtil";
import SearchBox from "../SearchBox/SearchBox";
import SearchResults from "../SearchResults/SearchResults";
import { EditNote } from "@mui/icons-material";
import InputDataGrid from "../InputDataGrid";
import { CreateConsoDoInput } from "@/app/api/do/actions";
import { TableRow } from "../InputDataGrid/InputDataGrid";
import { FooterPropsOverrides, GridColDef, GridRowModel, GridToolbarContainer, useGridApiRef } from "@mui/x-data-grid";
import {
FormProvider,
SubmitErrorHandler,
SubmitHandler,
useForm,
} from "react-hook-form";
import { Box, Button, Grid, Stack, Typography } from "@mui/material";
import StyledDataGrid from "../StyledDataGrid";
import { GridRowSelectionModel } from "@mui/x-data-grid";

type Props = {
dos: DoResult[]
}
dos: DoResult[];
};

type SearchQuery = Partial<Omit<DoResult, "id">>;
type SearchParamNames = keyof SearchQuery;
// put all this into a new component
// ConsoDoForm
type EntryError =
| {
[field in keyof DoResult]?: string;
}
| undefined;
type DoRow = TableRow<Partial<DoResult>, EntryError>;

const DoSearch: React.FC<Props> = ({
dos
}) => {
const DoSearch: React.FC<Props> = ({ dos }) => {
const apiRef = useGridApiRef();

const [filteredDos, setFilteredDos] = useState<DoResult[]>(dos);
const { t } = useTranslation("do");
const router = useRouter();
const formProps = useForm<CreateConsoDoInput>({
defaultValues: {},
});
const errors = formProps.formState.errors;

const searchCriteria: Criterion<SearchParamNames>[] = useMemo(() => [
{ label: t("Code"), paramName: "code", type: "text" },
{ label: t("Order Date From"), label2: t("Order Date To"), paramName: "orderDate", type: "dateRange" },
{ label: t("Shop Name"), paramName: "shopName", type: "text" },
{
label: t("Status"), paramName: "status", type: "autocomplete", options: sortBy(
uniqBy(dos.map((_do) => ({ value: _do.status, label: t(upperFirst(_do.status)) })), "value"),
"label")
},
], [t, dos])
const tttt = [
{
id: 1,
code: "string",
orderDate: "2025-01-01",
estimatedArrivalDate: "2025-01-01",
status: "string",
shopName: "string",
},
{
id: 2,
code: "string1",
orderDate: "2025-01-01",
estimatedArrivalDate: "2025-01-01",
status: "string",
shopName: "string",
},
{
id: 3,
code: "string2",
orderDate: "2025-01-01",
estimatedArrivalDate: "2025-01-01",
status: "string",
shopName: "string",
},
]
// const [filteredDos, setFilteredDos] = useState<DoResult[]>(dos);
const [filteredDos, setFilteredDos] = useState<DoResult[]>(tttt);
const { t } = useTranslation("do");
const router = useRouter();
const [rowSelectionModel, setRowSelectionModel] = useState<GridRowSelectionModel>([])

const onReset = useCallback(() => {
setFilteredDos(dos)
}, [dos])
const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
() => [
{ label: t("Code"), paramName: "code", type: "text" },

const onDetailClick = useCallback(
(doResult: DoResult) => {
router.push(`/do/edit?id=${doResult.id}`);
},
[router]
);
{
label: t("Order Date From"),
label2: t("Order Date To"),
paramName: "orderDate",
type: "dateRange",
},
{ label: t("Shop Name"), paramName: "shopName", type: "text" },
{
label: t("Status"),
paramName: "status",
type: "autocomplete",
options: sortBy(
uniqBy(
dos.map((_do) => ({
value: _do.status,
label: t(upperFirst(_do.status)),
})),
"value"
),
"label"
),
},
],
[t, dos]
);

const onReset = useCallback(() => {
setFilteredDos(dos);
}, [dos]);

const columns = useMemo<Column<DoResult>[]>(() => [
{
name: "id",
label: t("Details"),
onClick: onDetailClick,
buttonIcon: <EditNote />,
},
{
name: "code",
label: t("Code")
},
{
name: "shopName",
label: t("Shop Name")
const onDetailClick = useCallback(
(doResult: DoResult) => {
router.push(`/do/edit?id=${doResult.id}`);
},
[router]
);

const validationTest = useCallback(
(
newRow: GridRowModel<DoRow>
// rowModel: GridRowSelectionModel
): EntryError => {
const error: EntryError = {};
console.log(newRow);
// if (!newRow.lowerLimit) {
// error["lowerLimit"] = "lower limit cannot be null"
// }
// if (newRow.lowerLimit && newRow.upperLimit && newRow.lowerLimit > newRow.upperLimit) {
// error["lowerLimit"] = "lower limit should not be greater than upper limit"
// error["upperLimit"] = "lower limit should not be greater than upper limit"
// }
return Object.keys(error).length > 0 ? error : undefined;
},
[]
);

const columns = useMemo<GridColDef[]>(
() => [
// {
// name: "id",
// label: t("Details"),
// onClick: onDetailClick,
// buttonIcon: <EditNote />,
// },
{
field: "code",
headerName: "code",
flex: 1,
},
{
field: "shopName",
headerName: t("Shop Name"),
flex: 1,
},
{
field: "orderDate",
headerName: t("Order Date"),
flex: 1,
renderCell: (params) => {
return params.row.orderDate
? arrayToDateString(params.row.orderDate)
: "N/A";
},
{
name: "orderDate",
label: t("Order Date"),
renderCell: (params) => {
return params.orderDate ? arrayToDateString(params.orderDate) : "N/A"
},
},
{
field: "estimatedArrivalDate",
headerName: t("Estimated Arrival"),
flex: 1,
renderCell: (params) => {
return params.row.estimatedArrivalDate
? arrayToDateString(params.row.estimatedArrivalDate)
: "N/A";
},
{
name: "status",
label: t("Status"),
renderCell: (params) => {
return t(upperFirst(params.status))
},
},
{
field: "status",
headerName: t("Status"),
flex: 1,
renderCell: (params) => {
return t(upperFirst(params.row.status));
},
], [t])

return (
<>
<SearchBox
criteria={searchCriteria}
onSearch={(query) => {
setFilteredDos(
dos.filter(
(_do) => {
const doOrderDateStr = arrayToDayjs(_do.orderDate)
},
],
[t, arrayToDateString]
);

return _do.code.toLowerCase().includes(query.code.toLowerCase())
&& _do.shopName.toLowerCase().includes(query.shopName.toLowerCase())
&& (query.status == "All" || _do.status.toLowerCase().includes(query.status.toLowerCase()))
&& (isEmpty(query.orderDate) || doOrderDateStr.isSame(query.orderDate) || doOrderDateStr.isAfter(query.orderDate))
&& (isEmpty(query.orderDateTo) || doOrderDateStr.isSame(query.orderDateTo) || doOrderDateStr.isBefore(query.orderDateTo))
}
)
)
}}
onReset={onReset}
/>
<SearchResults<DoResult> items={filteredDos} columns={columns} pagingController={{
pageNum: 0,
pageSize: 0,
totalCount: 0,
}} />
</>
)
}
const onSubmit = useCallback<SubmitHandler<CreateConsoDoInput>>(
async (data, event) => {
let hasErrors = false;
console.log(errors);
console.log(data);
},
[]
);
const onSubmitError = useCallback<SubmitErrorHandler<CreateConsoDoInput>>(
(errors) => {},
[]
);
return (
<>
<FormProvider {...formProps}>
<Stack
spacing={2}
component="form"
onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)}
>
<Grid container>
<Grid item xs={8}>
<Typography variant="h4" marginInlineEnd={2}>
{t("Delivery Order")}
</Typography>
</Grid>
<Grid
item
xs={4}
display="flex"
justifyContent="end"
alignItems="end"
>
<Button
name="submit"
variant="contained"
// startIcon={<Check />}
type="submit"
>
{t("Create")}
</Button>
</Grid>
</Grid>
<SearchBox
criteria={searchCriteria}
onSearch={(query) => {
setFilteredDos(
dos.filter((_do) => {
const doOrderDateStr = arrayToDayjs(_do.orderDate);
return (
_do.code.toLowerCase().includes(query.code.toLowerCase()) &&
_do.shopName
.toLowerCase()
.includes(query.shopName.toLowerCase()) &&
(query.status == "All" ||
_do.status
.toLowerCase()
.includes(query.status.toLowerCase())) &&
(isEmpty(query.orderDate) ||
doOrderDateStr.isSame(query.orderDate) ||
doOrderDateStr.isAfter(query.orderDate)) &&
(isEmpty(query.orderDateTo) ||
doOrderDateStr.isSame(query.orderDateTo) ||
doOrderDateStr.isBefore(query.orderDateTo))
);
})
);
}}
onReset={onReset}
/>
<StyledDataGrid
checkboxSelection={true}
rows={filteredDos}
columns={columns}
rowSelectionModel={rowSelectionModel}
onRowSelectionModelChange={(newRowSelectionModel) => {
setRowSelectionModel(newRowSelectionModel);
formProps.setValue("ids", newRowSelectionModel)
}}
slots={{
footer: FooterToolbar,
noRowsOverlay: NoRowsOverlay,
}}
/>
</Stack>
</FormProvider>
</>
);
};

export default DoSearch;
const FooterToolbar: React.FC<FooterPropsOverrides> = ({ child }) => {
return <GridToolbarContainer sx={{ p: 2 }}>{child}</GridToolbarContainer>;
};
const NoRowsOverlay: React.FC = () => {
const { t } = useTranslation("home");
return (
<Box
display="flex"
justifyContent="center"
alignItems="center"
height="100%"
>
<Typography variant="caption">{t("Add some entries!")}</Typography>
</Box>
);
};
export default DoSearch;

+ 33
- 32
src/components/PoSearch/PoSearch.tsx 파일 보기

@@ -48,8 +48,12 @@ const PoSearch: React.FC<Props> = ({
{
label: t("Status"),
paramName: "status",
type: "select",
options: [t(`pending`), t(`receiving`), t(`completed`)],
type: "select-labelled",
options: [
{label: t(`pending`), value: `pending`},
{label: t(`receiving`), value: `receiving`},
{label: t(`completed`), value: `completed`},
]
},
{
label: t("Escalated"),
@@ -87,7 +91,7 @@ const PoSearch: React.FC<Props> = ({
label: t("OrderDate"),
renderCell: (params) => {
return dayjs(params.orderDate)
.add(-1, "month")
// .add(-1, "month")
.format(OUTPUT_DATE_FORMAT);
},
},
@@ -106,21 +110,12 @@ const PoSearch: React.FC<Props> = ({
name: "escalated",
label: t("Escalated"),
renderCell: (params) => {
console.log(params.escalated)
return params.escalated ? (
<NotificationIcon color="warning" />
) : undefined;
},
},
// {
// name: "name",
// label: t("Name"),
// },
// {
// name: "action",
// label: t(""),
// buttonIcon: <GridDeleteIcon />,
// onClick: onDeleteClick,
// },
],
[filteredPo]
);
@@ -144,6 +139,7 @@ const PoSearch: React.FC<Props> = ({
filterArgs: Record<string, number>
) => {
console.log(pagingController);
console.log(filterArgs);
const params = {
...pagingController,
...filterArgs,
@@ -152,16 +148,16 @@ const PoSearch: React.FC<Props> = ({
// const res = await testing(params);
if (res) {
console.log(res);
// setFilteredPo(res.records);
// setTotalCount(res.total);
setFilteredPo(res.records);
setTotalCount(res.total);
}
},
[fetchPoListClient, pagingController]
);

// useEffect(() => {
// newPageFetch(pagingController, filterArgs);
// }, [newPageFetch, pagingController, filterArgs]);
useEffect(() => {
newPageFetch(pagingController, filterArgs);
}, [newPageFetch, pagingController, filterArgs]);
return (
<>
<Grid container>
@@ -183,26 +179,31 @@ const PoSearch: React.FC<Props> = ({
<SearchBox
criteria={searchCriteria}
onSearch={(query) => {
// setFilterArgs({ ...query });
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"))))
)
})
);
console.log(query)
setFilterArgs({
code: query.code,
status: query.status === "All" ? "" : query.status,
escalated: query.escalated === 'All' ? undefined : query.escalated === t("Escalated")
});
// 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"))))
// )
// })
// );
}}
onReset={onReset}
/>
<SearchResults<PoResult>
items={filteredPo}
columns={columns}
// pagingController={pagingController}
// setPagingController={setPagingController}
// totalCount={totalCount}
// isAutoPaging={false}
pagingController={pagingController}
setPagingController={setPagingController}
totalCount={totalCount}
isAutoPaging={false}
/>
</>
</>


+ 5
- 5
src/components/PoSearch/PoSearchWrapper.tsx 파일 보기

@@ -32,11 +32,11 @@ const PoSearchWrapper: React.FC<Props> & SubComponents = async (
po,
warehouse,
] = await Promise.all([
// fetchPoList({
// "pageNum": 1,
// "pageSize": 10,
// }),
fetchPoList(),
fetchPoList({
"pageNum": 1,
"pageSize": 10,
}),
// fetchPoList(),
fetchWarehouseList(),
]);
const fixPoDate = po.records.map((p) => {


+ 30
- 2
src/components/SearchBox/SearchBox.tsx 파일 보기

@@ -34,6 +34,11 @@ interface BaseCriterion<T extends string> {
handleSelectionChange?: (selectedOptions: T[]) => void;
}

interface OptionWithLabel<T extends string> {
label: string,
value: any,
}

interface TextCriterion<T extends string> extends BaseCriterion<T> {
type: "text";
}
@@ -43,6 +48,11 @@ interface SelectCriterion<T extends string> extends BaseCriterion<T> {
options: string[];
}

interface SelectWithLabelCriterion<T extends string> extends BaseCriterion<T> {
type: "select-labelled";
options: OptionWithLabel<T>[];
}

interface MultiSelectCriterion<T extends string> extends BaseCriterion<T> {
type: "multi-select";
options: T[];
@@ -75,6 +85,7 @@ interface DateCriterion<T extends string> extends BaseCriterion<T> {
export type Criterion<T extends string> =
| TextCriterion<T>
| SelectCriterion<T>
| SelectWithLabelCriterion<T>
| DateRangeCriterion<T>
| DateCriterion<T>
| MultiSelectCriterion<T>
@@ -102,7 +113,7 @@ function SearchBox<T extends string>({
(acc, c) => {
return {
...acc,
[c.paramName]: (c.type === "select" || (c.type === "autocomplete" && !Boolean(c.multiple)) ? "All"
[c.paramName]: (c.type === "select" || c.type === "select-labelled" || (c.type === "autocomplete" && !Boolean(c.multiple)) ? "All"
: (c.type === "autocomplete" && Boolean(c.multiple)) ? [defaultAll.value]: "")
};
},
@@ -200,13 +211,30 @@ function SearchBox<T extends string>({
>
<MenuItem value={"All"}>{t("All")}</MenuItem>
{c.options.map((option) => (
<MenuItem key={option} value={option}>
<MenuItem key={(option)} value={option}>
{option}
</MenuItem>
))}
</Select>
</FormControl>
)}
{c.type === "select-labelled" && (
<FormControl fullWidth>
<InputLabel>{c.label}</InputLabel>
<Select
label={c.label}
onChange={makeSelectChangeHandler(c.paramName)}
value={inputs[c.paramName]}
>
<MenuItem value={"All"}>{t("All")}</MenuItem>
{c.options.map((option) => (
<MenuItem key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</Select>
</FormControl>
)}
{c.type === "autocomplete" && (
<Autocomplete
groupBy={


불러오는 중...
취소
저장