| @@ -0,0 +1,34 @@ | |||||
| import { preloadInventory } from "@/app/api/inventory"; | |||||
| import InventorySearch from "@/components/InventorySearch"; | |||||
| import { getServerI18n } from "@/i18n"; | |||||
| import { Stack, Typography } from "@mui/material"; | |||||
| import { Metadata } from "next"; | |||||
| import { Suspense } from "react"; | |||||
| export const metadata: Metadata = { | |||||
| title: "Inventory" | |||||
| } | |||||
| const Inventory: React.FC = async () => { | |||||
| const { t } = await getServerI18n("inventory") | |||||
| preloadInventory() | |||||
| return <> | |||||
| <Stack | |||||
| direction="row" | |||||
| justifyContent={"space-between"} | |||||
| flexWrap={"wrap"} | |||||
| rowGap={2} | |||||
| > | |||||
| <Typography variant="h4" marginInlineEnd={2}> | |||||
| {t("Inventory")} | |||||
| </Typography> | |||||
| </Stack> | |||||
| <Suspense fallback={<InventorySearch.Loading />}> | |||||
| <InventorySearch /> | |||||
| </Suspense> | |||||
| </>; | |||||
| } | |||||
| export default Inventory; | |||||
| @@ -1,19 +1,16 @@ | |||||
| import { TypeEnum } from "@/app/utils/typeEnum"; | import { TypeEnum } from "@/app/utils/typeEnum"; | ||||
| import ItemsSearch from "@/components/ItemsSearch"; | |||||
| import DetailSchedule from "@/components/DetailSchedule"; | |||||
| import { getServerI18n } from "@/i18n"; | import { getServerI18n } from "@/i18n"; | ||||
| import Add from "@mui/icons-material/Add"; | |||||
| import Button from "@mui/material/Button"; | |||||
| import Stack from "@mui/material/Stack"; | import Stack from "@mui/material/Stack"; | ||||
| import Typography from "@mui/material/Typography"; | import Typography from "@mui/material/Typography"; | ||||
| import { Metadata } from "next"; | import { Metadata } from "next"; | ||||
| import Link from "next/link"; | |||||
| import { Suspense } from "react"; | import { Suspense } from "react"; | ||||
| export const metadata: Metadata = { | export const metadata: Metadata = { | ||||
| title: "Detail Scheduling", | title: "Detail Scheduling", | ||||
| }; | }; | ||||
| const detailScheduling: React.FC = async () => { | |||||
| const DetailScheduling: React.FC = async () => { | |||||
| const project = TypeEnum.PRODUCT | const project = TypeEnum.PRODUCT | ||||
| const { t } = await getServerI18n(project); | const { t } = await getServerI18n(project); | ||||
| // preloadClaims(); | // preloadClaims(); | ||||
| @@ -29,20 +26,12 @@ const detailScheduling: React.FC = async () => { | |||||
| <Typography variant="h4" marginInlineEnd={2}> | <Typography variant="h4" marginInlineEnd={2}> | ||||
| {t("Detail Scheduling")} | {t("Detail Scheduling")} | ||||
| </Typography> | </Typography> | ||||
| {/* <Button | |||||
| variant="contained" | |||||
| startIcon={<Add />} | |||||
| LinkComponent={Link} | |||||
| href="product/create" | |||||
| > | |||||
| {t("Create product")} | |||||
| </Button> */} | |||||
| </Stack> | </Stack> | ||||
| <Suspense fallback={<ItemsSearch.Loading />}> | |||||
| <ItemsSearch /> | |||||
| <Suspense fallback={<DetailSchedule.Loading />}> | |||||
| <DetailSchedule /> | |||||
| </Suspense> | </Suspense> | ||||
| </> | </> | ||||
| ); | ); | ||||
| }; | }; | ||||
| export default detailScheduling; | |||||
| export default DetailScheduling; | |||||
| @@ -0,0 +1,30 @@ | |||||
| import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||||
| import { BASE_API_URL } from "@/config/api"; | |||||
| import { cache } from "react"; | |||||
| import "server-only"; | |||||
| export interface InventoryResult { | |||||
| id: number; | |||||
| code: string; | |||||
| name: string; | |||||
| type: string; | |||||
| qty: number; | |||||
| uomCode: string; | |||||
| uomUdfudesc: string; | |||||
| germPerSmallestUnit: number; | |||||
| qtyPerSmallestUnit: number; | |||||
| smallestUnit: string; | |||||
| price: number; | |||||
| currencyName: string; | |||||
| status: string; | |||||
| } | |||||
| export const preloadInventory = () => { | |||||
| fetchInventories(); | |||||
| } | |||||
| export const fetchInventories = cache(async() => { | |||||
| return serverFetchJson<InventoryResult[]>(`${BASE_API_URL}/inventory/list`, { | |||||
| next: { tags: ["inventories"]} | |||||
| }) | |||||
| }) | |||||
| @@ -18,6 +18,7 @@ const pathToLabelMap: { [path: string]: string } = { | |||||
| "/scheduling/rough/edit": "FG & Material Demand Forecast Detail", | "/scheduling/rough/edit": "FG & Material Demand Forecast Detail", | ||||
| "/scheduling/detail": "Detail Scheduling", | "/scheduling/detail": "Detail Scheduling", | ||||
| "/scheduling/detail/edit": "FG Production Schedule", | "/scheduling/detail/edit": "FG Production Schedule", | ||||
| "/inventory": "Inventory", | |||||
| }; | }; | ||||
| const Breadcrumb = () => { | const Breadcrumb = () => { | ||||
| @@ -101,14 +101,14 @@ const ViewByFGDetails: React.FC<Props> = ({ apiRef, isEdit }) => { | |||||
| () => [ | () => [ | ||||
| [ | [ | ||||
| { | { | ||||
| id: 1, jobNo: "JO20250507001", priority: 85, code: "PP1193", type: "FG", name: "蔥油(1磅) ", inStockQty: 1322, productionQty: 661, | |||||
| id: 1, jobNo: "JO20250507001", estimatedProductionTime: "1 hr", priority: 85, code: "PP1193", type: "FG", name: "蔥油(1磅) ", inStockQty: 1322, productionQty: 661, | |||||
| lines: [ | lines: [ | ||||
| { id: 1, code: "MH0040", type: "Material", name: "大豆油(1噸/桶)", inStockQty: 100, purchaseQty: 20 }, | { id: 1, code: "MH0040", type: "Material", name: "大豆油(1噸/桶)", inStockQty: 100, purchaseQty: 20 }, | ||||
| { id: 2, code: "FA0161", type: "Material", name: "洋蔥粒", inStockQty: 80, purchaseQty: 10 } | { id: 2, code: "FA0161", type: "Material", name: "洋蔥粒", inStockQty: 80, purchaseQty: 10 } | ||||
| ] | ] | ||||
| }, | }, | ||||
| { | { | ||||
| id: 2, jobNo: "JO20250507002", priority: 80, code: " PP1096", type: "FG", name: "白麵撈", inStockQty: 1040, productionQty: 520, | |||||
| id: 2, jobNo: "JO20250507002", estimatedProductionTime: "2 hrs", priority: 80, code: " PP1096", type: "FG", name: "白麵撈", inStockQty: 1040, productionQty: 520, | |||||
| lines: [ | lines: [ | ||||
| { id: 1, code: "MH0040", type: "Material", name: "大豆油(1噸/桶)", inStockQty: 1000, purchaseQty: 190.00 }, | { id: 1, code: "MH0040", type: "Material", name: "大豆油(1噸/桶)", inStockQty: 1000, purchaseQty: 190.00 }, | ||||
| { id: 1, code: "MH0040", type: "Material", name: "星加坡綠富貴花牌幼白麵粉 (50磅/包)", inStockQty: 1000, purchaseQty: 250.00 }, | { id: 1, code: "MH0040", type: "Material", name: "星加坡綠富貴花牌幼白麵粉 (50磅/包)", inStockQty: 1000, purchaseQty: 250.00 }, | ||||
| @@ -116,7 +116,7 @@ const ViewByFGDetails: React.FC<Props> = ({ apiRef, isEdit }) => { | |||||
| ] | ] | ||||
| }, | }, | ||||
| { | { | ||||
| id: 3, jobNo: "JO20250507003", priority: 35, code: "PP1080", type: "FG", name: "咖哩汁", inStockQty: 2400, productionQty: 1200.0, | |||||
| id: 3, jobNo: "JO20250507003", estimatedProductionTime: "5 hrs : 15 mins", priority: 35, code: "PP1080", type: "FG", name: "咖哩汁", inStockQty: 2400, productionQty: 1200.0, | |||||
| lines: [ | lines: [ | ||||
| { id: 1, code: "MH0040", type: "Material", name: "大豆油(1噸/桶)", inStockQty: 0, purchaseQty: 108.88 }, | { id: 1, code: "MH0040", type: "Material", name: "大豆油(1噸/桶)", inStockQty: 0, purchaseQty: 108.88 }, | ||||
| { id: 2, code: "GI3236", type: "Material", name: "清水(煮過牛腩)", inStockQty: 317.52, purchaseQty: 635.04 }, | { id: 2, code: "GI3236", type: "Material", name: "清水(煮過牛腩)", inStockQty: 317.52, purchaseQty: 635.04 }, | ||||
| @@ -132,7 +132,7 @@ const ViewByFGDetails: React.FC<Props> = ({ apiRef, isEdit }) => { | |||||
| ] | ] | ||||
| }, | }, | ||||
| { | { | ||||
| id: 4, jobNo: "JO20250507004", priority: 20, code: " PP1188", type: "FG", name: "咖喱膽", inStockQty: 1016.2, productionQty: 508.1, | |||||
| id: 4, jobNo: "JO20250507004", estimatedProductionTime: "3 hrs", priority: 20, code: " PP1188", type: "FG", name: "咖喱膽", inStockQty: 1016.2, productionQty: 508.1, | |||||
| lines: [ | lines: [ | ||||
| { id: 1, code: "MH0040", type: "Material", name: "大豆油(1噸/桶)", inStockQty: 0, purchaseQty: 217.72 }, | { id: 1, code: "MH0040", type: "Material", name: "大豆油(1噸/桶)", inStockQty: 0, purchaseQty: 217.72 }, | ||||
| { id: 2, code: "FA0161", type: "Material", name: "洋蔥粒", inStockQty: 0, purchaseQty: 18.15 }, | { id: 2, code: "FA0161", type: "Material", name: "洋蔥粒", inStockQty: 0, purchaseQty: 18.15 }, | ||||
| @@ -525,6 +525,14 @@ const ViewByFGDetails: React.FC<Props> = ({ apiRef, isEdit }) => { | |||||
| return row.productionQty | return row.productionQty | ||||
| } | } | ||||
| }, | }, | ||||
| { | |||||
| field: "estimatedProductionTime", | |||||
| label: "Estimated Production Time", | |||||
| type: "read-only", | |||||
| style: { | |||||
| textAlign: "right", | |||||
| } | |||||
| }, | |||||
| { | { | ||||
| field: "priority", | field: "priority", | ||||
| label: "Production Priority", | label: "Production Priority", | ||||
| @@ -0,0 +1,139 @@ | |||||
| "use client" | |||||
| import { InventoryResult } from "@/app/api/inventory"; | |||||
| import { useTranslation } from "react-i18next"; | |||||
| import SearchBox, { Criterion } from "../SearchBox"; | |||||
| import { useCallback, useMemo, useState } from "react"; | |||||
| import { uniq } from "lodash"; | |||||
| import SearchResults, { Column } from "../SearchResults"; | |||||
| import { CheckCircleOutline, DoDisturb } from "@mui/icons-material"; | |||||
| interface Props { | |||||
| inventories: InventoryResult[]; | |||||
| } | |||||
| type SearchQuery = Partial<Omit<InventoryResult, | |||||
| | "id" | |||||
| | "qty" | |||||
| | "uomCode" | |||||
| | "uomUdfudesc" | |||||
| | "germPerSmallestUnit" | |||||
| | "qtyPerSmallestUnit" | |||||
| | "itemSmallestUnit" | |||||
| | "price" | |||||
| | "description" | |||||
| | "category">>; | |||||
| type SearchParamNames = keyof SearchQuery; | |||||
| const InventorySearch: React.FC<Props> = ({ | |||||
| inventories, | |||||
| }) => { | |||||
| const { t } = useTranslation("inventories"); | |||||
| const [filteredInventories, setFilteredInventories] = useState(inventories) | |||||
| const searchCriteria: Criterion<SearchParamNames>[] = useMemo(() => [ | |||||
| { label: t("Code"), paramName: "code", type: "text" }, | |||||
| { label: t("Name"), paramName: "name", type: "text" }, | |||||
| { label: t("Type"), paramName: "type", type: "select", options: uniq(inventories.map(i => i.type)) }, | |||||
| { label: t("Status"), paramName: "status", type: "select", options: uniq(inventories.map(i => i.status)) }, | |||||
| ], [t] | |||||
| ); | |||||
| const onReset = useCallback(() => { | |||||
| setFilteredInventories(inventories) | |||||
| }, [inventories]) | |||||
| const columns = useMemo<Column<InventoryResult>[]>( | |||||
| () => [ | |||||
| { | |||||
| name: "code", | |||||
| label: t("Code"), | |||||
| }, | |||||
| { | |||||
| name: "name", | |||||
| label: t("Name"), | |||||
| }, | |||||
| { | |||||
| name: "type", | |||||
| label: t("Type"), | |||||
| }, | |||||
| { | |||||
| name: "qty", | |||||
| label: t("Qty"), | |||||
| align: "right", | |||||
| headerAlign: "right", | |||||
| type: "integer" | |||||
| }, | |||||
| { | |||||
| name: "uomUdfudesc", | |||||
| label: t("UoM"), | |||||
| }, | |||||
| { | |||||
| name: "qtyPerSmallestUnit", | |||||
| label: t("Qty Per Smallest Unit"), | |||||
| align: "right", | |||||
| headerAlign: "right", | |||||
| type: "decimal" | |||||
| }, | |||||
| { | |||||
| name: "smallestUnit", | |||||
| label: t("Smallest Unit"), | |||||
| }, | |||||
| // { | |||||
| // name: "price", | |||||
| // label: t("Price"), | |||||
| // align: "right", | |||||
| // sx: { | |||||
| // alignItems: "right", | |||||
| // justifyContent: "end", | |||||
| // } | |||||
| // }, | |||||
| // { | |||||
| // name: "currencyName", | |||||
| // label: t("Currency"), | |||||
| // }, | |||||
| // { | |||||
| // name: "status", | |||||
| // label: t("Status"), | |||||
| // type: "icon", | |||||
| // icons: { | |||||
| // available: <CheckCircleOutline fontSize="small"/>, | |||||
| // unavailable: <DoDisturb fontSize="small"/>, | |||||
| // }, | |||||
| // colors: { | |||||
| // available: "success", | |||||
| // unavailable: "error", | |||||
| // } | |||||
| // }, | |||||
| ], [t] | |||||
| ) | |||||
| return ( | |||||
| <> | |||||
| <SearchBox | |||||
| criteria={searchCriteria} | |||||
| onSearch={(query) => { | |||||
| console.log(query) | |||||
| console.log(inventories) | |||||
| setFilteredInventories( | |||||
| inventories.filter( | |||||
| (i) => | |||||
| i.code.toLowerCase().includes(query.code.toLowerCase()) && | |||||
| i.name.toLowerCase().includes(query.name.toLowerCase()) && | |||||
| (query.type == "All" || i.type.toLowerCase().includes(query.type.toLowerCase())) && | |||||
| (query.status == "All" || i.status.toLowerCase().includes(query.status.toLowerCase())) | |||||
| ) | |||||
| ) | |||||
| }} | |||||
| onReset={onReset} | |||||
| /> | |||||
| <SearchResults<InventoryResult> items={filteredInventories} columns={columns} pagingController={{ | |||||
| pageNum: 0, | |||||
| pageSize: 0, | |||||
| totalCount: 0, | |||||
| }} /> | |||||
| </> | |||||
| ) | |||||
| } | |||||
| export default InventorySearch | |||||
| @@ -0,0 +1,24 @@ | |||||
| import React from "react"; | |||||
| import GeneralLoading from "../General/GeneralLoading" | |||||
| import { fetchInventories } from "@/app/api/inventory"; | |||||
| import InventorySearch from "./InventorySearch"; | |||||
| interface SubComponents { | |||||
| Loading: typeof GeneralLoading; | |||||
| } | |||||
| const InventorySearchWrapper: React.FC & SubComponents = async () => { | |||||
| const [ | |||||
| inventories | |||||
| ] = await Promise.all([ | |||||
| fetchInventories() | |||||
| ]) | |||||
| return <InventorySearch inventories={inventories}/> | |||||
| } | |||||
| InventorySearchWrapper.Loading = GeneralLoading; | |||||
| export default InventorySearchWrapper | |||||
| @@ -0,0 +1 @@ | |||||
| export { default } from "./InventorySearchWrapper" | |||||
| @@ -76,8 +76,8 @@ const NavigationContent: React.FC = () => { | |||||
| }, | }, | ||||
| { | { | ||||
| icon: <RequestQuote />, | icon: <RequestQuote />, | ||||
| label: "View item In-out And invertory Ledger", | |||||
| path: "", | |||||
| label: "View item In-out And inventory Ledger", | |||||
| path: "/inventory", | |||||
| }, | }, | ||||
| ], | ], | ||||
| }, | }, | ||||
| @@ -28,7 +28,7 @@ interface BaseCriterion<T extends string> { | |||||
| label2?: string; | label2?: string; | ||||
| paramName: T; | paramName: T; | ||||
| paramName2?: T; | paramName2?: T; | ||||
| options?: T[]; | |||||
| options?: T[] | string[]; | |||||
| filterObj?: T; | filterObj?: T; | ||||
| handleSelectionChange?: (selectedOptions: T[]) => void; | handleSelectionChange?: (selectedOptions: T[]) => void; | ||||
| } | } | ||||
| @@ -39,11 +39,11 @@ interface TextCriterion<T extends string> extends BaseCriterion<T> { | |||||
| interface SelectCriterion<T extends string> extends BaseCriterion<T> { | interface SelectCriterion<T extends string> extends BaseCriterion<T> { | ||||
| type: "select"; | type: "select"; | ||||
| options: T[]; | |||||
| options: string[]; | |||||
| } | } | ||||
| interface MultiSelectCriterion<T extends string> extends BaseCriterion<T> { | interface MultiSelectCriterion<T extends string> extends BaseCriterion<T> { | ||||
| type: "select"; | |||||
| type: "multi-select"; | |||||
| options: T[]; | options: T[]; | ||||
| selectedOptions: T[]; | selectedOptions: T[]; | ||||
| handleSelectionChange: (selectedOptions: T[]) => void; | handleSelectionChange: (selectedOptions: T[]) => void; | ||||
| @@ -4,32 +4,62 @@ import React 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"; | ||||
| import TableCell from "@mui/material/TableCell"; | |||||
| import TableCell, { TableCellProps } from "@mui/material/TableCell"; | |||||
| import TableContainer from "@mui/material/TableContainer"; | import TableContainer from "@mui/material/TableContainer"; | ||||
| import TableHead from "@mui/material/TableHead"; | import TableHead from "@mui/material/TableHead"; | ||||
| import TablePagination, { | import TablePagination, { | ||||
| TablePaginationProps, | TablePaginationProps, | ||||
| } from "@mui/material/TablePagination"; | } from "@mui/material/TablePagination"; | ||||
| import TableRow from "@mui/material/TableRow"; | import TableRow from "@mui/material/TableRow"; | ||||
| import IconButton, {IconButtonOwnProps} from "@mui/material/IconButton"; | |||||
| import IconButton, { IconButtonOwnProps } from "@mui/material/IconButton"; | |||||
| import { ButtonOwnProps, Icon, IconOwnProps, SxProps, Theme } from "@mui/material"; | |||||
| import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'; | |||||
| import { decimalFormatter, integerFormatter } from "@/app/utils/formatUtil"; | |||||
| export interface ResultWithId { | export interface ResultWithId { | ||||
| id: string | number; | id: string | number; | ||||
| } | } | ||||
| type ColumnType = "icon" | "decimal" | "integer"; | |||||
| interface BaseColumn<T extends ResultWithId> { | interface BaseColumn<T extends ResultWithId> { | ||||
| name: keyof T; | name: keyof T; | ||||
| label: string; | label: string; | ||||
| align?: TableCellProps["align"]; | |||||
| headerAlign?: TableCellProps["align"]; | |||||
| sx?: SxProps<Theme> | undefined; | |||||
| style?: Partial<HTMLElement["style"]> & { [propName: string]: string }; | |||||
| type?: ColumnType; | |||||
| } | |||||
| interface IconColumn<T extends ResultWithId> extends BaseColumn<T> { | |||||
| name: keyof T; | |||||
| type: "icon"; | |||||
| icon?: React.ReactNode; | |||||
| icons?: { [columnValue in keyof T]: React.ReactNode }; | |||||
| color?: IconOwnProps["color"]; | |||||
| colors?: { [columnValue in keyof T]: IconOwnProps["color"] }; | |||||
| } | |||||
| interface DecimalColumn<T extends ResultWithId> extends BaseColumn<T> { | |||||
| type: "decimal"; | |||||
| } | |||||
| interface IntegerColumn<T extends ResultWithId> extends BaseColumn<T> { | |||||
| type: "integer"; | |||||
| } | } | ||||
| 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; | ||||
| buttonIcons: { [columnValue in keyof T]: React.ReactNode }; | |||||
| buttonColor?: IconButtonOwnProps["color"]; | buttonColor?: IconButtonOwnProps["color"]; | ||||
| } | } | ||||
| export type Column<T extends ResultWithId> = | export type Column<T extends ResultWithId> = | ||||
| | BaseColumn<T> | | BaseColumn<T> | ||||
| | IconColumn<T> | |||||
| | DecimalColumn<T> | |||||
| | ColumnWithAction<T>; | | ColumnWithAction<T>; | ||||
| interface Props<T extends ResultWithId> { | interface Props<T extends ResultWithId> { | ||||
| @@ -42,7 +72,7 @@ interface Props<T extends ResultWithId> { | |||||
| totalCount: number | totalCount: number | ||||
| }) | { pageNum: number; pageSize: number; totalCount: number })) => void, | }) | { pageNum: number; pageSize: number; totalCount: number })) => void, | ||||
| pagingController: { pageNum: number; pageSize: number; totalCount: number }, | pagingController: { pageNum: number; pageSize: number; totalCount: number }, | ||||
| isAutoPaging: boolean | |||||
| isAutoPaging?: boolean | |||||
| } | } | ||||
| function isActionColumn<T extends ResultWithId>( | function isActionColumn<T extends ResultWithId>( | ||||
| @@ -51,14 +81,67 @@ function isActionColumn<T extends ResultWithId>( | |||||
| return Boolean((column as ColumnWithAction<T>).onClick); | return Boolean((column as ColumnWithAction<T>).onClick); | ||||
| } | } | ||||
| function isIconColumn<T extends ResultWithId>( | |||||
| column: Column<T>, | |||||
| ): column is IconColumn<T> { | |||||
| return column.type === "icon"; | |||||
| } | |||||
| function isDecimalColumn<T extends ResultWithId>( | |||||
| column: Column<T>, | |||||
| ): column is DecimalColumn<T> { | |||||
| return column.type === "decimal"; | |||||
| } | |||||
| function isIntegerColumn<T extends ResultWithId>( | |||||
| column: Column<T>, | |||||
| ): column is IntegerColumn<T> { | |||||
| return column.type === "integer"; | |||||
| } | |||||
| // 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]) | |||||
| ) : undefined; | |||||
| } | |||||
| function handleIconColors<T extends ResultWithId>( | |||||
| column: IconColumn<T>, | |||||
| value: T[keyof T], | |||||
| ): IconOwnProps["color"] { | |||||
| const colors = convertObjectKeysToLowercase(column.colors ?? {}); | |||||
| const valueKey = String(value).toLowerCase() as keyof typeof colors; | |||||
| if (colors && valueKey in colors) { | |||||
| return colors[valueKey]; | |||||
| } | |||||
| return column.color ?? "primary"; | |||||
| }; | |||||
| function handleIconIcons<T extends ResultWithId>( | |||||
| column: IconColumn<T>, | |||||
| value: T[keyof T], | |||||
| ): React.ReactNode { | |||||
| const icons = convertObjectKeysToLowercase(column.icons ?? {}); | |||||
| const valueKey = String(value).toLowerCase() as keyof typeof icons; | |||||
| if (icons && valueKey in icons) { | |||||
| return icons[valueKey]; | |||||
| } | |||||
| return column.icon ?? <CheckCircleOutlineIcon fontSize="small" />; | |||||
| }; | |||||
| function SearchResults<T extends ResultWithId>({ | function SearchResults<T extends ResultWithId>({ | ||||
| items, | |||||
| columns, | |||||
| noWrapper, | |||||
| pagingController, | |||||
| setPagingController, | |||||
| isAutoPaging = true, | |||||
| }: Props<T>) { | |||||
| items, | |||||
| columns, | |||||
| noWrapper, | |||||
| pagingController, | |||||
| setPagingController, | |||||
| isAutoPaging = true, | |||||
| }: 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); | ||||
| @@ -90,12 +173,12 @@ function SearchResults<T extends ResultWithId>({ | |||||
| const table = ( | const table = ( | ||||
| <> | <> | ||||
| <TableContainer sx={{maxHeight: 440}}> | |||||
| <TableContainer sx={{ maxHeight: 440 }}> | |||||
| <Table stickyHeader> | <Table stickyHeader> | ||||
| <TableHead> | <TableHead> | ||||
| <TableRow> | <TableRow> | ||||
| {columns.map((column, idx) => ( | {columns.map((column, idx) => ( | ||||
| <TableCell key={`${column.name.toString()}${idx}`}> | |||||
| <TableCell align={column.headerAlign} sx={column.sx} key={`${column.name.toString()}${idx}`}> | |||||
| {column.label} | {column.label} | ||||
| </TableCell> | </TableCell> | ||||
| ))} | ))} | ||||
| @@ -113,18 +196,7 @@ function SearchResults<T extends ResultWithId>({ | |||||
| const columnName = column.name; | const columnName = column.name; | ||||
| return ( | return ( | ||||
| <TableCell key={`${columnName.toString()}-${idx}`}> | |||||
| {isActionColumn(column) ? ( | |||||
| <IconButton | |||||
| color={column.buttonColor ?? "primary"} | |||||
| onClick={() => column.onClick(item)} | |||||
| > | |||||
| {column.buttonIcon} | |||||
| </IconButton> | |||||
| ) : ( | |||||
| <>{item[columnName] as string}</> | |||||
| )} | |||||
| </TableCell> | |||||
| <TabelCells key={`${columnName.toString()}-${idx}`} column={column} columnName={columnName} idx={idx} item={item}/> | |||||
| ); | ); | ||||
| })} | })} | ||||
| </TableRow> | </TableRow> | ||||
| @@ -139,19 +211,8 @@ function SearchResults<T extends ResultWithId>({ | |||||
| const columnName = column.name; | const columnName = column.name; | ||||
| return ( | return ( | ||||
| <TableCell key={`${columnName.toString()}-${idx}`}> | |||||
| {isActionColumn(column) ? ( | |||||
| <IconButton | |||||
| color={column.buttonColor ?? "primary"} | |||||
| onClick={() => column.onClick(item)} | |||||
| > | |||||
| {column.buttonIcon} | |||||
| </IconButton> | |||||
| ) : ( | |||||
| <>{item[columnName] as string}</> | |||||
| )} | |||||
| </TableCell> | |||||
| ); | |||||
| <TabelCells key={`${columnName.toString()}-${idx}`} column={column} columnName={columnName} idx={idx} item={item}/> | |||||
| ); | |||||
| })} | })} | ||||
| </TableRow> | </TableRow> | ||||
| ); | ); | ||||
| @@ -172,7 +233,49 @@ function SearchResults<T extends ResultWithId>({ | |||||
| </> | </> | ||||
| ); | ); | ||||
| return noWrapper ? table : <Paper sx={{overflow: "hidden"}}>{table}</Paper>; | |||||
| return noWrapper ? table : <Paper sx={{ overflow: "hidden" }}>{table}</Paper>; | |||||
| } | |||||
| // Table cells | |||||
| interface TableCellsProps<T extends ResultWithId> { | |||||
| column: Column<T>, | |||||
| columnName: keyof T, | |||||
| idx: number, | |||||
| item: T, | |||||
| } | |||||
| function TabelCells<T extends ResultWithId>({ | |||||
| column, | |||||
| columnName, | |||||
| idx, | |||||
| item | |||||
| }: TableCellsProps<T>) { | |||||
| return ( | |||||
| <TableCell align={column.align} sx={column.sx} key={`${columnName.toString()}-${idx}`}> | |||||
| {isActionColumn(column) ? ( | |||||
| <IconButton | |||||
| color={column.buttonColor ?? "primary"} | |||||
| onClick={() => column.onClick(item)} | |||||
| > | |||||
| {column.buttonIcon} | |||||
| </IconButton> | |||||
| ) : | |||||
| isIconColumn(column) ? ( | |||||
| <Icon | |||||
| color={handleIconColors(column, item[columnName])} | |||||
| > | |||||
| {handleIconIcons(column, item[columnName])} | |||||
| </Icon> | |||||
| ) : | |||||
| isDecimalColumn(column) ? ( | |||||
| <>{decimalFormatter.format(Number(item[columnName]))}</> | |||||
| ) : | |||||
| isIntegerColumn(column) ? ( | |||||
| <>{integerFormatter.format(Number(item[columnName]))}</> | |||||
| ) : ( | |||||
| <>{item[columnName] as string}</> | |||||
| )} | |||||
| </TableCell>) | |||||
| } | } | ||||
| export default SearchResults; | export default SearchResults; | ||||