Selaa lähdekoodia

update

production_process
cyril.tsui 2 kuukautta sitten
vanhempi
commit
762e570f68
7 muutettua tiedostoa jossa 330 lisäystä ja 190 poistoa
  1. +3
    -1
      src/app/(main)/layout.tsx
  2. +6
    -2
      src/components/Breadcrumb/Breadcrumb.tsx
  3. +93
    -93
      src/components/NavigationContent/NavigationContent.tsx
  4. +12
    -0
      src/components/PickOrderSearch/ConsolidatedPickOrders.tsx
  5. +26
    -59
      src/components/PickOrderSearch/PickOrderSearch.tsx
  6. +100
    -0
      src/components/PickOrderSearch/PickOrders.tsx
  7. +90
    -35
      src/components/SearchResults/SearchResults.tsx

+ 3
- 1
src/app/(main)/layout.tsx Näytä tiedosto

@@ -51,7 +51,9 @@ export default async function MainLayout({
}}
>
<Stack spacing={2}>
<Breadcrumb />
<I18nProvider namespaces={["common"]}>
<Breadcrumb />
</I18nProvider>
{children}
</Stack>
</Box>


+ 6
- 2
src/components/Breadcrumb/Breadcrumb.tsx Näytä tiedosto

@@ -5,6 +5,7 @@ import Typography from "@mui/material/Typography";
import Link from "next/link";
import MUILink from "@mui/material/Link";
import { usePathname } from "next/navigation";
import { useTranslation } from "react-i18next";

