| @@ -51,7 +51,9 @@ export default async function MainLayout({ | |||||
| }} | }} | ||||
| > | > | ||||
| <Stack spacing={2}> | <Stack spacing={2}> | ||||
| <Breadcrumb /> | |||||
| <I18nProvider namespaces={["common"]}> | |||||
| <Breadcrumb /> | |||||
| </I18nProvider> | |||||
| {children} | {children} | ||||
| </Stack> | </Stack> | ||||
| </Box> | </Box> | ||||
| @@ -5,6 +5,7 @@ import Typography from "@mui/material/Typography"; | |||||
| import Link from "next/link"; | import Link from "next/link"; | ||||
| import MUILink from "@mui/material/Link"; | import MUILink from "@mui/material/Link"; | ||||
| import { usePathname } from "next/navigation"; | import { usePathname } from "next/navigation"; | ||||
| import { useTranslation } from "react-i18next"; | |||||
| const pathToLabelMap: { [path: string]: string } = { | const pathToLabelMap: { [path: string]: string } = { | ||||
| "": "Overview", | "": "Overview", | ||||
| @@ -21,11 +22,14 @@ const pathToLabelMap: { [path: string]: string } = { | |||||
| "/inventory": "Inventory", | "/inventory": "Inventory", | ||||
| "/settings/importTesting": "Import Testing", | "/settings/importTesting": "Import Testing", | ||||
| "/do": "Delivery Order", | "/do": "Delivery Order", | ||||
| "/pickOrder": "Pick Order", | |||||
| "/po": "Purchase Order", | |||||
| }; | }; | ||||
| const Breadcrumb = () => { | const Breadcrumb = () => { | ||||
| const pathname = usePathname(); | const pathname = usePathname(); | ||||
| const segments = pathname.split("/"); | const segments = pathname.split("/"); | ||||
| const { t } = useTranslation("common") | |||||
| return ( | return ( | ||||
| <Breadcrumbs> | <Breadcrumbs> | ||||
| @@ -36,7 +40,7 @@ const Breadcrumb = () => { | |||||
| if (index === segments.length - 1) { | if (index === segments.length - 1) { | ||||
| return ( | return ( | ||||
| <Typography key={index} color="text.primary"> | <Typography key={index} color="text.primary"> | ||||
| {label} | |||||
| {t(label)} | |||||
| </Typography> | </Typography> | ||||
| ); | ); | ||||
| } else { | } else { | ||||
| @@ -48,7 +52,7 @@ const Breadcrumb = () => { | |||||
| component={Link} | component={Link} | ||||
| href={href || "/"} | href={href || "/"} | ||||
| > | > | ||||
| {label} | |||||
| {t(label)} | |||||
| </MUILink> | </MUILink> | ||||
| ); | ); | ||||
| } | } | ||||
| @@ -54,26 +54,26 @@ const NavigationContent: React.FC = () => { | |||||
| label: "Pick Order", | label: "Pick Order", | ||||
| path: "/pickOrder", | 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 />, | icon: <RequestQuote />, | ||||
| label: "View item In-out And inventory Ledger", | 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 />, | icon: <RequestQuote />, | ||||
| label: "Delivery", | 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 />, | icon: <RequestQuote />, | ||||
| label: "Scheduling", | label: "Scheduling", | ||||
| @@ -0,0 +1,12 @@ | |||||
| interface Props { | |||||
| } | |||||
| const ConsolidatedPickOrders: React.FC<Props> = ({ | |||||
| }) => { | |||||
| return <></> | |||||
| } | |||||
| export default ConsolidatedPickOrders; | |||||
| @@ -8,6 +8,8 @@ import SearchResults, { Column } from "../SearchResults"; | |||||
| import { flatten, groupBy, intersectionWith, isEmpty, map, sortBy, sortedUniq, uniqBy, upperCase, upperFirst } from "lodash"; | import { flatten, groupBy, intersectionWith, isEmpty, map, sortBy, sortedUniq, uniqBy, upperCase, upperFirst } from "lodash"; | ||||
| import { arrayToDateString, arrayToDayjs, dateStringToDayjs } from "@/app/utils/formatUtil"; | import { arrayToDateString, arrayToDayjs, dateStringToDayjs } from "@/app/utils/formatUtil"; | ||||
| import dayjs from "dayjs"; | import dayjs from "dayjs"; | ||||
| import { Button, Grid, Stack, Tab, Tabs, TabsProps } from "@mui/material"; | |||||
| import PickOrders from "./PickOrders"; | |||||
| interface Props { | interface Props { | ||||
| pickOrders: PickOrderResult[]; | pickOrders: PickOrderResult[]; | ||||
| @@ -27,6 +29,14 @@ const PickOrderSearch: React.FC<Props> = ({ | |||||
| const [filteredPickOrders, setFilteredPickOrders] = useState(pickOrders) | 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(() => [ | const searchCriteria: Criterion<SearchParamNames>[] = useMemo(() => [ | ||||
| { label: t("Code"), paramName: "code", type: "text" }, | { label: t("Code"), paramName: "code", type: "text" }, | ||||
| { label: t("Target Date From"), label2: t("Target Date To"), paramName: "targetDate", type: "dateRange" }, | { 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") | ||||
| }, | }, | ||||
| { | { | ||||
| label: t("Items"), paramName: "items", type: "autocomplete", multiple: true, | |||||
| label: t("Items"), paramName: "items", type: "autocomplete", // multiple: true, | |||||
| options: uniqBy(flatten(sortBy( | 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") | "label")), "value") | ||||
| }, | }, | ||||
| ], [t]) | ], [t]) | ||||
| @@ -54,52 +67,6 @@ const PickOrderSearch: React.FC<Props> = ({ | |||||
| setFilteredPickOrders(pickOrders) | setFilteredPickOrders(pickOrders) | ||||
| }, [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 ( | return ( | ||||
| <> | <> | ||||
| <SearchBox | <SearchBox | ||||
| @@ -107,27 +74,27 @@ const PickOrderSearch: React.FC<Props> = ({ | |||||
| onSearch={(query) => { | onSearch={(query) => { | ||||
| setFilteredPickOrders( | setFilteredPickOrders( | ||||
| pickOrders.filter( | pickOrders.filter( | ||||
| (po) =>{ | |||||
| (po) => { | |||||
| const poTargetDateStr = arrayToDayjs(po.targetDate) | const poTargetDateStr = arrayToDayjs(po.targetDate) | ||||
| // console.log(intersectionWith(po.items?.map(item => item.name), query.items)) | // console.log(intersectionWith(po.items?.map(item => item.name), query.items)) | ||||
| return po.code.toLowerCase().includes(query.code.toLowerCase()) | 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} | 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}/>} | |||||
| </> | </> | ||||
| ) | ) | ||||
| } | } | ||||
| @@ -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; | |||||
| @@ -1,6 +1,6 @@ | |||||
| "use client"; | "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 Paper from "@mui/material/Paper"; | ||||
| import Table from "@mui/material/Table"; | import Table from "@mui/material/Table"; | ||||
| import TableBody from "@mui/material/TableBody"; | import TableBody from "@mui/material/TableBody"; | ||||
| @@ -14,6 +14,7 @@ import TableRow from "@mui/material/TableRow"; | |||||
| import IconButton, { IconButtonOwnProps } from "@mui/material/IconButton"; | import IconButton, { IconButtonOwnProps } from "@mui/material/IconButton"; | ||||
| import { | import { | ||||
| ButtonOwnProps, | ButtonOwnProps, | ||||
| Checkbox, | |||||
| Icon, | Icon, | ||||
| IconOwnProps, | IconOwnProps, | ||||
| SxProps, | SxProps, | ||||
| @@ -26,7 +27,7 @@ export interface ResultWithId { | |||||
| id: string | number; | id: string | number; | ||||
| } | } | ||||
| type ColumnType = "icon" | "decimal" | "integer"; | |||||
| type ColumnType = "icon" | "decimal" | "integer" | "checkbox"; | |||||
| interface BaseColumn<T extends ResultWithId> { | interface BaseColumn<T extends ResultWithId> { | ||||
| name: keyof T; | name: keyof T; | ||||
| @@ -56,6 +57,13 @@ interface IntegerColumn<T extends ResultWithId> extends BaseColumn<T> { | |||||
| type: "integer"; | 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> { | interface ColumnWithAction<T extends ResultWithId> extends BaseColumn<T> { | ||||
| onClick: (item: T) => void; | onClick: (item: T) => void; | ||||
| buttonIcon: React.ReactNode; | buttonIcon: React.ReactNode; | ||||
| @@ -67,6 +75,7 @@ export type Column<T extends ResultWithId> = | |||||
| | BaseColumn<T> | | BaseColumn<T> | ||||
| | IconColumn<T> | | IconColumn<T> | ||||
| | DecimalColumn<T> | | DecimalColumn<T> | ||||
| | CheckboxColumn<T> | |||||
| | ColumnWithAction<T>; | | ColumnWithAction<T>; | ||||
| interface Props<T extends ResultWithId> { | interface Props<T extends ResultWithId> { | ||||
| @@ -77,9 +86,11 @@ interface Props<T extends ResultWithId> { | |||||
| setPagingController?: Dispatch<SetStateAction<{ | setPagingController?: Dispatch<SetStateAction<{ | ||||
| pageNum: number; | pageNum: number; | ||||
| pageSize: number; | pageSize: number; | ||||
| }>> | |||||
| pagingController: { pageNum: number; pageSize: number;}; | |||||
| }>> | |||||
| pagingController: { pageNum: number; pageSize: number; }; | |||||
| isAutoPaging?: boolean; | isAutoPaging?: boolean; | ||||
| checkboxIds?: (string | number)[]; | |||||
| setCheckboxIds?: Dispatch<SetStateAction<(string | number)[]>>; | |||||
| } | } | ||||
| function isActionColumn<T extends ResultWithId>( | function isActionColumn<T extends ResultWithId>( | ||||
| @@ -106,14 +117,20 @@ function isIntegerColumn<T extends ResultWithId>( | |||||
| return column.type === "integer"; | return column.type === "integer"; | ||||
| } | } | ||||
| function isCheckboxColumn<T extends ResultWithId>( | |||||
| column: Column<T> | |||||
| ): column is CheckboxColumn<T> { | |||||
| return column.type === "checkbox"; | |||||
| } | |||||
| // Icon Component Functions | // Icon Component Functions | ||||
| function convertObjectKeysToLowercase<T extends object>( | function convertObjectKeysToLowercase<T extends object>( | ||||
| obj: T | obj: T | ||||
| ): object | undefined { | ): object | undefined { | ||||
| return obj | return obj | ||||
| ? Object.fromEntries( | ? Object.fromEntries( | ||||
| Object.entries(obj).map(([key, value]) => [key.toLowerCase(), value]) | |||||
| ) | |||||
| Object.entries(obj).map(([key, value]) => [key.toLowerCase(), value]) | |||||
| ) | |||||
| : undefined; | : undefined; | ||||
| } | } | ||||
| @@ -144,9 +161,9 @@ function handleIconIcons<T extends ResultWithId>( | |||||
| return column.icon ?? <CheckCircleOutlineIcon fontSize="small" />; | 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>({ | function SearchResults<T extends ResultWithId>({ | ||||
| items, | items, | ||||
| @@ -155,7 +172,9 @@ function SearchResults<T extends ResultWithId>({ | |||||
| pagingController, | pagingController, | ||||
| setPagingController, | setPagingController, | ||||
| isAutoPaging = true, | isAutoPaging = true, | ||||
| totalCount | |||||
| totalCount, | |||||
| checkboxIds = [], | |||||
| setCheckboxIds = undefined, | |||||
| }: Props<T>) { | }: Props<T>) { | ||||
| const [page, setPage] = React.useState(0); | const [page, setPage] = React.useState(0); | ||||
| const [rowsPerPage, setRowsPerPage] = React.useState(10); | 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 = ( | const table = ( | ||||
| <> | <> | ||||
| <TableContainer sx={{ maxHeight: 440 }}> | <TableContainer sx={{ maxHeight: 440 }}> | ||||
| @@ -209,29 +250,16 @@ function SearchResults<T extends ResultWithId>({ | |||||
| <TableBody> | <TableBody> | ||||
| {isAutoPaging | {isAutoPaging | ||||
| ? items | ? 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 ( | 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) => { | {columns.map((column, idx) => { | ||||
| const columnName = column.name; | const columnName = column.name; | ||||
| @@ -242,12 +270,33 @@ function SearchResults<T extends ResultWithId>({ | |||||
| columnName={columnName} | columnName={columnName} | ||||
| idx={idx} | idx={idx} | ||||
| item={item} | item={item} | ||||
| checkboxIds={checkboxIds} | |||||
| /> | /> | ||||
| ); | ); | ||||
| })} | })} | ||||
| </TableRow> | </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> | </TableBody> | ||||
| </Table> | </Table> | ||||
| </TableContainer> | </TableContainer> | ||||
| @@ -255,8 +304,8 @@ function SearchResults<T extends ResultWithId>({ | |||||
| rowsPerPageOptions={[10, 25, 100]} | rowsPerPageOptions={[10, 25, 100]} | ||||
| component="div" | component="div" | ||||
| count={!totalCount || totalCount == 0 | count={!totalCount || totalCount == 0 | ||||
| ? items.length | |||||
| : totalCount | |||||
| ? items.length | |||||
| : totalCount | |||||
| } | } | ||||
| // count={ | // count={ | ||||
| // !pagingController || pagingController.totalCount == 0 | // !pagingController || pagingController.totalCount == 0 | ||||
| @@ -280,6 +329,7 @@ interface TableCellsProps<T extends ResultWithId> { | |||||
| columnName: keyof T; | columnName: keyof T; | ||||
| idx: number; | idx: number; | ||||
| item: T; | item: T; | ||||
| checkboxIds: (string | number)[]; | |||||
| } | } | ||||
| function TabelCells<T extends ResultWithId>({ | function TabelCells<T extends ResultWithId>({ | ||||
| @@ -287,7 +337,10 @@ function TabelCells<T extends ResultWithId>({ | |||||
| columnName, | columnName, | ||||
| idx, | idx, | ||||
| item, | item, | ||||
| checkboxIds = [], | |||||
| }: TableCellsProps<T>) { | }: TableCellsProps<T>) { | ||||
| const isItemSelected = checkboxIds.includes(item.id); | |||||
| return ( | return ( | ||||
| <TableCell | <TableCell | ||||
| align={column.align} | align={column.align} | ||||
| @@ -309,6 +362,8 @@ function TabelCells<T extends ResultWithId>({ | |||||
| <>{decimalFormatter.format(Number(item[columnName]))}</> | <>{decimalFormatter.format(Number(item[columnName]))}</> | ||||
| ) : isIntegerColumn(column) ? ( | ) : isIntegerColumn(column) ? ( | ||||
| <>{integerFormatter.format(Number(item[columnName]))}</> | <>{integerFormatter.format(Number(item[columnName]))}</> | ||||
| ) : isCheckboxColumn(column) ? ( | |||||
| <Checkbox disabled={column.disabled ? column.disabled(item) : undefined} checked={isItemSelected} /> | |||||
| ) : column.renderCell ? ( | ) : column.renderCell ? ( | ||||
| column.renderCell(item) | column.renderCell(item) | ||||
| ) : ( | ) : ( | ||||