@@ -1,7 +1,7 @@
|
|||||||
import { Col, Row, Table } from "antd";
|
import { Col, Row, Table } from "antd";
|
||||||
import { ColumnProps as AntdColumnProps } from "antd/es/table";
|
import { ColumnProps as AntdColumnProps } from "antd/es/table";
|
||||||
import { ColumnFilterItem } from "antd/es/table/interface";
|
import { ColumnFilterItem } from "antd/es/table/interface";
|
||||||
import { getFiltersForField, typeFilters, useListFiltersForField } from "../utils/filtering";
|
import { getFiltersForField, typeFilters } from "../utils/filtering";
|
||||||
import { TableState } from "../utils/saveload";
|
import { TableState } from "../utils/saveload";
|
||||||
import { getSortOrderForField, typeSorters } from "../utils/sorting";
|
import { getSortOrderForField, typeSorters } from "../utils/sorting";
|
||||||
import { NumberFieldUnit } from "./numberField";
|
import { NumberFieldUnit } from "./numberField";
|
||||||
@@ -12,12 +12,14 @@ import Icon from "@ant-design/icons";
|
|||||||
import { ReactComponent as SpoolIcon } from "../icon_spool.svg";
|
import { ReactComponent as SpoolIcon } from "../icon_spool.svg";
|
||||||
import { useTranslate } from "@refinedev/core";
|
import { useTranslate } from "@refinedev/core";
|
||||||
import { enrichText } from "../utils/parsing";
|
import { enrichText } from "../utils/parsing";
|
||||||
|
import { UseQueryResult } from "@tanstack/react-query";
|
||||||
|
|
||||||
dayjs.extend(utc);
|
dayjs.extend(utc);
|
||||||
|
|
||||||
interface BaseColumnProps<Obj> {
|
interface BaseColumnProps<Obj> {
|
||||||
id: keyof Obj & string;
|
id: keyof Obj & string;
|
||||||
i18ncat: string;
|
i18ncat?: string;
|
||||||
|
i18nkey?: string;
|
||||||
dataSource: Obj[];
|
dataSource: Obj[];
|
||||||
tableState: TableState;
|
tableState: TableState;
|
||||||
}
|
}
|
||||||
@@ -25,6 +27,7 @@ interface BaseColumnProps<Obj> {
|
|||||||
interface FilteredColumnProps {
|
interface FilteredColumnProps {
|
||||||
filters?: ColumnFilterItem[];
|
filters?: ColumnFilterItem[];
|
||||||
filteredValue?: string[];
|
filteredValue?: string[];
|
||||||
|
allowMultipleFilters?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CustomColumnProps<Obj> {
|
interface CustomColumnProps<Obj> {
|
||||||
@@ -45,9 +48,10 @@ function Column<Obj>(props: BaseColumnProps<Obj> & FilteredColumnProps & CustomC
|
|||||||
const typedSorters = typeSorters<Obj>(props.tableState.sorters);
|
const typedSorters = typeSorters<Obj>(props.tableState.sorters);
|
||||||
const columnProps: AntdColumnProps<Obj> = {
|
const columnProps: AntdColumnProps<Obj> = {
|
||||||
dataIndex: props.id,
|
dataIndex: props.id,
|
||||||
title: t(`${props.i18ncat}.fields.${props.id}`),
|
title: t(props.i18nkey ?? `${props.i18ncat}.fields.${props.id}`),
|
||||||
sorter: true,
|
sorter: true,
|
||||||
sortOrder: getSortOrderForField(typedSorters, props.id),
|
sortOrder: getSortOrderForField(typedSorters, props.id),
|
||||||
|
filterMultiple: props.allowMultipleFilters ?? true,
|
||||||
};
|
};
|
||||||
if (props.filters && props.filteredValue) {
|
if (props.filters && props.filteredValue) {
|
||||||
columnProps.filters = props.filters;
|
columnProps.filters = props.filters;
|
||||||
@@ -75,10 +79,29 @@ export function RichColumn<Obj>(props: BaseColumnProps<Obj>) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FilteredColumn<Obj>(props: BaseColumnProps<Obj>) {
|
interface FilteredQueryColumnProps<Obj> extends BaseColumnProps<Obj> {
|
||||||
const typedFilters = typeFilters<Obj>(props.tableState.filters);
|
filterValueQuery: UseQueryResult<string[]>;
|
||||||
|
allowMultipleFilters?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
const filters = useListFiltersForField(props.dataSource, props.id);
|
export function FilteredQueryColumn<Obj>(props: FilteredQueryColumnProps<Obj>) {
|
||||||
|
const query = props.filterValueQuery;
|
||||||
|
|
||||||
|
let filters: ColumnFilterItem[] = [];
|
||||||
|
if (query.data) {
|
||||||
|
filters = query.data.map((item) => {
|
||||||
|
return {
|
||||||
|
text: item,
|
||||||
|
value: item,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
filters.push({
|
||||||
|
text: "<empty>",
|
||||||
|
value: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
const typedFilters = typeFilters<Obj>(props.tableState.filters);
|
||||||
const filteredValue = getFiltersForField(typedFilters, props.id);
|
const filteredValue = getFiltersForField(typedFilters, props.id);
|
||||||
|
|
||||||
return Column({ ...props, filters, filteredValue });
|
return Column({ ...props, filters, filteredValue });
|
||||||
@@ -127,14 +150,28 @@ export function DateColumn<Obj>(props: BaseColumnProps<Obj>) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SpoolIconColumnProps<Obj> extends BaseColumnProps<Obj> {
|
interface SpoolIconColumnProps<Obj> extends FilteredQueryColumnProps<Obj> {
|
||||||
color: (record: Obj) => string | undefined;
|
color: (record: Obj) => string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SpoolIconColumn<Obj>(props: SpoolIconColumnProps<Obj>) {
|
export function SpoolIconColumn<Obj>(props: SpoolIconColumnProps<Obj>) {
|
||||||
const typedFilters = typeFilters<Obj>(props.tableState.filters);
|
const query = props.filterValueQuery;
|
||||||
|
|
||||||
const filters = useListFiltersForField(props.dataSource, props.id);
|
let filters: ColumnFilterItem[] = [];
|
||||||
|
if (query.data) {
|
||||||
|
filters = query.data.map((item) => {
|
||||||
|
return {
|
||||||
|
text: item,
|
||||||
|
value: item,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
filters.push({
|
||||||
|
text: "<empty>",
|
||||||
|
value: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
const typedFilters = typeFilters<Obj>(props.tableState.filters);
|
||||||
const filteredValue = getFiltersForField(typedFilters, props.id);
|
const filteredValue = getFiltersForField(typedFilters, props.id);
|
||||||
|
|
||||||
return Column({
|
return Column({
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ const dataProvider = (
|
|||||||
getList: async ({ resource, meta, pagination, sorters, filters }) => {
|
getList: async ({ resource, meta, pagination, sorters, filters }) => {
|
||||||
const url = `${apiUrl}/${resource}`;
|
const url = `${apiUrl}/${resource}`;
|
||||||
|
|
||||||
const { headers: headersFromMeta, method, queryParams } = meta ?? {};
|
const { headers: headersFromMeta, method } = meta ?? {};
|
||||||
|
const queryParams: Record<string, string | number> = meta?.queryParams ?? {};
|
||||||
const requestMethod = (method as MethodTypes) ?? "get";
|
const requestMethod = (method as MethodTypes) ?? "get";
|
||||||
|
|
||||||
if (pagination && pagination.mode == "server") {
|
if (pagination && pagination.mode == "server") {
|
||||||
@@ -28,22 +29,60 @@ const dataProvider = (
|
|||||||
|
|
||||||
if (sorters && sorters.length > 0) {
|
if (sorters && sorters.length > 0) {
|
||||||
queryParams["sort"] = sorters.map((sort) => {
|
queryParams["sort"] = sorters.map((sort) => {
|
||||||
const field = sort.field.replace("_", ".")
|
const field = sort.field
|
||||||
return `${field}:${sort.order}`;
|
return `${field}:${sort.order}`;
|
||||||
}).join(",")
|
}).join(",")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filters && filters.length > 0) {
|
if (filters && filters.length > 0) {
|
||||||
|
// Pre-process the filters and handle the special case where we want to split the filament name into vendor and filament name
|
||||||
|
if (resource === "spool") {
|
||||||
|
filters = structuredClone(filters)
|
||||||
|
|
||||||
|
for (let i = 0; i < filters.length; i++) {
|
||||||
|
const filter = filters[i]
|
||||||
|
if (("field" in filter) && filter.field === "filament.name") {
|
||||||
|
const filamentNames: string[] = []
|
||||||
|
const vendorNames: string[] = []
|
||||||
|
if (Array.isArray(filter.value)) {
|
||||||
|
filter.value.forEach((value: string) => {
|
||||||
|
const split = value.split(" - ")
|
||||||
|
if (split.length === 1) {
|
||||||
|
filamentNames.push(split[0])
|
||||||
|
} else {
|
||||||
|
vendorNames.push(split[0])
|
||||||
|
filamentNames.push(split[1])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
filamentNames.push(filter.value.split(" - ")[1])
|
||||||
|
vendorNames.push(filter.value.split(" - ")[0])
|
||||||
|
}
|
||||||
|
filter.value = filamentNames
|
||||||
|
|
||||||
|
filters.push({
|
||||||
|
field: "vendor.name",
|
||||||
|
operator: "in",
|
||||||
|
value: vendorNames,
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
filters.forEach(filter => {
|
filters.forEach(filter => {
|
||||||
if (!("field" in filter)) {
|
if (!("field" in filter)) {
|
||||||
throw Error("Filter must be a LogicalFilter.")
|
throw Error("Filter must be a LogicalFilter.")
|
||||||
}
|
}
|
||||||
const field = filter.field.replace(".", "_")
|
const field = filter.field
|
||||||
queryParams[field] = filter.value
|
const fieldValue = Array.isArray(filter.value) ? filter.value.join(",") : filter.value
|
||||||
|
if (fieldValue.length > 0) {
|
||||||
|
queryParams[field] = fieldValue
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data } = await httpClient[requestMethod](
|
const { data, headers } = await httpClient[requestMethod](
|
||||||
`${url}`,
|
`${url}`,
|
||||||
{
|
{
|
||||||
headers: headersFromMeta,
|
headers: headersFromMeta,
|
||||||
@@ -51,10 +90,11 @@ const dataProvider = (
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// console.log(url, requestMethod, queryParams, data, headers)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data,
|
data,
|
||||||
total: 100,
|
total: parseInt(headers["x-total-count"]) ?? 100,
|
||||||
//TODO: total: data.length,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
152
client/src/components/otherModels.tsx
Normal file
152
client/src/components/otherModels.tsx
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { IFilament } from "../pages/filaments/model";
|
||||||
|
import { IVendor } from "../pages/vendors/model";
|
||||||
|
|
||||||
|
export function useSpoolmanFilamentFullNames() {
|
||||||
|
const apiEndpoint = import.meta.env.VITE_APIURL;
|
||||||
|
return useQuery<IFilament[], unknown, string[]>({
|
||||||
|
queryKey: ["filaments"],
|
||||||
|
queryFn: async () => {
|
||||||
|
const response = await fetch(apiEndpoint + "/filament");
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Network response was not ok");
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
},
|
||||||
|
select: (data) => {
|
||||||
|
// Concatenate vendor name and filament name
|
||||||
|
let names = data
|
||||||
|
.filter((filament) => {
|
||||||
|
return filament.name !== null && filament.name !== undefined && filament.name !== "";
|
||||||
|
})
|
||||||
|
.map((filament) => {
|
||||||
|
if (filament.vendor?.name) {
|
||||||
|
return `${filament.vendor.name} - ${filament.name ?? "<unknown>"}`;
|
||||||
|
} else {
|
||||||
|
return `${filament.name ?? "<unknown>"}`;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.sort();
|
||||||
|
// Remove duplicates
|
||||||
|
names = [...new Set(names)];
|
||||||
|
return names;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSpoolmanFilamentNames() {
|
||||||
|
const apiEndpoint = import.meta.env.VITE_APIURL;
|
||||||
|
return useQuery<IFilament[], unknown, string[]>({
|
||||||
|
queryKey: ["filaments"],
|
||||||
|
queryFn: async () => {
|
||||||
|
const response = await fetch(apiEndpoint + "/filament");
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Network response was not ok");
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
},
|
||||||
|
select: (data) => {
|
||||||
|
// Concatenate vendor name and filament name
|
||||||
|
let names = data
|
||||||
|
.filter((filament) => {
|
||||||
|
return filament.name !== null && filament.name !== undefined && filament.name !== "";
|
||||||
|
})
|
||||||
|
.map((filament) => {
|
||||||
|
return filament.name ?? "<unknown>";
|
||||||
|
})
|
||||||
|
.sort();
|
||||||
|
// Remove duplicates
|
||||||
|
names = [...new Set(names)];
|
||||||
|
return names;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSpoolmanVendors() {
|
||||||
|
const apiEndpoint = import.meta.env.VITE_APIURL;
|
||||||
|
return useQuery<IVendor[], unknown, string[]>({
|
||||||
|
queryKey: ["vendors"],
|
||||||
|
queryFn: async () => {
|
||||||
|
const response = await fetch(apiEndpoint + "/vendor");
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Network response was not ok");
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
},
|
||||||
|
select: (data) => {
|
||||||
|
return data
|
||||||
|
.map((vendor) => {
|
||||||
|
return vendor.name ?? `ID ${vendor.id}`;
|
||||||
|
})
|
||||||
|
.sort();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSpoolmanMaterials() {
|
||||||
|
const apiEndpoint = import.meta.env.VITE_APIURL;
|
||||||
|
return useQuery<string[]>({
|
||||||
|
queryKey: ["materials"],
|
||||||
|
queryFn: async () => {
|
||||||
|
const response = await fetch(apiEndpoint + "/material");
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Network response was not ok");
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
},
|
||||||
|
select: (data) => {
|
||||||
|
return data.sort();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSpoolmanArticleNumbers() {
|
||||||
|
const apiEndpoint = import.meta.env.VITE_APIURL;
|
||||||
|
return useQuery<string[]>({
|
||||||
|
queryKey: ["articleNumbers"],
|
||||||
|
queryFn: async () => {
|
||||||
|
const response = await fetch(apiEndpoint + "/article-number");
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Network response was not ok");
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
},
|
||||||
|
select: (data) => {
|
||||||
|
return data.sort();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSpoolmanLotNumbers() {
|
||||||
|
const apiEndpoint = import.meta.env.VITE_APIURL;
|
||||||
|
return useQuery<string[]>({
|
||||||
|
queryKey: ["lotNumbers"],
|
||||||
|
queryFn: async () => {
|
||||||
|
const response = await fetch(apiEndpoint + "/lot-number");
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Network response was not ok");
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
},
|
||||||
|
select: (data) => {
|
||||||
|
return data.sort();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSpoolmanLocations() {
|
||||||
|
const apiEndpoint = import.meta.env.VITE_APIURL;
|
||||||
|
return useQuery<string[]>({
|
||||||
|
queryKey: ["locations"],
|
||||||
|
queryFn: async () => {
|
||||||
|
const response = await fetch(apiEndpoint + "/location");
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Network response was not ok");
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
},
|
||||||
|
select: (data) => {
|
||||||
|
return data.sort();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { Modal, Table, Checkbox, Space, Row, Col, message } from "antd";
|
import { Modal, Table, Checkbox, Space, Row, Col, message } from "antd";
|
||||||
import { ISpool } from "../pages/spools/model";
|
import { ISpool } from "../pages/spools/model";
|
||||||
import { FilteredColumn, SortedColumn, SpoolIconColumn } from "./column";
|
import { FilteredQueryColumn, SortedColumn, SpoolIconColumn } from "./column";
|
||||||
import { TableState } from "../utils/saveload";
|
import { TableState } from "../utils/saveload";
|
||||||
import { useTable } from "@refinedev/antd";
|
import { useTable } from "@refinedev/antd";
|
||||||
import { genericSorter, typeSorters } from "../utils/sorting";
|
|
||||||
import { genericFilterer, typeFilters } from "../utils/filtering";
|
|
||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
|
import { useSpoolmanFilamentFullNames, useSpoolmanMaterials } from "./otherModels";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
@@ -16,8 +15,8 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface ISpoolCollapsed extends ISpool {
|
interface ISpoolCollapsed extends ISpool {
|
||||||
filament_name: string;
|
"filament.name": string;
|
||||||
material?: string;
|
"filament.material"?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SpoolSelectModal: React.FC<Props> = ({ visible, description, onCancel, onContinue }) => {
|
const SpoolSelectModal: React.FC<Props> = ({ visible, description, onCancel, onContinue }) => {
|
||||||
@@ -64,28 +63,17 @@ const SpoolSelectModal: React.FC<Props> = ({ visible, description, onCancel, onC
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...element,
|
...element,
|
||||||
filament_name,
|
"filament.name": filament_name,
|
||||||
material: element.filament.material,
|
"filament.material": element.filament.material,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
[tableProps.dataSource]
|
[tableProps.dataSource]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Type the sorters and filters
|
|
||||||
const typedSorters = typeSorters<ISpoolCollapsed>(sorters);
|
|
||||||
const typedFilters = typeFilters<ISpoolCollapsed>(filters);
|
|
||||||
|
|
||||||
// Filter and sort the dataSource
|
|
||||||
const filteredDataSource = React.useMemo(() => {
|
|
||||||
const filtered = dataSource.filter(genericFilterer(typedFilters));
|
|
||||||
filtered.sort(genericSorter(typedSorters));
|
|
||||||
return filtered;
|
|
||||||
}, [dataSource, typedFilters, typedSorters]);
|
|
||||||
|
|
||||||
// Function to add/remove all filtered items from selected items
|
// Function to add/remove all filtered items from selected items
|
||||||
const selectUnselectFiltered = (select: boolean) => {
|
const selectUnselectFiltered = (select: boolean) => {
|
||||||
setSelectedItems((prevSelected) => {
|
setSelectedItems((prevSelected) => {
|
||||||
const filtered = filteredDataSource.map((spool) => spool.id).filter((spool) => !prevSelected.includes(spool));
|
const filtered = dataSource.map((spool) => spool.id).filter((spool) => !prevSelected.includes(spool));
|
||||||
return select ? [...prevSelected, ...filtered] : filtered;
|
return select ? [...prevSelected, ...filtered] : filtered;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -98,7 +86,7 @@ const SpoolSelectModal: React.FC<Props> = ({ visible, description, onCancel, onC
|
|||||||
};
|
};
|
||||||
|
|
||||||
// State for the select/unselect all checkbox
|
// State for the select/unselect all checkbox
|
||||||
const isAllFilteredSelected = filteredDataSource.every((spool) => selectedItems.includes(spool.id));
|
const isAllFilteredSelected = dataSource.every((spool) => selectedItems.includes(spool.id));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
@@ -123,7 +111,7 @@ const SpoolSelectModal: React.FC<Props> = ({ visible, description, onCancel, onC
|
|||||||
<Table
|
<Table
|
||||||
{...tableProps}
|
{...tableProps}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
dataSource={filteredDataSource}
|
dataSource={dataSource}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
scroll={{ x: "max-content", y: 200 }}
|
scroll={{ x: "max-content", y: 200 }}
|
||||||
>
|
>
|
||||||
@@ -140,17 +128,19 @@ const SpoolSelectModal: React.FC<Props> = ({ visible, description, onCancel, onC
|
|||||||
tableState,
|
tableState,
|
||||||
})}
|
})}
|
||||||
{SpoolIconColumn({
|
{SpoolIconColumn({
|
||||||
id: "filament_name",
|
id: "filament.name",
|
||||||
i18ncat: "spool",
|
i18ncat: "spool",
|
||||||
color: (record: ISpoolCollapsed) => record.filament.color_hex,
|
color: (record: ISpoolCollapsed) => record.filament.color_hex,
|
||||||
dataSource,
|
dataSource,
|
||||||
tableState,
|
tableState,
|
||||||
|
filterValueQuery: useSpoolmanFilamentFullNames(),
|
||||||
})}
|
})}
|
||||||
{FilteredColumn({
|
{FilteredQueryColumn({
|
||||||
id: "material",
|
id: "filament.material",
|
||||||
i18ncat: "spool",
|
i18nkey: "spool.fields.material",
|
||||||
dataSource,
|
dataSource,
|
||||||
tableState,
|
tableState,
|
||||||
|
filterValueQuery: useSpoolmanMaterials(),
|
||||||
})}
|
})}
|
||||||
</Table>
|
</Table>
|
||||||
<Row>
|
<Row>
|
||||||
|
|||||||
@@ -5,46 +5,51 @@ import { Table, Space, Button, Dropdown } from "antd";
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import utc from "dayjs/plugin/utc";
|
import utc from "dayjs/plugin/utc";
|
||||||
import { IFilament } from "./model";
|
import { IFilament } from "./model";
|
||||||
import { genericSorter, typeSorters } from "../../utils/sorting";
|
|
||||||
import { genericFilterer, typeFilters } from "../../utils/filtering";
|
|
||||||
import { EditOutlined, FilterOutlined } from "@ant-design/icons";
|
import { EditOutlined, FilterOutlined } from "@ant-design/icons";
|
||||||
import { TableState, useInitialTableState, useStoreInitialState } from "../../utils/saveload";
|
import { TableState, useInitialTableState, useStoreInitialState } from "../../utils/saveload";
|
||||||
import {
|
import {
|
||||||
DateColumn,
|
DateColumn,
|
||||||
FilteredColumn,
|
FilteredQueryColumn,
|
||||||
NumberColumn,
|
NumberColumn,
|
||||||
RichColumn,
|
RichColumn,
|
||||||
SortedColumn,
|
SortedColumn,
|
||||||
SpoolIconColumn,
|
SpoolIconColumn,
|
||||||
} from "../../components/column";
|
} from "../../components/column";
|
||||||
|
import {
|
||||||
|
useSpoolmanArticleNumbers,
|
||||||
|
useSpoolmanFilamentNames,
|
||||||
|
useSpoolmanMaterials,
|
||||||
|
useSpoolmanVendors,
|
||||||
|
} from "../../components/otherModels";
|
||||||
|
|
||||||
dayjs.extend(utc);
|
dayjs.extend(utc);
|
||||||
|
|
||||||
interface IFilamentCollapsed extends Omit<IFilament, "vendor"> {
|
interface IFilamentCollapsed extends Omit<IFilament, "vendor"> {
|
||||||
vendor_name: string | null;
|
"vendor.name": string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const namespace = "filamentList-v2";
|
||||||
|
|
||||||
export const FilamentList: React.FC<IResourceComponentsProps> = () => {
|
export const FilamentList: React.FC<IResourceComponentsProps> = () => {
|
||||||
const t = useTranslate();
|
const t = useTranslate();
|
||||||
|
|
||||||
// Load initial state
|
// Load initial state
|
||||||
const initialState = useInitialTableState("filamentList");
|
const initialState = useInitialTableState(namespace);
|
||||||
|
|
||||||
// Fetch data from the API
|
// Fetch data from the API
|
||||||
const { tableProps, sorters, setSorters, filters, setFilters, current, pageSize, setCurrent, setPageSize } =
|
const { tableProps, sorters, setSorters, filters, setFilters, current, pageSize, setCurrent } = useTable<IFilament>({
|
||||||
useTable<IFilament>({
|
|
||||||
syncWithLocation: false,
|
syncWithLocation: false,
|
||||||
pagination: {
|
pagination: {
|
||||||
mode: "off", // Perform pagination in antd's Table instead. Otherwise client-side sorting/filtering doesn't work.
|
mode: "server",
|
||||||
current: initialState.pagination.current,
|
current: initialState.pagination.current,
|
||||||
pageSize: initialState.pagination.pageSize,
|
pageSize: initialState.pagination.pageSize,
|
||||||
},
|
},
|
||||||
sorters: {
|
sorters: {
|
||||||
mode: "off", // Disable server-side sorting
|
mode: "server",
|
||||||
initial: initialState.sorters,
|
initial: initialState.sorters,
|
||||||
},
|
},
|
||||||
filters: {
|
filters: {
|
||||||
mode: "off", // Disable server-side filtering
|
mode: "server",
|
||||||
initial: initialState.filters,
|
initial: initialState.filters,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -52,7 +57,7 @@ export const FilamentList: React.FC<IResourceComponentsProps> = () => {
|
|||||||
// Create state for the columns to show
|
// Create state for the columns to show
|
||||||
const allColumns: (keyof IFilamentCollapsed & string)[] = [
|
const allColumns: (keyof IFilamentCollapsed & string)[] = [
|
||||||
"id",
|
"id",
|
||||||
"vendor_name",
|
"vendor.name",
|
||||||
"name",
|
"name",
|
||||||
"material",
|
"material",
|
||||||
"price",
|
"price",
|
||||||
@@ -72,10 +77,6 @@ export const FilamentList: React.FC<IResourceComponentsProps> = () => {
|
|||||||
|
|
||||||
const [showColumns, setShowColumns] = React.useState<string[]>(initialState.showColumns ?? defaultColumns);
|
const [showColumns, setShowColumns] = React.useState<string[]>(initialState.showColumns ?? defaultColumns);
|
||||||
|
|
||||||
// Type the sorters and filters
|
|
||||||
const typedSorters = typeSorters<IFilamentCollapsed>(sorters);
|
|
||||||
const typedFilters = typeFilters<IFilamentCollapsed>(filters);
|
|
||||||
|
|
||||||
// Store state in local storage
|
// Store state in local storage
|
||||||
const tableState: TableState = {
|
const tableState: TableState = {
|
||||||
sorters,
|
sorters,
|
||||||
@@ -83,7 +84,7 @@ export const FilamentList: React.FC<IResourceComponentsProps> = () => {
|
|||||||
pagination: { current, pageSize },
|
pagination: { current, pageSize },
|
||||||
showColumns,
|
showColumns,
|
||||||
};
|
};
|
||||||
useStoreInitialState("filamentList", tableState);
|
useStoreInitialState(namespace, tableState);
|
||||||
|
|
||||||
// Collapse the dataSource to a mutable list and add a filament_name field
|
// Collapse the dataSource to a mutable list and add a filament_name field
|
||||||
const dataSource: IFilamentCollapsed[] = React.useMemo(
|
const dataSource: IFilamentCollapsed[] = React.useMemo(
|
||||||
@@ -95,17 +96,14 @@ export const FilamentList: React.FC<IResourceComponentsProps> = () => {
|
|||||||
} else {
|
} else {
|
||||||
vendor_name = null;
|
vendor_name = null;
|
||||||
}
|
}
|
||||||
return { ...element, vendor_name };
|
return { ...element, "vendor.name": vendor_name };
|
||||||
}),
|
}),
|
||||||
[tableProps.dataSource]
|
[tableProps.dataSource]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Filter and sort the dataSource
|
if (tableProps.pagination) {
|
||||||
const filteredDataSource = React.useMemo(() => {
|
tableProps.pagination.showSizeChanger = true;
|
||||||
const filtered = dataSource.filter(genericFilterer(typedFilters));
|
}
|
||||||
filtered.sort(genericSorter(typedSorters));
|
|
||||||
return filtered;
|
|
||||||
}, [dataSource, typedFilters, typedSorters]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<List
|
<List
|
||||||
@@ -148,31 +146,20 @@ export const FilamentList: React.FC<IResourceComponentsProps> = () => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Table
|
<Table {...tableProps} dataSource={dataSource} rowKey="id">
|
||||||
{...tableProps}
|
|
||||||
dataSource={filteredDataSource}
|
|
||||||
pagination={{
|
|
||||||
showSizeChanger: true,
|
|
||||||
current: current,
|
|
||||||
pageSize: pageSize,
|
|
||||||
onChange: (page, pageSize) => {
|
|
||||||
setCurrent(page);
|
|
||||||
setPageSize(pageSize);
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
rowKey="id"
|
|
||||||
>
|
|
||||||
{SortedColumn({
|
{SortedColumn({
|
||||||
id: "id",
|
id: "id",
|
||||||
i18ncat: "filament",
|
i18ncat: "filament",
|
||||||
dataSource,
|
dataSource,
|
||||||
tableState,
|
tableState,
|
||||||
})}
|
})}
|
||||||
{FilteredColumn({
|
{FilteredQueryColumn({
|
||||||
id: "vendor_name",
|
id: "vendor.name",
|
||||||
i18ncat: "filament",
|
i18nkey: "filament.fields.vendor_name",
|
||||||
dataSource,
|
dataSource,
|
||||||
tableState,
|
tableState,
|
||||||
|
filterValueQuery: useSpoolmanVendors(),
|
||||||
|
allowMultipleFilters: false,
|
||||||
})}
|
})}
|
||||||
{SpoolIconColumn({
|
{SpoolIconColumn({
|
||||||
id: "name",
|
id: "name",
|
||||||
@@ -180,12 +167,16 @@ export const FilamentList: React.FC<IResourceComponentsProps> = () => {
|
|||||||
color: (record: IFilamentCollapsed) => record.color_hex,
|
color: (record: IFilamentCollapsed) => record.color_hex,
|
||||||
dataSource,
|
dataSource,
|
||||||
tableState,
|
tableState,
|
||||||
|
filterValueQuery: useSpoolmanFilamentNames(),
|
||||||
|
allowMultipleFilters: false,
|
||||||
})}
|
})}
|
||||||
{FilteredColumn({
|
{FilteredQueryColumn({
|
||||||
id: "material",
|
id: "material",
|
||||||
i18ncat: "filament",
|
i18ncat: "filament",
|
||||||
dataSource,
|
dataSource,
|
||||||
tableState,
|
tableState,
|
||||||
|
filterValueQuery: useSpoolmanMaterials(),
|
||||||
|
allowMultipleFilters: false,
|
||||||
})}
|
})}
|
||||||
{SortedColumn({
|
{SortedColumn({
|
||||||
id: "price",
|
id: "price",
|
||||||
@@ -225,11 +216,13 @@ export const FilamentList: React.FC<IResourceComponentsProps> = () => {
|
|||||||
dataSource,
|
dataSource,
|
||||||
tableState,
|
tableState,
|
||||||
})}
|
})}
|
||||||
{FilteredColumn({
|
{FilteredQueryColumn({
|
||||||
id: "article_number",
|
id: "article_number",
|
||||||
i18ncat: "filament",
|
i18ncat: "filament",
|
||||||
dataSource,
|
dataSource,
|
||||||
tableState,
|
tableState,
|
||||||
|
filterValueQuery: useSpoolmanArticleNumbers(),
|
||||||
|
allowMultipleFilters: false,
|
||||||
})}
|
})}
|
||||||
{NumberColumn({
|
{NumberColumn({
|
||||||
id: "settings_extruder_temp",
|
id: "settings_extruder_temp",
|
||||||
|
|||||||
@@ -4,14 +4,12 @@ import { useTable, List, EditButton, ShowButton, CloneButton } from "@refinedev/
|
|||||||
import { Table, Space, Button, Dropdown, Modal } from "antd";
|
import { Table, Space, Button, Dropdown, Modal } from "antd";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import utc from "dayjs/plugin/utc";
|
import utc from "dayjs/plugin/utc";
|
||||||
import { genericSorter, typeSorters } from "../../utils/sorting";
|
|
||||||
import { genericFilterer, typeFilters } from "../../utils/filtering";
|
|
||||||
import { ISpool } from "./model";
|
import { ISpool } from "./model";
|
||||||
import { TableState, useInitialTableState, useSavedState, useStoreInitialState } from "../../utils/saveload";
|
import { TableState, useInitialTableState, useSavedState, useStoreInitialState } from "../../utils/saveload";
|
||||||
import { EditOutlined, FilterOutlined, InboxOutlined, ToTopOutlined } from "@ant-design/icons";
|
import { EditOutlined, FilterOutlined, InboxOutlined, ToTopOutlined } from "@ant-design/icons";
|
||||||
import {
|
import {
|
||||||
DateColumn,
|
DateColumn,
|
||||||
FilteredColumn,
|
FilteredQueryColumn,
|
||||||
NumberColumn,
|
NumberColumn,
|
||||||
RichColumn,
|
RichColumn,
|
||||||
SortedColumn,
|
SortedColumn,
|
||||||
@@ -19,29 +17,36 @@ import {
|
|||||||
} from "../../components/column";
|
} from "../../components/column";
|
||||||
import { setSpoolArchived } from "./functions";
|
import { setSpoolArchived } from "./functions";
|
||||||
import SelectAndPrint from "../../components/selectAndPrintDialog";
|
import SelectAndPrint from "../../components/selectAndPrintDialog";
|
||||||
|
import {
|
||||||
|
useSpoolmanFilamentFullNames,
|
||||||
|
useSpoolmanLocations,
|
||||||
|
useSpoolmanLotNumbers,
|
||||||
|
useSpoolmanMaterials,
|
||||||
|
} from "../../components/otherModels";
|
||||||
|
|
||||||
dayjs.extend(utc);
|
dayjs.extend(utc);
|
||||||
|
|
||||||
const { confirm } = Modal;
|
const { confirm } = Modal;
|
||||||
|
|
||||||
interface ISpoolCollapsed extends ISpool {
|
interface ISpoolCollapsed extends ISpool {
|
||||||
filament_name: string;
|
"filament.name": string;
|
||||||
filament_material?: string;
|
"filament.material"?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const namespace = "spoolList-v2";
|
||||||
|
|
||||||
export const SpoolList: React.FC<IResourceComponentsProps> = () => {
|
export const SpoolList: React.FC<IResourceComponentsProps> = () => {
|
||||||
const t = useTranslate();
|
const t = useTranslate();
|
||||||
const invalidate = useInvalidate();
|
const invalidate = useInvalidate();
|
||||||
|
|
||||||
// Load initial state
|
// Load initial state
|
||||||
const initialState = useInitialTableState("spoolList");
|
const initialState = useInitialTableState(namespace);
|
||||||
|
|
||||||
// State for the switch to show archived spools
|
// State for the switch to show archived spools
|
||||||
const [showArchived, setShowArchived] = useSavedState("spoolList-showArchived", false);
|
const [showArchived, setShowArchived] = useSavedState("spoolList-showArchived", false);
|
||||||
|
|
||||||
// Fetch data from the API
|
// Fetch data from the API
|
||||||
const { tableProps, sorters, setSorters, filters, setFilters, current, pageSize, setCurrent, setPageSize } =
|
const { tableProps, sorters, setSorters, filters, setFilters, current, pageSize, setCurrent } = useTable<ISpool>({
|
||||||
useTable<ISpool>({
|
|
||||||
meta: {
|
meta: {
|
||||||
queryParams: {
|
queryParams: {
|
||||||
["allow_archived"]: showArchived,
|
["allow_archived"]: showArchived,
|
||||||
@@ -66,8 +71,8 @@ export const SpoolList: React.FC<IResourceComponentsProps> = () => {
|
|||||||
// Create state for the columns to show
|
// Create state for the columns to show
|
||||||
const allColumns: (keyof ISpoolCollapsed & string)[] = [
|
const allColumns: (keyof ISpoolCollapsed & string)[] = [
|
||||||
"id",
|
"id",
|
||||||
"filament_name",
|
"filament.name",
|
||||||
"filament_material",
|
"filament.material",
|
||||||
"used_weight",
|
"used_weight",
|
||||||
"remaining_weight",
|
"remaining_weight",
|
||||||
"used_length",
|
"used_length",
|
||||||
@@ -91,7 +96,7 @@ export const SpoolList: React.FC<IResourceComponentsProps> = () => {
|
|||||||
pagination: { current, pageSize },
|
pagination: { current, pageSize },
|
||||||
showColumns,
|
showColumns,
|
||||||
};
|
};
|
||||||
useStoreInitialState("spoolList", tableState);
|
useStoreInitialState(namespace, tableState);
|
||||||
|
|
||||||
// Collapse the dataSource to a mutable list and add a filament_name field
|
// Collapse the dataSource to a mutable list and add a filament_name field
|
||||||
const dataSource: ISpoolCollapsed[] = React.useMemo(
|
const dataSource: ISpoolCollapsed[] = React.useMemo(
|
||||||
@@ -105,8 +110,8 @@ export const SpoolList: React.FC<IResourceComponentsProps> = () => {
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...element,
|
...element,
|
||||||
filament_name,
|
"filament.name": filament_name,
|
||||||
filament_material: element.filament.material,
|
"filament.material": element.filament.material,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
[tableProps.dataSource]
|
[tableProps.dataSource]
|
||||||
@@ -143,6 +148,10 @@ export const SpoolList: React.FC<IResourceComponentsProps> = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (tableProps.pagination) {
|
||||||
|
tableProps.pagination.showSizeChanger = true;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<List
|
<List
|
||||||
headerButtons={({ defaultButtons }) => (
|
headerButtons={({ defaultButtons }) => (
|
||||||
@@ -219,17 +228,21 @@ export const SpoolList: React.FC<IResourceComponentsProps> = () => {
|
|||||||
tableState,
|
tableState,
|
||||||
})}
|
})}
|
||||||
{SpoolIconColumn({
|
{SpoolIconColumn({
|
||||||
id: "filament_name",
|
id: "filament.name",
|
||||||
i18ncat: "spool",
|
i18nkey: "spool.fields.filament_name",
|
||||||
color: (record: ISpoolCollapsed) => record.filament.color_hex,
|
color: (record: ISpoolCollapsed) => record.filament.color_hex,
|
||||||
dataSource,
|
dataSource,
|
||||||
tableState,
|
tableState,
|
||||||
|
filterValueQuery: useSpoolmanFilamentFullNames(),
|
||||||
|
allowMultipleFilters: false,
|
||||||
})}
|
})}
|
||||||
{FilteredColumn({
|
{FilteredQueryColumn({
|
||||||
id: "filament_material",
|
id: "filament.material",
|
||||||
i18ncat: "spool",
|
i18nkey: "spool.fields.material",
|
||||||
dataSource,
|
dataSource,
|
||||||
tableState,
|
tableState,
|
||||||
|
filterValueQuery: useSpoolmanMaterials(),
|
||||||
|
allowMultipleFilters: false,
|
||||||
})}
|
})}
|
||||||
{NumberColumn({
|
{NumberColumn({
|
||||||
id: "used_weight",
|
id: "used_weight",
|
||||||
@@ -265,17 +278,21 @@ export const SpoolList: React.FC<IResourceComponentsProps> = () => {
|
|||||||
dataSource,
|
dataSource,
|
||||||
tableState,
|
tableState,
|
||||||
})}
|
})}
|
||||||
{FilteredColumn({
|
{FilteredQueryColumn({
|
||||||
id: "location",
|
id: "location",
|
||||||
i18ncat: "spool",
|
i18ncat: "spool",
|
||||||
dataSource,
|
dataSource,
|
||||||
tableState,
|
tableState,
|
||||||
|
filterValueQuery: useSpoolmanLocations(),
|
||||||
|
allowMultipleFilters: false,
|
||||||
})}
|
})}
|
||||||
{FilteredColumn({
|
{FilteredQueryColumn({
|
||||||
id: "lot_nr",
|
id: "lot_nr",
|
||||||
i18ncat: "spool",
|
i18ncat: "spool",
|
||||||
dataSource,
|
dataSource,
|
||||||
tableState,
|
tableState,
|
||||||
|
filterValueQuery: useSpoolmanLotNumbers(),
|
||||||
|
allowMultipleFilters: false,
|
||||||
})}
|
})}
|
||||||
{DateColumn({
|
{DateColumn({
|
||||||
id: "first_used",
|
id: "first_used",
|
||||||
|
|||||||
45
client/src/pages/vendors/list.tsx
vendored
45
client/src/pages/vendors/list.tsx
vendored
@@ -4,8 +4,6 @@ import { useTable, List, EditButton, ShowButton, CloneButton } from "@refinedev/
|
|||||||
import { Table, Space, Button, Dropdown } from "antd";
|
import { Table, Space, Button, Dropdown } from "antd";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import utc from "dayjs/plugin/utc";
|
import utc from "dayjs/plugin/utc";
|
||||||
import { genericSorter, typeSorters } from "../../utils/sorting";
|
|
||||||
import { genericFilterer, typeFilters } from "../../utils/filtering";
|
|
||||||
import { IVendor } from "./model";
|
import { IVendor } from "./model";
|
||||||
import { TableState, useInitialTableState, useStoreInitialState } from "../../utils/saveload";
|
import { TableState, useInitialTableState, useStoreInitialState } from "../../utils/saveload";
|
||||||
import { EditOutlined, FilterOutlined } from "@ant-design/icons";
|
import { EditOutlined, FilterOutlined } from "@ant-design/icons";
|
||||||
@@ -13,27 +11,28 @@ import { DateColumn, RichColumn, SortedColumn } from "../../components/column";
|
|||||||
|
|
||||||
dayjs.extend(utc);
|
dayjs.extend(utc);
|
||||||
|
|
||||||
|
const namespace = "vendorList-v2";
|
||||||
|
|
||||||
export const VendorList: React.FC<IResourceComponentsProps> = () => {
|
export const VendorList: React.FC<IResourceComponentsProps> = () => {
|
||||||
const t = useTranslate();
|
const t = useTranslate();
|
||||||
|
|
||||||
// Load initial state
|
// Load initial state
|
||||||
const initialState = useInitialTableState("vendorList");
|
const initialState = useInitialTableState(namespace);
|
||||||
|
|
||||||
// Fetch data from the API
|
// Fetch data from the API
|
||||||
const { tableProps, sorters, setSorters, filters, setFilters, current, pageSize, setCurrent, setPageSize } =
|
const { tableProps, sorters, setSorters, filters, setFilters, current, pageSize, setCurrent } = useTable<IVendor>({
|
||||||
useTable<IVendor>({
|
|
||||||
syncWithLocation: false,
|
syncWithLocation: false,
|
||||||
pagination: {
|
pagination: {
|
||||||
mode: "off", // Perform pagination in antd's Table instead. Otherwise client-side sorting/filtering doesn't work.
|
mode: "server",
|
||||||
current: initialState.pagination.current,
|
current: initialState.pagination.current,
|
||||||
pageSize: initialState.pagination.pageSize,
|
pageSize: initialState.pagination.pageSize,
|
||||||
},
|
},
|
||||||
sorters: {
|
sorters: {
|
||||||
mode: "off", // Disable server-side sorting
|
mode: "server",
|
||||||
initial: initialState.sorters,
|
initial: initialState.sorters,
|
||||||
},
|
},
|
||||||
filters: {
|
filters: {
|
||||||
mode: "off", // Disable server-side filtering
|
mode: "server",
|
||||||
initial: initialState.filters,
|
initial: initialState.filters,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -42,10 +41,6 @@ export const VendorList: React.FC<IResourceComponentsProps> = () => {
|
|||||||
const allColumns: (keyof IVendor & string)[] = ["id", "name", "registered", "comment"];
|
const allColumns: (keyof IVendor & string)[] = ["id", "name", "registered", "comment"];
|
||||||
const [showColumns, setShowColumns] = React.useState<string[]>(initialState.showColumns ?? allColumns);
|
const [showColumns, setShowColumns] = React.useState<string[]>(initialState.showColumns ?? allColumns);
|
||||||
|
|
||||||
// Type the sorters and filters
|
|
||||||
const typedSorters = typeSorters<IVendor>(sorters);
|
|
||||||
const typedFilters = typeFilters<IVendor>(filters);
|
|
||||||
|
|
||||||
// Store state in local storage
|
// Store state in local storage
|
||||||
const tableState: TableState = {
|
const tableState: TableState = {
|
||||||
sorters,
|
sorters,
|
||||||
@@ -53,7 +48,7 @@ export const VendorList: React.FC<IResourceComponentsProps> = () => {
|
|||||||
pagination: { current, pageSize },
|
pagination: { current, pageSize },
|
||||||
showColumns,
|
showColumns,
|
||||||
};
|
};
|
||||||
useStoreInitialState("vendorList", tableState);
|
useStoreInitialState(namespace, tableState);
|
||||||
|
|
||||||
// Collapse the dataSource to a mutable list
|
// Collapse the dataSource to a mutable list
|
||||||
const dataSource: IVendor[] = React.useMemo(
|
const dataSource: IVendor[] = React.useMemo(
|
||||||
@@ -61,12 +56,9 @@ export const VendorList: React.FC<IResourceComponentsProps> = () => {
|
|||||||
[tableProps.dataSource]
|
[tableProps.dataSource]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Filter and sort the dataSource
|
if (tableProps.pagination) {
|
||||||
const filteredDataSource = React.useMemo(() => {
|
tableProps.pagination.showSizeChanger = true;
|
||||||
const filtered = dataSource.filter(genericFilterer(typedFilters));
|
}
|
||||||
filtered.sort(genericSorter(typedSorters));
|
|
||||||
return filtered;
|
|
||||||
}, [dataSource, typedFilters, typedSorters]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<List
|
<List
|
||||||
@@ -109,20 +101,7 @@ export const VendorList: React.FC<IResourceComponentsProps> = () => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Table
|
<Table {...tableProps} dataSource={dataSource} rowKey="id">
|
||||||
{...tableProps}
|
|
||||||
dataSource={filteredDataSource}
|
|
||||||
pagination={{
|
|
||||||
showSizeChanger: true,
|
|
||||||
current: current,
|
|
||||||
pageSize: pageSize,
|
|
||||||
onChange: (page, pageSize) => {
|
|
||||||
setCurrent(page);
|
|
||||||
setPageSize(pageSize);
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
rowKey="id"
|
|
||||||
>
|
|
||||||
{SortedColumn({
|
{SortedColumn({
|
||||||
id: "id",
|
id: "id",
|
||||||
i18ncat: "vendor",
|
i18ncat: "vendor",
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import { CrudFilter, CrudOperators } from "@refinedev/core";
|
import { CrudFilter, CrudOperators } from "@refinedev/core";
|
||||||
import { ColumnFilterItem } from "antd/es/table/interface";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
interface TypedCrudFilter<Obj> {
|
interface TypedCrudFilter<Obj> {
|
||||||
field: keyof Obj;
|
field: keyof Obj;
|
||||||
@@ -12,92 +10,6 @@ export function typeFilters<Obj>(filters: CrudFilter[]): TypedCrudFilter<Obj>[]
|
|||||||
return filters as TypedCrudFilter<Obj>[]; // <-- Unsafe cast
|
return filters as TypedCrudFilter<Obj>[]; // <-- Unsafe cast
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a filtering function that can be used to filter an array of objects based on the provided filters.
|
|
||||||
* @param filters An array of `CrudFilter` objects that define the filtering criteria.
|
|
||||||
* @returns A function that returns a boolean value based on whether the provided object matches the filtering criteria.
|
|
||||||
*/
|
|
||||||
export function genericFilterer<Obj>(filters: TypedCrudFilter<Obj>[]) {
|
|
||||||
return (record: Obj) => {
|
|
||||||
for (const filter of filters) {
|
|
||||||
if (!("field" in filter)) {
|
|
||||||
console.error("Filter must be of type LogicalFilter");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!Array.isArray(filter.value)) {
|
|
||||||
console.error("Filter value must be an array of strings.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!filter.value.length) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const value = record[filter.field];
|
|
||||||
let strValue: string;
|
|
||||||
if (value === undefined || value === null) {
|
|
||||||
strValue = "";
|
|
||||||
} else {
|
|
||||||
if (typeof value !== "string") {
|
|
||||||
console.error("Only string fields can be filtered, field is of type " + typeof value);
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
strValue = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (filter.operator) {
|
|
||||||
case "in":
|
|
||||||
if (!filter.value.includes(strValue)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.error(`Not implemented operator: ${filter.operator}`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Populates an array of `ColumnFilterItem` objects based on the unique values of a given field in an array of objects.
|
|
||||||
* @param dataSource An array of objects to filter.
|
|
||||||
* @param field The name of the field to filter on.
|
|
||||||
* @returns An array of `ColumnFilterItem` objects representing the unique values of the specified field in the input array.
|
|
||||||
*/
|
|
||||||
export function useListFiltersForField<Obj, Field extends keyof Obj>(
|
|
||||||
dataSource: Obj[],
|
|
||||||
field: Field): ColumnFilterItem[] {
|
|
||||||
return React.useMemo(() => {
|
|
||||||
const filters: ColumnFilterItem[] = [];
|
|
||||||
dataSource.forEach((element) => {
|
|
||||||
const value = element[field];
|
|
||||||
if (typeof value === "string" && value !== "") {
|
|
||||||
// Make sure it's not already in the filters
|
|
||||||
if (filters.find((f) => f.value === value)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
filters.push({
|
|
||||||
text: value,
|
|
||||||
value: value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Sort the filters
|
|
||||||
filters.sort((a, b) => {
|
|
||||||
if (typeof a.text !== "string" || typeof b.text !== "string") {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return a.text.localeCompare(b.text);
|
|
||||||
});
|
|
||||||
filters.push({
|
|
||||||
text: "<empty>",
|
|
||||||
value: "",
|
|
||||||
});
|
|
||||||
return filters;
|
|
||||||
}, [dataSource, field]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an array of filter values for a given field based on the provided filters.
|
* Returns an array of filter values for a given field based on the provided filters.
|
||||||
* @param filters An array of `CrudFilter` objects that define the filtering criteria.
|
* @param filters An array of `CrudFilter` objects that define the filtering criteria.
|
||||||
|
|||||||
@@ -7,62 +7,6 @@ interface TypedCrudSort<Obj> {
|
|||||||
order: "asc" | "desc";
|
order: "asc" | "desc";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a sorting function that can be used to sort an array of objects based on the provided sorters.
|
|
||||||
* @param sorters An array of `CrudSort` objects that define the sorting criteria.
|
|
||||||
* @returns A sorting function that can be used to sort an array of objects based on the provided sorters.
|
|
||||||
*/
|
|
||||||
export function genericSorter<Obj>(sorters: TypedCrudSort<Obj>[]) {
|
|
||||||
return (a: Obj, b: Obj) => {
|
|
||||||
for (const sorter of sorters) {
|
|
||||||
const aValue = a[sorter.field];
|
|
||||||
const bValue = b[sorter.field];
|
|
||||||
if (aValue === bValue) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Send empty fields to the bottom
|
|
||||||
if (aValue === undefined || aValue === null) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (bValue === undefined || bValue === null) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
// Perform sorting based on type of the fields
|
|
||||||
if (typeof aValue === "string" && typeof bValue === "string") {
|
|
||||||
// Try to sort them as dates if possible
|
|
||||||
const aDate = new Date(aValue);
|
|
||||||
const bDate = new Date(bValue);
|
|
||||||
if (!isNaN(aDate.getTime()) && !isNaN(bDate.getTime())) {
|
|
||||||
return sorter.order === "asc"
|
|
||||||
? aDate.getTime() - bDate.getTime()
|
|
||||||
: bDate.getTime() - aDate.getTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
return sorter.order === "asc"
|
|
||||||
? aValue.localeCompare(bValue)
|
|
||||||
: bValue.localeCompare(aValue);
|
|
||||||
}
|
|
||||||
if (typeof aValue === "number" && typeof bValue === "number") {
|
|
||||||
return sorter.order === "asc" ? aValue - bValue : bValue - aValue;
|
|
||||||
}
|
|
||||||
if (typeof aValue === "boolean" && typeof bValue === "boolean") {
|
|
||||||
return sorter.order === "asc" ? +aValue - +bValue : +bValue - +aValue;
|
|
||||||
}
|
|
||||||
if (aValue instanceof Date && bValue instanceof Date) {
|
|
||||||
return sorter.order === "asc"
|
|
||||||
? aValue.getTime() - bValue.getTime()
|
|
||||||
: bValue.getTime() - aValue.getTime();
|
|
||||||
}
|
|
||||||
if (typeof aValue === "object" && typeof bValue === "object") {
|
|
||||||
return sorter.order === "asc"
|
|
||||||
? JSON.stringify(aValue).localeCompare(JSON.stringify(bValue))
|
|
||||||
: JSON.stringify(bValue).localeCompare(JSON.stringify(aValue));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the sort order for a given field based on the provided sorters.
|
* Returns the sort order for a given field based on the provided sorters.
|
||||||
* @param sorters An array of `CrudSort` objects that define the sorting criteria.
|
* @param sorters An array of `CrudSort` objects that define the sorting criteria.
|
||||||
|
|||||||
Reference in New Issue
Block a user