const pathToLabelMap: { [path: string]: string } = {
"": "Overview",
@@ -21,11 +22,14 @@ const pathToLabelMap: { [path: string]: string } = {
"/inventory": "Inventory",
"/settings/importTesting": "Import Testing",
"/do": "Delivery Order",
"/pickOrder": "Pick Order",
"/po": "Purchase Order",
};

const Breadcrumb = () => {
const pathname = usePathname();
const segments = pathname.split("/");
const { t } = useTranslation("common")

return (
<Breadcrumbs>
@@ -36,7 +40,7 @@ const Breadcrumb = () => {
if (index === segments.length - 1) {
return (
<Typography key={index} color="text.primary">
{label}
{t(label)}
</Typography>
);
} else {
@@ -48,7 +52,7 @@ const Breadcrumb = () => {
component={Link}
href={href || "/"}
>
{label}
{t(label)}
</MUILink>
);
}


+ 93
- 93
src/components/NavigationContent/NavigationContent.tsx Näytä tiedosto

@@ -54,26 +54,26 @@ const NavigationContent: React.FC = () => {
label: "Pick Order",
path: "/pickOrder",
},
{
icon: <RequestQuote />,
label: "Cons. Pick Order",
path: "",
},
{
icon: <RequestQuote />,
label: "Delivery Pick Order",
path: "",
},
{
icon: <RequestQuote />,
label: "Warehouse",
path: "",
},
{
icon: <RequestQuote />,
label: "Location Transfer Order",
path: "",
},
// {
// icon: <RequestQuote />,
// label: "Cons. Pick Order",
// path: "",
// },
// {
// icon: <RequestQuote />,
// label: "Delivery Pick Order",
// path: "",
// },
// {
// icon: <RequestQuote />,
// label: "Warehouse",
// path: "",
// },
// {
// icon: <RequestQuote />,
// label: "Location Transfer Order",
// path: "",
// },
{
icon: <RequestQuote />,
label: "View item In-out And inventory Ledger",
@@ -81,45 +81,45 @@ const NavigationContent: React.FC = () => {
},
],
},
{
icon: <RequestQuote />,
label: "Production",
path: "",
children: [
{
icon: <RequestQuote />,
label: "Job Order",
path: "/production",
},
{
icon: <RequestQuote />,
label: "Job Order Traceablity ",
path: "",
},
{
icon: <RequestQuote />,
label: "Work Order",
path: "",
},
{
icon: <RequestQuote />,
label: "Work Order Traceablity ",
path: "",
},
],
},
{
icon: <RequestQuote />,
label: "Quality Control Log",
path: "",
children: [
{
icon: <RequestQuote />,
label: "Quality Control Log",
path: "",
},
],
},
// {
// icon: <RequestQuote />,
// label: "Production",
// path: "",
// children: [
// {
// icon: <RequestQuote />,
// label: "Job Order",
// path: "",
// },
// {
// icon: <RequestQuote />,
// label: "Job Order Traceablity ",
// path: "",
// },
// {
// icon: <RequestQuote />,
// label: "Work Order",
// path: "",
// },
// {
// icon: <RequestQuote />,
// label: "Work Order Traceablity ",
// path: "",
// },
// ],
// },
// {
// icon: <RequestQuote />,
// label: "Quality Control Log",
// path: "",
// children: [
// {
// icon: <RequestQuote />,
// label: "Quality Control Log",
// path: "",
// },
// ],
// },
{
icon: <RequestQuote />,
label: "Delivery",
@@ -132,40 +132,40 @@ const NavigationContent: React.FC = () => {
},
],
},
{
icon: <RequestQuote />,
label: "Report",
path: "",
children: [
{
icon: <RequestQuote />,
label: "report",
path: "",
},
],
},
{
icon: <RequestQuote />,
label: "Recipe",
path: "",
children: [
{
icon: <RequestQuote />,
label: "FG Recipe",
path: "",
},
{
icon: <RequestQuote />,
label: "SFG Recipe",
path: "",
},
{
icon: <RequestQuote />,
label: "Recipe",
path: "",
},
],
},
// {
// icon: <RequestQuote />,
// label: "Report",
// path: "",
// children: [
// {
// icon: <RequestQuote />,
// label: "report",
// path: "",
// },
// ],
// },
// {
// icon: <RequestQuote />,
// label: "Recipe",
// path: "",
// children: [
// {
// icon: <RequestQuote />,
// label: "FG Recipe",
// path: "",
// },
// {
// icon: <RequestQuote />,
// label: "SFG Recipe",
// path: "",
// },
// {
// icon: <RequestQuote />,
// label: "Recipe",
// path: "",
// },
// ],
// },
{
icon: <RequestQuote />,
label: "Scheduling",


+ 12
- 0
src/components/PickOrderSearch/ConsolidatedPickOrders.tsx Näytä tiedosto

@@ -0,0 +1,12 @@

interface Props {

}

const ConsolidatedPickOrders: React.FC<Props> = ({

}) => {
return <></>
}

export default ConsolidatedPickOrders;

+ 26
- 59
src/components/PickOrderSearch/PickOrderSearch.tsx Näytä tiedosto

@@ -8,6 +8,8 @@ import SearchResults, { Column } from "../SearchResults";
import { flatten, groupBy, intersectionWith, isEmpty, map, sortBy, sortedUniq, uniqBy, upperCase, upperFirst } from "lodash";
import { arrayToDateString, arrayToDayjs, dateStringToDayjs } from "@/app/utils/formatUtil";
import dayjs from "dayjs";
import { Button, Grid, Stack, Tab, Tabs, TabsProps } from "@mui/material";
import PickOrders from "./PickOrders";

interface Props {
pickOrders: PickOrderResult[];
@@ -27,6 +29,14 @@ const PickOrderSearch: React.FC<Props> = ({

const [filteredPickOrders, setFilteredPickOrders] = useState(pickOrders)

const [tabIndex, setTabIndex] = useState(0);
const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>(
(_e, newValue) => {
setTabIndex(newValue);
},
[],
);

const searchCriteria: Criterion<SearchParamNames>[] = useMemo(() => [
{ label: t("Code"), paramName: "code", type: "text" },
{ label: t("Target Date From"), label2: t("Target Date To"), paramName: "targetDate", type: "dateRange" },
@@ -43,9 +53,12 @@ const PickOrderSearch: React.FC<Props> = ({
"label")
},
{
label: t("Items"), paramName: "items", type: "autocomplete", multiple: true,
label: t("Items"), paramName: "items", type: "autocomplete", // multiple: true,
options: uniqBy(flatten(sortBy(
pickOrders.map((po) => po.items ? po.items.map((item) => ({ value: item.name, label: item.name, group: item.type })): []),
pickOrders.map((po) => po.items ? po.items.map((item) => ({
value: item.name, label: item.name,
// group: item.type
})) : []),
"label")), "value")
},
], [t])
@@ -54,52 +67,6 @@ const PickOrderSearch: React.FC<Props> = ({
setFilteredPickOrders(pickOrders)
}, [pickOrders])

const columns = useMemo<Column<PickOrderResult>[]>(() => [
{
name: "code",
label: t("Code"),
},
{
name: "consoCode",
label: t("Consolidated Code"),
renderCell: (params) => {
return params.consoCode ?? "N/A"
}
},
{
name: "type",
label: t("type"),
renderCell: (params) => {
return upperCase(params.type)
}
},
{
name: "items",
label: t("Items"),
renderCell: (params) => {
return params.items?.map((i) => i.name).join(", ")
}
},
{
name: "targetDate",
label: t("Target Date"),
renderCell: (params) => {
return arrayToDateString(params.targetDate)
}
},
{
name: "releasedBy",
label: t("Released By"),
},
{
name: "status",
label: t("Status"),
renderCell: (params) => {
return upperFirst(params.status)
}
},
], [t])

return (
<>
<SearchBox
@@ -107,27 +74,27 @@ const PickOrderSearch: React.FC<Props> = ({
onSearch={(query) => {
setFilteredPickOrders(
pickOrders.filter(
(po) =>{
(po) => {
const poTargetDateStr = arrayToDayjs(po.targetDate)

// console.log(intersectionWith(po.items?.map(item => item.name), query.items))
return po.code.toLowerCase().includes(query.code.toLowerCase())
&& (isEmpty(query.targetDate) || poTargetDateStr.isSame(query.targetDate) || poTargetDateStr.isAfter(query.targetDate))
&& (isEmpty(query.targetDateTo) || poTargetDateStr.isSame(query.targetDateTo) || poTargetDateStr.isBefore(query.targetDateTo))
&& (intersectionWith(["All"], query.items).length > 0 || intersectionWith(po.items?.map(item => item.name), query.items).length > 0)
&& (query.status.toLowerCase() == "all" || po.status.toLowerCase().includes(query.status.toLowerCase()))
&& (query.type.toLowerCase() == "all" || po.type.toLowerCase().includes(query.type.toLowerCase()))
&& (isEmpty(query.targetDate) || poTargetDateStr.isSame(query.targetDate) || poTargetDateStr.isAfter(query.targetDate))
&& (isEmpty(query.targetDateTo) || poTargetDateStr.isSame(query.targetDateTo) || poTargetDateStr.isBefore(query.targetDateTo))
&& (intersectionWith(["All"], query.items).length > 0 || intersectionWith(po.items?.map(item => item.name), query.items).length > 0)
&& (query.status.toLowerCase() == "all" || po.status.toLowerCase().includes(query.status.toLowerCase()))
&& (query.type.toLowerCase() == "all" || po.type.toLowerCase().includes(query.type.toLowerCase()))
}
)
)
}}
onReset={onReset}
/>
<SearchResults<PickOrderResult> items={filteredPickOrders} columns={columns} pagingController={{
pageNum: 0,
pageSize: 0,
totalCount: 0,
}} />
<Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable">
<Tab label={t("Pick Orders")} iconPosition="end" />
<Tab label={t("Consolidated Pick Orders")} iconPosition="end" />
</Tabs>
{tabIndex === 0 && <PickOrders filteredPickOrders={filteredPickOrders}/>}
</>
)
}


+ 100
- 0
src/components/PickOrderSearch/PickOrders.tsx Näytä tiedosto

@@ -0,0 +1,100 @@
import { Button, Grid } from "@mui/material";
import SearchResults, { Column } from "../SearchResults/SearchResults";
import { PickOrderResult } from "@/app/api/pickOrder";
import { useTranslation } from "react-i18next";
import { useCallback, useMemo, useState } from "react";
import { isEmpty, upperCase, upperFirst } from "lodash";
import { arrayToDateString } from "@/app/utils/formatUtil";

interface Props {
filteredPickOrders: PickOrderResult[],
}

const PickOrders: React.FC<Props> = ({
filteredPickOrders
}) => {
const { t } = useTranslation("pickOrder")
const [selectedRows, setSelectedRows] = useState<(string | number)[]>([]);

const handleConsolidatedRows = useCallback(() => {

}, [selectedRows])

const columns = useMemo<Column<PickOrderResult>[]>(() => [
{
name: "id",
label: "",
type: "checkbox",
disabled: (params) => {
return !isEmpty(params.consoCode);
}
},
{
name: "code",
label: t("Code"),
},
{
name: "consoCode",
label: t("Consolidated Code"),
renderCell: (params) => {
return params.consoCode ?? "N/A"
}
},
{
name: "type",
label: t("type"),
renderCell: (params) => {
return upperCase(params.type)
}
},
{
name: "items",
label: t("Items"),
renderCell: (params) => {
return params.items?.map((i) => i.name).join(", ")
}
},
{
name: "targetDate",
label: t("Target Date"),
renderCell: (params) => {
return arrayToDateString(params.targetDate)
}
},
{
name: "releasedBy",
label: t("Released By"),
},
{
name: "status",
label: t("Status"),
renderCell: (params) => {
return upperFirst(params.status)
}
},
], [t])

return (
<Grid container rowGap={1}>
<Grid item xs={3}>
<Button
disabled={selectedRows.length < 1}
variant="outlined"
>
{t("Consolidate")}
</Button>
</Grid>
<Grid item xs={12}>
<SearchResults<PickOrderResult> items={filteredPickOrders} columns={columns} pagingController={{
pageNum: 0,
pageSize: 0
}}
checkboxIds={selectedRows}
setCheckboxIds={setSelectedRows}
/>
</Grid>
</Grid>
)
}

export default PickOrders;

+ 90
- 35
src/components/SearchResults/SearchResults.tsx Näytä tiedosto

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

import React, { Dispatch, SetStateAction } from "react";
import React, { ChangeEvent, Dispatch, MouseEvent, SetStateAction, useCallback, useState } from "react";
import Paper from "@mui/material/Paper";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
@@ -14,6 +14,7 @@ import TableRow from "@mui/material/TableRow";
import IconButton, { IconButtonOwnProps } from "@mui/material/IconButton";
import {
ButtonOwnProps,
Checkbox,
Icon,
IconOwnProps,
SxProps,
@@ -26,7 +27,7 @@ export interface ResultWithId {
id: string | number;
}

type ColumnType = "icon" | "decimal" | "integer";
type ColumnType = "icon" | "decimal" | "integer" | "checkbox";

interface BaseColumn<T extends ResultWithId> {
name: keyof T;
@@ -56,6 +57,13 @@ interface IntegerColumn<T extends ResultWithId> extends BaseColumn<T> {
type: "integer";
}

interface CheckboxColumn<T extends ResultWithId> extends BaseColumn<T> {
type: "checkbox";
disabled?: (params: T) => boolean;
// checkboxIds: readonly (string | number)[],
// setCheckboxIds: (ids: readonly (string | number)[]) => void
}

interface ColumnWithAction<T extends ResultWithId> extends BaseColumn<T> {
onClick: (item: T) => void;
buttonIcon: React.ReactNode;
@@ -67,6 +75,7 @@ export type Column<T extends ResultWithId> =
| BaseColumn<T>
| IconColumn<T>
| DecimalColumn<T>
| CheckboxColumn<T>
| ColumnWithAction<T>;

interface Props<T extends ResultWithId> {
@@ -77,9 +86,11 @@ interface Props<T extends ResultWithId> {
setPagingController?: Dispatch<SetStateAction<{
pageNum: number;
pageSize: number;
}>>
pagingController: { pageNum: number; pageSize: number;};
}>>
pagingController: { pageNum: number; pageSize: number; };
isAutoPaging?: boolean;
checkboxIds?: (string | number)[];
setCheckboxIds?: Dispatch<SetStateAction<(string | number)[]>>;
}

function isActionColumn<T extends ResultWithId>(
@@ -106,14 +117,20 @@ function isIntegerColumn<T extends ResultWithId>(
return column.type === "integer";
}

function isCheckboxColumn<T extends ResultWithId>(
column: Column<T>
): column is CheckboxColumn<T> {
return column.type === "checkbox";
}

// Icon Component Functions
function convertObjectKeysToLowercase<T extends object>(
obj: T
): object | undefined {
return obj
? Object.fromEntries(
Object.entries(obj).map(([key, value]) => [key.toLowerCase(), value])
)
Object.entries(obj).map(([key, value]) => [key.toLowerCase(), value])
)
: undefined;
}

@@ -144,9 +161,9 @@ function handleIconIcons<T extends ResultWithId>(

return column.icon ?? <CheckCircleOutlineIcon fontSize="small" />;
}
export const defaultPagingController:{ pageNum: number; pageSize: number} = {
"pageNum": 1,
"pageSize": 10,
export const defaultPagingController: { pageNum: number; pageSize: number } = {
"pageNum": 1,
"pageSize": 10,
}
function SearchResults<T extends ResultWithId>({
items,
@@ -155,7 +172,9 @@ function SearchResults<T extends ResultWithId>({
pagingController,
setPagingController,
isAutoPaging = true,
totalCount
totalCount,
checkboxIds = [],
setCheckboxIds = undefined,
}: Props<T>) {
const [page, setPage] = React.useState(0);
const [rowsPerPage, setRowsPerPage] = React.useState(10);
@@ -189,6 +208,28 @@ function SearchResults<T extends ResultWithId>({
}
};

// checkbox
const handleRowClick = useCallback((event: MouseEvent<unknown>, id: string | number) => {
if (setCheckboxIds) {
const selectedIndex = checkboxIds.indexOf(id);
let newSelected: (string | number)[] = [];

if (selectedIndex === -1) {
newSelected = newSelected.concat(checkboxIds, id);
} else if (selectedIndex === 0) {
newSelected = newSelected.concat(checkboxIds.slice(1));
} else if (selectedIndex === checkboxIds.length - 1) {
newSelected = newSelected.concat(checkboxIds.slice(0, -1));
} else if (selectedIndex > 0) {
newSelected = newSelected.concat(
checkboxIds.slice(0, selectedIndex),
checkboxIds.slice(selectedIndex + 1),
);
}
setCheckboxIds(newSelected);
}
}, [checkboxIds])

const table = (
<>
<TableContainer sx={{ maxHeight: 440 }}>
@@ -209,29 +250,16 @@ function SearchResults<T extends ResultWithId>({
<TableBody>
{isAutoPaging
? items
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
.map((item) => {
return (
<TableRow hover tabIndex={-1} key={item.id}>
{columns.map((column, idx) => {
const columnName = column.name;

return (
<TabelCells
key={`${columnName.toString()}-${idx}`}
column={column}
columnName={columnName}
idx={idx}
item={item}
/>
);
})}
</TableRow>
);
})
: items.map((item) => {
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
.map((item) => {
return (
<TableRow hover tabIndex={-1} key={item.id}>
<TableRow
hover
tabIndex={-1}
key={item.id}
onClick={setCheckboxIds ? (event) => handleRowClick(event, item.id) : undefined}
role={setCheckboxIds ? "checkbox" : undefined}
>
{columns.map((column, idx) => {
const columnName = column.name;

@@ -242,12 +270,33 @@ function SearchResults<T extends ResultWithId>({
columnName={columnName}
idx={idx}
item={item}
checkboxIds={checkboxIds}
/>
);
})}
</TableRow>
);
})}
})
: items.map((item) => {
return (
<TableRow hover tabIndex={-1} key={item.id}>
{columns.map((column, idx) => {
const columnName = column.name;

return (
<TabelCells
key={`${columnName.toString()}-${idx}`}
column={column}
columnName={columnName}
idx={idx}
item={item}
checkboxIds={checkboxIds}
/>
);
})}
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
@@ -255,8 +304,8 @@ function SearchResults<T extends ResultWithId>({
rowsPerPageOptions={[10, 25, 100]}
component="div"
count={!totalCount || totalCount == 0
? items.length
: totalCount
? items.length
: totalCount
}
// count={
// !pagingController || pagingController.totalCount == 0
@@ -280,6 +329,7 @@ interface TableCellsProps<T extends ResultWithId> {
columnName: keyof T;
idx: number;
item: T;
checkboxIds: (string | number)[];
}

function TabelCells<T extends ResultWithId>({
@@ -287,7 +337,10 @@ function TabelCells<T extends ResultWithId>({
columnName,
idx,
item,
checkboxIds = [],
}: TableCellsProps<T>) {
const isItemSelected = checkboxIds.includes(item.id);

return (
<TableCell
align={column.align}
@@ -309,6 +362,8 @@ function TabelCells<T extends ResultWithId>({
<>{decimalFormatter.format(Number(item[columnName]))}</>
) : isIntegerColumn(column) ? (
<>{integerFormatter.format(Number(item[columnName]))}</>
) : isCheckboxColumn(column) ? (
<Checkbox disabled={column.disabled ? column.disabled(item) : undefined} checked={isItemSelected} />
) : column.renderCell ? (
column.renderCell(item)
) : (


Ladataan…
Peruuta
Tallenna