Browse Source

[Pick Order & Search Box] Copy & update autocomplete to search box. Update Pick Order Search.

create_edit_user
cyril.tsui 2 months ago
parent
commit
d3b391a192
4 changed files with 73 additions and 19 deletions
  1. +2
    -2
      src/app/api/pickOrder/index.ts
  2. +25
    -0
      src/app/utils/formatUtil.ts
  3. +38
    -11
      src/components/PickOrderSearch/PickOrderSearch.tsx
  4. +8
    -6
      src/components/SearchBox/SearchBox.tsx

+ 2
- 2
src/app/api/pickOrder/index.ts View File

@@ -12,8 +12,8 @@ export interface PickOrderResult{
id: number,
code: string,
consoCode?: string,
targetDate: string,
completeDate?: string,
targetDate: number[],
completeDate?: number[],
type: string,
status: string,
releasedBy: string,


+ 25
- 0
src/app/utils/formatUtil.ts View File

@@ -1,4 +1,6 @@
import dayjs, { ConfigType } from "dayjs";
import { Uom } from "../api/settings/uom";
import { ListIterateeCustom, every, isArray, isNaN, isNull, isUndefined, take } from "lodash";

export const manhourFormatter = new Intl.NumberFormat("en-HK", {
minimumFractionDigits: 2,
@@ -24,6 +26,29 @@ export const OUTPUT_DATE_FORMAT = "YYYY/MM/DD";

export const OUTPUT_TIME_FORMAT = "HH:mm:ss";

export const arrayToDayjs = (arr: ConfigType | (number | undefined)[]) => {
const isValidNumber = (item: ListIterateeCustom<number | undefined, boolean>): boolean =>
typeof item === "number" && !isNaN(item) && isFinite(item)

let tempArr = arr;

if (isArray(arr) && every(arr, isValidNumber) && arr.length >= 3) {
// [year, month, day]
tempArr = take(arr, 3)
}

return dayjs(tempArr as ConfigType)
}

export const arrayToDateString = (arr: ConfigType | (number | undefined)[]) => {
return arrayToDayjs(arr).format(OUTPUT_DATE_FORMAT)
}

export const dateStringToDayjs = (date: string) => {
// Format: YYYY/MM/DD
return dayjs(date, OUTPUT_DATE_FORMAT)
}

export const stockInLineStatusMap: { [status: string]: number } = {
"draft": 0,
"pending": 1,


+ 38
- 11
src/components/PickOrderSearch/PickOrderSearch.tsx View File

@@ -5,7 +5,9 @@ import { useCallback, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import SearchBox, { Criterion } from "../SearchBox";
import SearchResults, { Column } from "../SearchResults";
import { groupBy, 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 dayjs from "dayjs";

interface Props {
pickOrders: PickOrderResult[];
@@ -32,20 +34,20 @@ const PickOrderSearch: React.FC<Props> = ({
label: t("Type"), paramName: "type", type: "autocomplete",
options: sortBy(
uniqBy(pickOrders.map((po) => ({ value: po.type, label: t(upperCase(po.type)) })), "value"),
"label").map((po) => (po))
"label")
},
{
label: t("Status"), paramName: "status", type: "autocomplete",
options: sortBy(
uniqBy(pickOrders.map((po) => ({ value: po.status, label: t(upperFirst(po.status)) })), "value"),
"label").map((po) => (po))
"label")
},
{
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 })): []),
"label")), "value")
},
// {
// label: t("Items"), paramName: "items", type: "autocomplete", multiple: true,
// options: sortBy(
// uniqBy(pickOrders.map((po) => po.items?.map((item) => ({ value: item.name, label: item.name, group: item.type }))), "value"),
// "label")
// },
], [t])

const onReset = useCallback(() => {
@@ -60,10 +62,16 @@ const PickOrderSearch: React.FC<Props> = ({
{
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",
@@ -72,6 +80,13 @@ const PickOrderSearch: React.FC<Props> = ({
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"),
@@ -79,6 +94,9 @@ const PickOrderSearch: React.FC<Props> = ({
{
name: "status",
label: t("Status"),
renderCell: (params) => {
return upperFirst(params.status)
}
},
], [t])

@@ -89,8 +107,17 @@ const PickOrderSearch: React.FC<Props> = ({
onSearch={(query) => {
setFilteredPickOrders(
pickOrders.filter(
(po) =>
po.code.toLowerCase().includes(query.code.toLowerCase())
(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()))
}
)
)
}}


+ 8
- 6
src/components/SearchBox/SearchBox.tsx View File

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

// TODO: Add group
interface AutocompleteOptions {
value: string | number;
label: string;
@@ -83,7 +82,10 @@ export type Criterion<T extends string> =

interface Props<T extends string> {
criteria: Criterion<T>[];
onSearch: (inputs: Record<T, string>) => void;
// TODO: may need to check the type is "autocomplete" and "multiple" = true, then allow string[].
// TODO: may need to check the type is "dateRange", then add T and `${T}To` in the same time.
// onSearch: (inputs: Record<T | (Criterion<T>["type"] extends "dateRange" ? `${T}To` : never), string>) => void;
onSearch: (inputs: Record<T | `${T}To`, string>) => void;
onReset?: () => void;
}

@@ -93,14 +95,15 @@ function SearchBox<T extends string>({
onReset,
}: Props<T>) {
const { t } = useTranslation("common");
const defaultAll = { value: "All", label: t("All"), group: t("All") }
const defaultAll: AutocompleteOptions = { value: "All", label: t("All"), group: t("All") }
const defaultInputs = useMemo(
() =>
criteria.reduce<Record<T, string>>(
(acc, c) => {
return {
...acc,
[c.paramName]: (c.type === "select" || c.type === "autocomplete" ? "All" : "")
[c.paramName]: (c.type === "select" || (c.type === "autocomplete" && !Boolean(c.multiple)) ? "All"
: (c.type === "autocomplete" && Boolean(c.multiple)) ? [defaultAll.value]: "")
};
},
{} as Record<T, string>,
@@ -168,7 +171,6 @@ function SearchBox<T extends string>({
<Typography variant="overline">{t("Search Criteria")}</Typography>
<Grid container spacing={2} columns={{ xs: 6, sm: 12 }}>
{criteria.map((c) => {
console.log(c.options)
return (
<Grid key={c.paramName} item xs={6}>
{c.type === "text" && (
@@ -216,7 +218,7 @@ function SearchBox<T extends string>({
noOptionsText={c.noOptionsText ?? t("No options")}
disableClearable
fullWidth
value={c.multiple ? intersectionWith(c.options, inputs[c.paramName], (option, v) => {
value={c.multiple ? intersectionWith([defaultAll, ...c.options], inputs[c.paramName], (option, v) => {
return option.value === (v ?? "")
})
: c.options.find((option) => option.value === inputs[c.paramName]) ?? defaultAll}


Loading…
Cancel
Save