Client Table update
* Columns are now specified using the ant design columns property instead of being Table.Column components. This should make this WoW a bit more sense, we're not pretending to be components anymore. column.tsx now only returns objects, not nodes. * Added filament/spool collapsing as a useTable queryOptions so that the output object is directly the one we want, which will make the types play better together. * Added a more unified record action system. This enables us to add actions to column rightclick/long press, which is better UX for mobile devices. * Actions column is now hidden on smaller screens thanks to the above bullet.
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
import { Col, Row, Spin, Table } from "antd";
|
import { Button, Col, Dropdown, Row, Space, Spin } from "antd";
|
||||||
import { ColumnProps as AntdColumnProps } from "antd/es/table";
|
import { ColumnFilterItem, ColumnType } from "antd/es/table/interface";
|
||||||
import { ColumnFilterItem } from "antd/es/table/interface";
|
|
||||||
import { getFiltersForField, typeFilters } 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";
|
||||||
@@ -13,6 +12,7 @@ import SpoolIcon from "../icon_spool.svg?react";
|
|||||||
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";
|
import { UseQueryResult } from "@tanstack/react-query";
|
||||||
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
dayjs.extend(utc);
|
dayjs.extend(utc);
|
||||||
|
|
||||||
@@ -27,7 +27,18 @@ const FilterDropdownLoading = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
interface BaseColumnProps<Obj> {
|
interface Entity {
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Action {
|
||||||
|
name: string;
|
||||||
|
icon: React.ReactNode;
|
||||||
|
link?: string;
|
||||||
|
onClick?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BaseColumnProps<Obj extends Entity> {
|
||||||
id: keyof Obj & string;
|
id: keyof Obj & string;
|
||||||
dataId?: keyof Obj & string;
|
dataId?: keyof Obj & string;
|
||||||
i18ncat?: string;
|
i18ncat?: string;
|
||||||
@@ -35,6 +46,7 @@ interface BaseColumnProps<Obj> {
|
|||||||
dataSource: Obj[];
|
dataSource: Obj[];
|
||||||
tableState: TableState;
|
tableState: TableState;
|
||||||
width?: number;
|
width?: number;
|
||||||
|
actions?: (record: Obj) => Action[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FilteredColumnProps {
|
interface FilteredColumnProps {
|
||||||
@@ -55,22 +67,28 @@ interface CustomColumnProps<Obj> {
|
|||||||
) => React.HTMLAttributes<any> | React.TdHTMLAttributes<any>;
|
) => React.HTMLAttributes<any> | React.TdHTMLAttributes<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Column<Obj>(props: BaseColumnProps<Obj> & FilteredColumnProps & CustomColumnProps<Obj>) {
|
function Column<Obj extends Entity>(
|
||||||
|
props: BaseColumnProps<Obj> & FilteredColumnProps & CustomColumnProps<Obj>
|
||||||
|
): ColumnType<Obj> | undefined {
|
||||||
const t = useTranslate();
|
const t = useTranslate();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
// Hide if not in showColumns
|
||||||
if (props.tableState.showColumns && !props.tableState.showColumns.includes(props.id)) {
|
if (props.tableState.showColumns && !props.tableState.showColumns.includes(props.id)) {
|
||||||
return <></>;
|
return undefined;
|
||||||
}
|
}
|
||||||
const typedSorters = typeSorters<Obj>(props.tableState.sorters);
|
|
||||||
const columnProps: AntdColumnProps<Obj> = {
|
const columnProps: ColumnType<Obj> = {
|
||||||
dataIndex: props.id,
|
dataIndex: props.id,
|
||||||
title: t(props.i18nkey ?? `${props.i18ncat}.fields.${props.id}`),
|
title: t(props.i18nkey ?? `${props.i18ncat}.fields.${props.id}`),
|
||||||
sorter: true,
|
sorter: true,
|
||||||
sortOrder: getSortOrderForField(typedSorters, props.dataId ?? props.id),
|
sortOrder: getSortOrderForField(typeSorters<Obj>(props.tableState.sorters), props.dataId ?? props.id),
|
||||||
filterMultiple: props.allowMultipleFilters ?? true,
|
filterMultiple: props.allowMultipleFilters ?? true,
|
||||||
|
width: props.width ?? undefined,
|
||||||
|
onCell: props.onCell ?? undefined,
|
||||||
};
|
};
|
||||||
if (props.width) {
|
|
||||||
columnProps.width = props.width;
|
// Filter
|
||||||
}
|
|
||||||
if (props.filters && props.filteredValue) {
|
if (props.filters && props.filteredValue) {
|
||||||
columnProps.filters = props.filters;
|
columnProps.filters = props.filters;
|
||||||
columnProps.filteredValue = props.filteredValue;
|
columnProps.filteredValue = props.filteredValue;
|
||||||
@@ -86,20 +104,50 @@ function Column<Obj>(props: BaseColumnProps<Obj> & FilteredColumnProps & CustomC
|
|||||||
columnProps.key = props.dataId;
|
columnProps.key = props.dataId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (props.render) {
|
|
||||||
columnProps.render = props.render;
|
// Render
|
||||||
}
|
const render = props.render ?? ((value) => <>{value}</>);
|
||||||
if (props.onCell) {
|
columnProps.render = (value, record, index) => {
|
||||||
columnProps.onCell = props.onCell;
|
if (!props.actions) {
|
||||||
}
|
return render(value, record, index);
|
||||||
return <Table.Column<Obj> {...columnProps} />;
|
}
|
||||||
|
|
||||||
|
const actions = props.actions(record);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dropdown
|
||||||
|
menu={{
|
||||||
|
items: actions.map((action) => ({
|
||||||
|
key: action.name,
|
||||||
|
label: action.name,
|
||||||
|
icon: action.icon,
|
||||||
|
})),
|
||||||
|
onClick: (item) => {
|
||||||
|
const action = actions.find((action) => action.name === item.key);
|
||||||
|
if (action) {
|
||||||
|
if (action.link) {
|
||||||
|
navigate(action.link);
|
||||||
|
} else if (action.onClick) {
|
||||||
|
action.onClick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
trigger={["contextMenu"]}
|
||||||
|
>
|
||||||
|
<div>{render(value, record, index)}</div>
|
||||||
|
</Dropdown>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return columnProps;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SortedColumn<Obj>(props: BaseColumnProps<Obj>) {
|
export function SortedColumn<Obj extends Entity>(props: BaseColumnProps<Obj>) {
|
||||||
return Column(props);
|
return Column(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function RichColumn<Obj>(props: BaseColumnProps<Obj>) {
|
export function RichColumn<Obj extends Entity>(props: BaseColumnProps<Obj>) {
|
||||||
return Column({
|
return Column({
|
||||||
...props,
|
...props,
|
||||||
render: (value: string | undefined) => {
|
render: (value: string | undefined) => {
|
||||||
@@ -108,12 +156,12 @@ export function RichColumn<Obj>(props: BaseColumnProps<Obj>) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FilteredQueryColumnProps<Obj> extends BaseColumnProps<Obj> {
|
interface FilteredQueryColumnProps<Obj extends Entity> extends BaseColumnProps<Obj> {
|
||||||
filterValueQuery: UseQueryResult<string[] | ColumnFilterItem[]>;
|
filterValueQuery: UseQueryResult<string[] | ColumnFilterItem[]>;
|
||||||
allowMultipleFilters?: boolean;
|
allowMultipleFilters?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FilteredQueryColumn<Obj>(props: FilteredQueryColumnProps<Obj>) {
|
export function FilteredQueryColumn<Obj extends Entity>(props: FilteredQueryColumnProps<Obj>) {
|
||||||
const query = props.filterValueQuery;
|
const query = props.filterValueQuery;
|
||||||
|
|
||||||
let filters: ColumnFilterItem[] = [];
|
let filters: ColumnFilterItem[] = [];
|
||||||
@@ -143,13 +191,13 @@ export function FilteredQueryColumn<Obj>(props: FilteredQueryColumnProps<Obj>) {
|
|||||||
return Column({ ...props, filters, filteredValue, onFilterDropdownOpen, loadingFilters: query.isLoading });
|
return Column({ ...props, filters, filteredValue, onFilterDropdownOpen, loadingFilters: query.isLoading });
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NumberColumnProps<Obj> extends BaseColumnProps<Obj> {
|
interface NumberColumnProps<Obj extends Entity> extends BaseColumnProps<Obj> {
|
||||||
unit: string;
|
unit: string;
|
||||||
decimals?: number;
|
decimals?: number;
|
||||||
defaultText?: string;
|
defaultText?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function NumberColumn<Obj>(props: NumberColumnProps<Obj>) {
|
export function NumberColumn<Obj extends Entity>(props: NumberColumnProps<Obj>) {
|
||||||
return Column({
|
return Column({
|
||||||
...props,
|
...props,
|
||||||
render: (value) => {
|
render: (value) => {
|
||||||
@@ -170,7 +218,7 @@ export function NumberColumn<Obj>(props: NumberColumnProps<Obj>) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DateColumn<Obj>(props: BaseColumnProps<Obj>) {
|
export function DateColumn<Obj extends Entity>(props: BaseColumnProps<Obj>) {
|
||||||
return Column({
|
return Column({
|
||||||
...props,
|
...props,
|
||||||
render: (value) => {
|
render: (value) => {
|
||||||
@@ -186,11 +234,43 @@ export function DateColumn<Obj>(props: BaseColumnProps<Obj>) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SpoolIconColumnProps<Obj> extends FilteredQueryColumnProps<Obj> {
|
export function ActionsColumn<Obj extends Entity>(actionsFn: (record: Obj) => Action[]): ColumnType<Obj> | undefined {
|
||||||
|
const t = useTranslate();
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: t("table.actions"),
|
||||||
|
responsive: ["lg"],
|
||||||
|
render: (_, record) => {
|
||||||
|
const buttons = actionsFn(record).map((action) => {
|
||||||
|
if (action.link) {
|
||||||
|
return (
|
||||||
|
<Link key={action.name} to={action.link}>
|
||||||
|
<Button icon={action.icon} title={action.name} size="small" />
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
} else if (action.onClick) {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
key={action.name}
|
||||||
|
icon={action.icon}
|
||||||
|
title={action.name}
|
||||||
|
size="small"
|
||||||
|
onClick={() => action.onClick!()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return <Space>{buttons}</Space>;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SpoolIconColumnProps<Obj extends Entity> extends FilteredQueryColumnProps<Obj> {
|
||||||
color: (record: Obj) => string | undefined;
|
color: (record: Obj) => string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SpoolIconColumn<Obj>(props: SpoolIconColumnProps<Obj>) {
|
export function SpoolIconColumn<Obj extends Entity>(props: SpoolIconColumnProps<Obj>) {
|
||||||
const query = props.filterValueQuery;
|
const query = props.filterValueQuery;
|
||||||
|
|
||||||
let filters: ColumnFilterItem[] = [];
|
let filters: ColumnFilterItem[] = [];
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { IResourceComponentsProps, BaseRecord, useTranslate, useInvalidate } from "@refinedev/core";
|
import { IResourceComponentsProps, useTranslate, useInvalidate, useNavigation } from "@refinedev/core";
|
||||||
import { useTable, List, EditButton, ShowButton, CloneButton } from "@refinedev/antd";
|
import { useTable, List } from "@refinedev/antd";
|
||||||
import { Table, Space, Button, Dropdown } from "antd";
|
import { Table, 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 { EditOutlined, FilterOutlined } from "@ant-design/icons";
|
import { EditOutlined, EyeOutlined, FilterOutlined, PlusSquareOutlined } from "@ant-design/icons";
|
||||||
import { TableState, useInitialTableState, useStoreInitialState } from "../../utils/saveload";
|
import { TableState, useInitialTableState, useStoreInitialState } from "../../utils/saveload";
|
||||||
import {
|
import {
|
||||||
DateColumn,
|
DateColumn,
|
||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
RichColumn,
|
RichColumn,
|
||||||
SortedColumn,
|
SortedColumn,
|
||||||
SpoolIconColumn,
|
SpoolIconColumn,
|
||||||
|
ActionsColumn,
|
||||||
} from "../../components/column";
|
} from "../../components/column";
|
||||||
import {
|
import {
|
||||||
useSpoolmanArticleNumbers,
|
useSpoolmanArticleNumbers,
|
||||||
@@ -22,6 +23,7 @@ import {
|
|||||||
useSpoolmanVendors,
|
useSpoolmanVendors,
|
||||||
} from "../../components/otherModels";
|
} from "../../components/otherModels";
|
||||||
import { useLiveify } from "../../components/liveify";
|
import { useLiveify } from "../../components/liveify";
|
||||||
|
import { removeUndefined } from "../../utils/filtering";
|
||||||
|
|
||||||
dayjs.extend(utc);
|
dayjs.extend(utc);
|
||||||
|
|
||||||
@@ -46,6 +48,26 @@ function translateColumnI18nKey(columnName: string): string {
|
|||||||
|
|
||||||
const namespace = "filamentList-v2";
|
const namespace = "filamentList-v2";
|
||||||
|
|
||||||
|
const allColumns: (keyof IFilamentCollapsed & string)[] = [
|
||||||
|
"id",
|
||||||
|
"vendor.name",
|
||||||
|
"name",
|
||||||
|
"material",
|
||||||
|
"price",
|
||||||
|
"density",
|
||||||
|
"diameter",
|
||||||
|
"weight",
|
||||||
|
"spool_weight",
|
||||||
|
"article_number",
|
||||||
|
"settings_extruder_temp",
|
||||||
|
"settings_bed_temp",
|
||||||
|
"registered",
|
||||||
|
"comment",
|
||||||
|
];
|
||||||
|
const defaultColumns = allColumns.filter(
|
||||||
|
(column_id) => ["registered", "density", "diameter", "spool_weight"].indexOf(column_id) === -1
|
||||||
|
);
|
||||||
|
|
||||||
export const FilamentList: React.FC<IResourceComponentsProps> = () => {
|
export const FilamentList: React.FC<IResourceComponentsProps> = () => {
|
||||||
const t = useTranslate();
|
const t = useTranslate();
|
||||||
const invalidate = useInvalidate();
|
const invalidate = useInvalidate();
|
||||||
@@ -57,54 +79,43 @@ export const FilamentList: React.FC<IResourceComponentsProps> = () => {
|
|||||||
// To provide the live updates, we use a custom solution (useLiveify) instead of the built-in refine "liveMode" feature.
|
// To provide the live updates, we use a custom solution (useLiveify) instead of the built-in refine "liveMode" feature.
|
||||||
// This is because the built-in feature does not call the liveProvider subscriber with a list of IDs, but instead
|
// This is because the built-in feature does not call the liveProvider subscriber with a list of IDs, but instead
|
||||||
// calls it with a list of filters, sorters, etc. This means the server-side has to support this, which is quite hard.
|
// calls it with a list of filters, sorters, etc. This means the server-side has to support this, which is quite hard.
|
||||||
const { tableProps, sorters, setSorters, filters, setFilters, current, pageSize, setCurrent } = useTable<IFilament>({
|
const { tableProps, sorters, setSorters, filters, setFilters, current, pageSize, setCurrent } =
|
||||||
syncWithLocation: false,
|
useTable<IFilamentCollapsed>({
|
||||||
pagination: {
|
syncWithLocation: false,
|
||||||
mode: "server",
|
pagination: {
|
||||||
current: initialState.pagination.current,
|
mode: "server",
|
||||||
pageSize: initialState.pagination.pageSize,
|
current: initialState.pagination.current,
|
||||||
},
|
pageSize: initialState.pagination.pageSize,
|
||||||
sorters: {
|
},
|
||||||
mode: "server",
|
sorters: {
|
||||||
initial: initialState.sorters,
|
mode: "server",
|
||||||
},
|
initial: initialState.sorters,
|
||||||
filters: {
|
},
|
||||||
mode: "server",
|
filters: {
|
||||||
initial: initialState.filters,
|
mode: "server",
|
||||||
},
|
initial: initialState.filters,
|
||||||
liveMode: "manual",
|
},
|
||||||
onLiveEvent(event) {
|
liveMode: "manual",
|
||||||
if (event.type === "created" || event.type === "deleted") {
|
onLiveEvent(event) {
|
||||||
// updated is handled by the liveify
|
if (event.type === "created" || event.type === "deleted") {
|
||||||
invalidate({
|
// updated is handled by the liveify
|
||||||
resource: "filament",
|
invalidate({
|
||||||
invalidates: ["list"],
|
resource: "filament",
|
||||||
});
|
invalidates: ["list"],
|
||||||
}
|
});
|
||||||
},
|
}
|
||||||
});
|
},
|
||||||
|
queryOptions: {
|
||||||
|
select(data) {
|
||||||
|
return {
|
||||||
|
total: data.total,
|
||||||
|
data: data.data.map(collapseFilament),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// Create state for the columns to show
|
// Create state for the columns to show
|
||||||
const allColumns: (keyof IFilamentCollapsed & string)[] = [
|
|
||||||
"id",
|
|
||||||
"vendor.name",
|
|
||||||
"name",
|
|
||||||
"material",
|
|
||||||
"price",
|
|
||||||
"density",
|
|
||||||
"diameter",
|
|
||||||
"weight",
|
|
||||||
"spool_weight",
|
|
||||||
"article_number",
|
|
||||||
"settings_extruder_temp",
|
|
||||||
"settings_bed_temp",
|
|
||||||
"registered",
|
|
||||||
"comment",
|
|
||||||
];
|
|
||||||
const defaultColumns = allColumns.filter(
|
|
||||||
(column_id) => ["registered", "density", "diameter", "spool_weight"].indexOf(column_id) === -1
|
|
||||||
);
|
|
||||||
|
|
||||||
const [showColumns, setShowColumns] = React.useState<string[]>(initialState.showColumns ?? defaultColumns);
|
const [showColumns, setShowColumns] = React.useState<string[]>(initialState.showColumns ?? defaultColumns);
|
||||||
|
|
||||||
// Store state in local storage
|
// Store state in local storage
|
||||||
@@ -116,9 +127,9 @@ export const FilamentList: React.FC<IResourceComponentsProps> = () => {
|
|||||||
};
|
};
|
||||||
useStoreInitialState(namespace, tableState);
|
useStoreInitialState(namespace, tableState);
|
||||||
|
|
||||||
// Collapse the dataSource to a mutable list and add a filament_name field
|
// Collapse the dataSource to a mutable list
|
||||||
const queryDataSource: IFilamentCollapsed[] = React.useMemo(
|
const queryDataSource: IFilamentCollapsed[] = React.useMemo(
|
||||||
() => (tableProps.dataSource ?? []).map(collapseFilament),
|
() => (tableProps.dataSource || []).map((record) => ({ ...record })),
|
||||||
[tableProps.dataSource]
|
[tableProps.dataSource]
|
||||||
);
|
);
|
||||||
const dataSource = useLiveify("filament", queryDataSource, collapseFilament);
|
const dataSource = useLiveify("filament", queryDataSource, collapseFilament);
|
||||||
@@ -127,6 +138,13 @@ export const FilamentList: React.FC<IResourceComponentsProps> = () => {
|
|||||||
tableProps.pagination.showSizeChanger = true;
|
tableProps.pagination.showSizeChanger = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { editUrl, showUrl, cloneUrl } = useNavigation();
|
||||||
|
const actions = (record: IFilamentCollapsed) => [
|
||||||
|
{ name: t("buttons.edit"), icon: <EditOutlined />, link: editUrl("filament", record.id) },
|
||||||
|
{ name: t("buttons.show"), icon: <EyeOutlined />, link: showUrl("filament", record.id) },
|
||||||
|
{ name: t("buttons.clone"), icon: <PlusSquareOutlined />, link: cloneUrl("filament", record.id) },
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<List
|
<List
|
||||||
headerButtons={({ defaultButtons }) => (
|
headerButtons={({ defaultButtons }) => (
|
||||||
@@ -168,130 +186,142 @@ export const FilamentList: React.FC<IResourceComponentsProps> = () => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Table {...tableProps} sticky scroll={{ x: "max-content" }} dataSource={dataSource} rowKey="id">
|
<Table<IFilamentCollapsed>
|
||||||
{SortedColumn({
|
{...tableProps}
|
||||||
id: "id",
|
sticky
|
||||||
i18ncat: "filament",
|
scroll={{ x: "max-content" }}
|
||||||
dataSource,
|
dataSource={dataSource}
|
||||||
tableState,
|
rowKey="id"
|
||||||
width: 70,
|
columns={removeUndefined([
|
||||||
})}
|
SortedColumn({
|
||||||
{FilteredQueryColumn({
|
id: "id",
|
||||||
id: "vendor.name",
|
i18ncat: "filament",
|
||||||
i18nkey: "filament.fields.vendor_name",
|
actions,
|
||||||
dataSource,
|
dataSource,
|
||||||
tableState,
|
tableState,
|
||||||
filterValueQuery: useSpoolmanVendors(),
|
width: 70,
|
||||||
})}
|
}),
|
||||||
{SpoolIconColumn({
|
FilteredQueryColumn({
|
||||||
id: "name",
|
id: "vendor.name",
|
||||||
i18ncat: "filament",
|
i18nkey: "filament.fields.vendor_name",
|
||||||
color: (record: IFilamentCollapsed) => record.color_hex,
|
actions,
|
||||||
dataSource,
|
dataSource,
|
||||||
tableState,
|
tableState,
|
||||||
filterValueQuery: useSpoolmanFilamentNames(),
|
filterValueQuery: useSpoolmanVendors(),
|
||||||
})}
|
}),
|
||||||
{FilteredQueryColumn({
|
SpoolIconColumn({
|
||||||
id: "material",
|
id: "name",
|
||||||
i18ncat: "filament",
|
i18ncat: "filament",
|
||||||
dataSource,
|
color: (record: IFilamentCollapsed) => record.color_hex,
|
||||||
tableState,
|
actions,
|
||||||
filterValueQuery: useSpoolmanMaterials(),
|
dataSource,
|
||||||
width: 110,
|
tableState,
|
||||||
})}
|
filterValueQuery: useSpoolmanFilamentNames(),
|
||||||
{SortedColumn({
|
}),
|
||||||
id: "price",
|
FilteredQueryColumn({
|
||||||
i18ncat: "filament",
|
id: "material",
|
||||||
dataSource,
|
i18ncat: "filament",
|
||||||
tableState,
|
actions,
|
||||||
width: 80,
|
dataSource,
|
||||||
})}
|
tableState,
|
||||||
{NumberColumn({
|
filterValueQuery: useSpoolmanMaterials(),
|
||||||
id: "density",
|
width: 110,
|
||||||
i18ncat: "filament",
|
}),
|
||||||
unit: "g/cm³",
|
SortedColumn({
|
||||||
decimals: 2,
|
id: "price",
|
||||||
dataSource,
|
i18ncat: "filament",
|
||||||
tableState,
|
actions,
|
||||||
width: 100,
|
dataSource,
|
||||||
})}
|
tableState,
|
||||||
{NumberColumn({
|
width: 80,
|
||||||
id: "diameter",
|
}),
|
||||||
i18ncat: "filament",
|
NumberColumn({
|
||||||
unit: "mm",
|
id: "density",
|
||||||
decimals: 2,
|
i18ncat: "filament",
|
||||||
dataSource,
|
unit: "g/cm³",
|
||||||
tableState,
|
decimals: 2,
|
||||||
width: 100,
|
actions,
|
||||||
})}
|
dataSource,
|
||||||
{NumberColumn({
|
tableState,
|
||||||
id: "weight",
|
width: 100,
|
||||||
i18ncat: "filament",
|
}),
|
||||||
unit: "g",
|
NumberColumn({
|
||||||
decimals: 1,
|
id: "diameter",
|
||||||
dataSource,
|
i18ncat: "filament",
|
||||||
tableState,
|
unit: "mm",
|
||||||
width: 100,
|
decimals: 2,
|
||||||
})}
|
actions,
|
||||||
{NumberColumn({
|
dataSource,
|
||||||
id: "spool_weight",
|
tableState,
|
||||||
i18ncat: "filament",
|
width: 100,
|
||||||
unit: "g",
|
}),
|
||||||
decimals: 1,
|
NumberColumn({
|
||||||
dataSource,
|
id: "weight",
|
||||||
tableState,
|
i18ncat: "filament",
|
||||||
width: 100,
|
unit: "g",
|
||||||
})}
|
decimals: 1,
|
||||||
{FilteredQueryColumn({
|
actions,
|
||||||
id: "article_number",
|
dataSource,
|
||||||
i18ncat: "filament",
|
tableState,
|
||||||
dataSource,
|
width: 100,
|
||||||
tableState,
|
}),
|
||||||
filterValueQuery: useSpoolmanArticleNumbers(),
|
NumberColumn({
|
||||||
width: 130,
|
id: "spool_weight",
|
||||||
})}
|
i18ncat: "filament",
|
||||||
{NumberColumn({
|
unit: "g",
|
||||||
id: "settings_extruder_temp",
|
decimals: 1,
|
||||||
i18ncat: "filament",
|
actions,
|
||||||
unit: "°C",
|
dataSource,
|
||||||
decimals: 0,
|
tableState,
|
||||||
dataSource,
|
width: 100,
|
||||||
tableState,
|
}),
|
||||||
width: 100,
|
FilteredQueryColumn({
|
||||||
})}
|
id: "article_number",
|
||||||
{NumberColumn({
|
i18ncat: "filament",
|
||||||
id: "settings_bed_temp",
|
actions,
|
||||||
i18ncat: "filament",
|
dataSource,
|
||||||
unit: "°C",
|
tableState,
|
||||||
decimals: 0,
|
filterValueQuery: useSpoolmanArticleNumbers(),
|
||||||
dataSource,
|
width: 130,
|
||||||
tableState,
|
}),
|
||||||
width: 100,
|
NumberColumn({
|
||||||
})}
|
id: "settings_extruder_temp",
|
||||||
{DateColumn({
|
i18ncat: "filament",
|
||||||
id: "registered",
|
unit: "°C",
|
||||||
i18ncat: "filament",
|
decimals: 0,
|
||||||
dataSource,
|
actions,
|
||||||
tableState,
|
dataSource,
|
||||||
})}
|
tableState,
|
||||||
{RichColumn({
|
width: 100,
|
||||||
id: "comment",
|
}),
|
||||||
i18ncat: "filament",
|
NumberColumn({
|
||||||
dataSource,
|
id: "settings_bed_temp",
|
||||||
tableState,
|
i18ncat: "filament",
|
||||||
width: 150,
|
unit: "°C",
|
||||||
})}
|
decimals: 0,
|
||||||
<Table.Column
|
actions,
|
||||||
title={t("table.actions")}
|
dataSource,
|
||||||
render={(_, record: BaseRecord) => (
|
tableState,
|
||||||
<Space>
|
width: 100,
|
||||||
<EditButton hideText title={t("buttons.edit")} size="small" recordItemId={record.id} />
|
}),
|
||||||
<ShowButton hideText title={t("buttons.show")} size="small" recordItemId={record.id} />
|
DateColumn({
|
||||||
<CloneButton hideText title={t("buttons.clone")} size="small" recordItemId={record.id} />
|
id: "registered",
|
||||||
</Space>
|
i18ncat: "filament",
|
||||||
)}
|
actions,
|
||||||
/>
|
dataSource,
|
||||||
</Table>
|
tableState,
|
||||||
|
}),
|
||||||
|
RichColumn({
|
||||||
|
id: "comment",
|
||||||
|
i18ncat: "filament",
|
||||||
|
actions,
|
||||||
|
dataSource,
|
||||||
|
tableState,
|
||||||
|
width: 150,
|
||||||
|
}),
|
||||||
|
ActionsColumn(actions),
|
||||||
|
])}
|
||||||
|
/>
|
||||||
</List>
|
</List>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { IResourceComponentsProps, useInvalidate, useTranslate } from "@refinedev/core";
|
import { IResourceComponentsProps, useInvalidate, useNavigation, useTranslate } from "@refinedev/core";
|
||||||
import { useTable, List, EditButton, ShowButton, CloneButton } from "@refinedev/antd";
|
import { useTable, List } from "@refinedev/antd";
|
||||||
import { Table, Space, Button, Dropdown, Modal } from "antd";
|
import { Table, 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 { 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,
|
||||||
|
EyeOutlined,
|
||||||
|
FilterOutlined,
|
||||||
|
InboxOutlined,
|
||||||
|
PlusSquareOutlined,
|
||||||
|
ToTopOutlined,
|
||||||
|
} from "@ant-design/icons";
|
||||||
import {
|
import {
|
||||||
DateColumn,
|
DateColumn,
|
||||||
FilteredQueryColumn,
|
FilteredQueryColumn,
|
||||||
@@ -14,6 +21,8 @@ import {
|
|||||||
RichColumn,
|
RichColumn,
|
||||||
SortedColumn,
|
SortedColumn,
|
||||||
SpoolIconColumn,
|
SpoolIconColumn,
|
||||||
|
Action,
|
||||||
|
ActionsColumn,
|
||||||
} from "../../components/column";
|
} from "../../components/column";
|
||||||
import { setSpoolArchived } from "./functions";
|
import { setSpoolArchived } from "./functions";
|
||||||
import SelectAndPrint from "../../components/selectAndPrintDialog";
|
import SelectAndPrint from "../../components/selectAndPrintDialog";
|
||||||
@@ -24,6 +33,7 @@ import {
|
|||||||
useSpoolmanMaterials,
|
useSpoolmanMaterials,
|
||||||
} from "../../components/otherModels";
|
} from "../../components/otherModels";
|
||||||
import { useLiveify } from "../../components/liveify";
|
import { useLiveify } from "../../components/liveify";
|
||||||
|
import { removeUndefined } from "../../utils/filtering";
|
||||||
|
|
||||||
dayjs.extend(utc);
|
dayjs.extend(utc);
|
||||||
|
|
||||||
@@ -59,6 +69,25 @@ function translateColumnI18nKey(columnName: string): string {
|
|||||||
|
|
||||||
const namespace = "spoolList-v2";
|
const namespace = "spoolList-v2";
|
||||||
|
|
||||||
|
const allColumns: (keyof ISpoolCollapsed & string)[] = [
|
||||||
|
"id",
|
||||||
|
"combined_name",
|
||||||
|
"filament.material",
|
||||||
|
"used_weight",
|
||||||
|
"remaining_weight",
|
||||||
|
"used_length",
|
||||||
|
"remaining_length",
|
||||||
|
"location",
|
||||||
|
"lot_nr",
|
||||||
|
"first_used",
|
||||||
|
"last_used",
|
||||||
|
"registered",
|
||||||
|
"comment",
|
||||||
|
];
|
||||||
|
const defaultColumns = allColumns.filter(
|
||||||
|
(column_id) => ["registered", "used_length", "remaining_length", "lot_nr"].indexOf(column_id) === -1
|
||||||
|
);
|
||||||
|
|
||||||
export const SpoolList: React.FC<IResourceComponentsProps> = () => {
|
export const SpoolList: React.FC<IResourceComponentsProps> = () => {
|
||||||
const t = useTranslate();
|
const t = useTranslate();
|
||||||
const invalidate = useInvalidate();
|
const invalidate = useInvalidate();
|
||||||
@@ -73,57 +102,48 @@ export const SpoolList: React.FC<IResourceComponentsProps> = () => {
|
|||||||
// To provide the live updates, we use a custom solution (useLiveify) instead of the built-in refine "liveMode" feature.
|
// To provide the live updates, we use a custom solution (useLiveify) instead of the built-in refine "liveMode" feature.
|
||||||
// This is because the built-in feature does not call the liveProvider subscriber with a list of IDs, but instead
|
// This is because the built-in feature does not call the liveProvider subscriber with a list of IDs, but instead
|
||||||
// calls it with a list of filters, sorters, etc. This means the server-side has to support this, which is quite hard.
|
// calls it with a list of filters, sorters, etc. This means the server-side has to support this, which is quite hard.
|
||||||
const { tableProps, sorters, setSorters, filters, setFilters, current, pageSize, setCurrent } = useTable<ISpool>({
|
const { tableProps, sorters, setSorters, filters, setFilters, current, pageSize, setCurrent } =
|
||||||
meta: {
|
useTable<ISpoolCollapsed>({
|
||||||
queryParams: {
|
meta: {
|
||||||
["allow_archived"]: showArchived,
|
queryParams: {
|
||||||
|
["allow_archived"]: showArchived,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
syncWithLocation: false,
|
||||||
syncWithLocation: false,
|
pagination: {
|
||||||
pagination: {
|
mode: "server",
|
||||||
mode: "server",
|
current: initialState.pagination.current,
|
||||||
current: initialState.pagination.current,
|
pageSize: initialState.pagination.pageSize,
|
||||||
pageSize: initialState.pagination.pageSize,
|
},
|
||||||
},
|
sorters: {
|
||||||
sorters: {
|
mode: "server",
|
||||||
mode: "server",
|
initial: initialState.sorters,
|
||||||
initial: initialState.sorters,
|
},
|
||||||
},
|
filters: {
|
||||||
filters: {
|
mode: "server",
|
||||||
mode: "server",
|
initial: initialState.filters,
|
||||||
initial: initialState.filters,
|
},
|
||||||
},
|
liveMode: "manual",
|
||||||
liveMode: "manual",
|
onLiveEvent(event) {
|
||||||
onLiveEvent(event) {
|
if (event.type === "created" || event.type === "deleted") {
|
||||||
if (event.type === "created" || event.type === "deleted") {
|
// updated is handled by the liveify
|
||||||
// updated is handled by the liveify
|
invalidate({
|
||||||
invalidate({
|
resource: "spool",
|
||||||
resource: "spool",
|
invalidates: ["list"],
|
||||||
invalidates: ["list"],
|
});
|
||||||
});
|
}
|
||||||
}
|
},
|
||||||
},
|
queryOptions: {
|
||||||
});
|
select(data) {
|
||||||
|
return {
|
||||||
|
total: data.total,
|
||||||
|
data: data.data.map(collapseSpool),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// Create state for the columns to show
|
// Create state for the columns to show
|
||||||
const allColumns: (keyof ISpoolCollapsed & string)[] = [
|
|
||||||
"id",
|
|
||||||
"combined_name",
|
|
||||||
"filament.material",
|
|
||||||
"used_weight",
|
|
||||||
"remaining_weight",
|
|
||||||
"used_length",
|
|
||||||
"remaining_length",
|
|
||||||
"location",
|
|
||||||
"lot_nr",
|
|
||||||
"first_used",
|
|
||||||
"last_used",
|
|
||||||
"registered",
|
|
||||||
"comment",
|
|
||||||
];
|
|
||||||
const defaultColumns = allColumns.filter(
|
|
||||||
(column_id) => ["registered", "used_length", "remaining_length", "lot_nr"].indexOf(column_id) === -1
|
|
||||||
);
|
|
||||||
const [showColumns, setShowColumns] = React.useState<string[]>(initialState.showColumns ?? defaultColumns);
|
const [showColumns, setShowColumns] = React.useState<string[]>(initialState.showColumns ?? defaultColumns);
|
||||||
|
|
||||||
// Store state in local storage
|
// Store state in local storage
|
||||||
@@ -135,9 +155,9 @@ export const SpoolList: React.FC<IResourceComponentsProps> = () => {
|
|||||||
};
|
};
|
||||||
useStoreInitialState(namespace, tableState);
|
useStoreInitialState(namespace, tableState);
|
||||||
|
|
||||||
// Collapse the dataSource to a mutable list and add a filament_name field
|
// Collapse the dataSource to a mutable list
|
||||||
const queryDataSource = React.useMemo(
|
const queryDataSource: ISpoolCollapsed[] = React.useMemo(
|
||||||
() => (tableProps.dataSource ?? []).map(collapseSpool),
|
() => (tableProps.dataSource || []).map((record) => ({ ...record })),
|
||||||
[tableProps.dataSource]
|
[tableProps.dataSource]
|
||||||
);
|
);
|
||||||
const dataSource = useLiveify("spool", queryDataSource, collapseSpool);
|
const dataSource = useLiveify("spool", queryDataSource, collapseSpool);
|
||||||
@@ -174,6 +194,25 @@ export const SpoolList: React.FC<IResourceComponentsProps> = () => {
|
|||||||
tableProps.pagination.showSizeChanger = true;
|
tableProps.pagination.showSizeChanger = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { editUrl, showUrl, cloneUrl } = useNavigation();
|
||||||
|
const actions = (record: ISpoolCollapsed) => {
|
||||||
|
const actions: Action[] = [
|
||||||
|
{ name: t("buttons.edit"), icon: <EditOutlined />, link: editUrl("spool", record.id) },
|
||||||
|
{ name: t("buttons.show"), icon: <EyeOutlined />, link: showUrl("spool", record.id) },
|
||||||
|
{ name: t("buttons.clone"), icon: <PlusSquareOutlined />, link: cloneUrl("spool", record.id) },
|
||||||
|
];
|
||||||
|
if (record.archived) {
|
||||||
|
actions.push({
|
||||||
|
name: t("buttons.unArchive"),
|
||||||
|
icon: <ToTopOutlined />,
|
||||||
|
onClick: () => archiveSpool(record, false),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
actions.push({ name: t("buttons.archive"), icon: <InboxOutlined />, onClick: () => archiveSpoolPopup(record) });
|
||||||
|
}
|
||||||
|
return actions;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<List
|
<List
|
||||||
headerButtons={({ defaultButtons }) => (
|
headerButtons={({ defaultButtons }) => (
|
||||||
@@ -244,136 +283,126 @@ export const SpoolList: React.FC<IResourceComponentsProps> = () => {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
columns={removeUndefined([
|
||||||
{SortedColumn({
|
SortedColumn({
|
||||||
id: "id",
|
id: "id",
|
||||||
i18ncat: "spool",
|
i18ncat: "spool",
|
||||||
dataSource,
|
actions,
|
||||||
tableState,
|
dataSource,
|
||||||
width: 70,
|
tableState,
|
||||||
})}
|
width: 70,
|
||||||
{SpoolIconColumn({
|
}),
|
||||||
id: "combined_name",
|
SpoolIconColumn({
|
||||||
i18nkey: "spool.fields.filament_name",
|
id: "combined_name",
|
||||||
color: (record: ISpoolCollapsed) => record.filament.color_hex,
|
i18nkey: "spool.fields.filament_name",
|
||||||
dataSource,
|
color: (record: ISpoolCollapsed) => record.filament.color_hex,
|
||||||
tableState,
|
actions,
|
||||||
dataId: "filament.id",
|
dataSource,
|
||||||
filterValueQuery: useSpoolmanFilamentFilter(),
|
tableState,
|
||||||
})}
|
dataId: "filament.id",
|
||||||
{FilteredQueryColumn({
|
filterValueQuery: useSpoolmanFilamentFilter(),
|
||||||
id: "filament.material",
|
}),
|
||||||
i18nkey: "spool.fields.material",
|
FilteredQueryColumn({
|
||||||
dataSource,
|
id: "filament.material",
|
||||||
tableState,
|
i18nkey: "spool.fields.material",
|
||||||
filterValueQuery: useSpoolmanMaterials(),
|
actions,
|
||||||
width: 120,
|
dataSource,
|
||||||
})}
|
tableState,
|
||||||
{NumberColumn({
|
filterValueQuery: useSpoolmanMaterials(),
|
||||||
id: "used_weight",
|
width: 120,
|
||||||
i18ncat: "spool",
|
}),
|
||||||
unit: "g",
|
NumberColumn({
|
||||||
decimals: 1,
|
id: "used_weight",
|
||||||
dataSource,
|
i18ncat: "spool",
|
||||||
tableState,
|
unit: "g",
|
||||||
width: 110,
|
decimals: 1,
|
||||||
})}
|
actions,
|
||||||
{NumberColumn({
|
dataSource,
|
||||||
id: "remaining_weight",
|
tableState,
|
||||||
i18ncat: "spool",
|
width: 110,
|
||||||
unit: "g",
|
}),
|
||||||
decimals: 1,
|
NumberColumn({
|
||||||
defaultText: t("unknown"),
|
id: "remaining_weight",
|
||||||
dataSource,
|
i18ncat: "spool",
|
||||||
tableState,
|
unit: "g",
|
||||||
width: 110,
|
decimals: 1,
|
||||||
})}
|
defaultText: t("unknown"),
|
||||||
{NumberColumn({
|
actions,
|
||||||
id: "used_length",
|
dataSource,
|
||||||
i18ncat: "spool",
|
tableState,
|
||||||
unit: "mm",
|
width: 110,
|
||||||
decimals: 1,
|
}),
|
||||||
dataSource,
|
NumberColumn({
|
||||||
tableState,
|
id: "used_length",
|
||||||
width: 120,
|
i18ncat: "spool",
|
||||||
})}
|
unit: "mm",
|
||||||
{NumberColumn({
|
decimals: 1,
|
||||||
id: "remaining_length",
|
actions,
|
||||||
i18ncat: "spool",
|
dataSource,
|
||||||
unit: "mm",
|
tableState,
|
||||||
decimals: 1,
|
width: 120,
|
||||||
defaultText: t("unknown"),
|
}),
|
||||||
dataSource,
|
NumberColumn({
|
||||||
tableState,
|
id: "remaining_length",
|
||||||
width: 120,
|
i18ncat: "spool",
|
||||||
})}
|
unit: "mm",
|
||||||
{FilteredQueryColumn({
|
decimals: 1,
|
||||||
id: "location",
|
defaultText: t("unknown"),
|
||||||
i18ncat: "spool",
|
actions,
|
||||||
dataSource,
|
dataSource,
|
||||||
tableState,
|
tableState,
|
||||||
filterValueQuery: useSpoolmanLocations(),
|
width: 120,
|
||||||
width: 120,
|
}),
|
||||||
})}
|
FilteredQueryColumn({
|
||||||
{FilteredQueryColumn({
|
id: "location",
|
||||||
id: "lot_nr",
|
i18ncat: "spool",
|
||||||
i18ncat: "spool",
|
actions,
|
||||||
dataSource,
|
dataSource,
|
||||||
tableState,
|
tableState,
|
||||||
filterValueQuery: useSpoolmanLotNumbers(),
|
filterValueQuery: useSpoolmanLocations(),
|
||||||
width: 120,
|
width: 120,
|
||||||
})}
|
}),
|
||||||
{DateColumn({
|
FilteredQueryColumn({
|
||||||
id: "first_used",
|
id: "lot_nr",
|
||||||
i18ncat: "spool",
|
i18ncat: "spool",
|
||||||
dataSource,
|
actions,
|
||||||
tableState,
|
dataSource,
|
||||||
})}
|
tableState,
|
||||||
{DateColumn({
|
filterValueQuery: useSpoolmanLotNumbers(),
|
||||||
id: "last_used",
|
width: 120,
|
||||||
i18ncat: "spool",
|
}),
|
||||||
dataSource,
|
DateColumn({
|
||||||
tableState,
|
id: "first_used",
|
||||||
})}
|
i18ncat: "spool",
|
||||||
{DateColumn({
|
actions,
|
||||||
id: "registered",
|
dataSource,
|
||||||
i18ncat: "spool",
|
tableState,
|
||||||
dataSource,
|
}),
|
||||||
tableState,
|
DateColumn({
|
||||||
})}
|
id: "last_used",
|
||||||
{RichColumn({
|
i18ncat: "spool",
|
||||||
id: "comment",
|
actions,
|
||||||
i18ncat: "spool",
|
dataSource,
|
||||||
dataSource,
|
tableState,
|
||||||
tableState,
|
}),
|
||||||
width: 150,
|
DateColumn({
|
||||||
})}
|
id: "registered",
|
||||||
<Table.Column
|
i18ncat: "spool",
|
||||||
title={t("table.actions")}
|
actions,
|
||||||
render={(_, record: ISpoolCollapsed) => (
|
dataSource,
|
||||||
<Space>
|
tableState,
|
||||||
<EditButton hideText title={t("buttons.edit")} size="small" recordItemId={record.id} />
|
}),
|
||||||
<ShowButton hideText title={t("buttons.show")} size="small" recordItemId={record.id} />
|
RichColumn({
|
||||||
<CloneButton hideText title={t("buttons.clone")} size="small" recordItemId={record.id} />
|
id: "comment",
|
||||||
{record.archived ? (
|
i18ncat: "spool",
|
||||||
<Button
|
actions,
|
||||||
icon={<ToTopOutlined />}
|
dataSource,
|
||||||
title={t("buttons.unArchive")}
|
tableState,
|
||||||
size="small"
|
width: 150,
|
||||||
onClick={() => archiveSpool(record, false)}
|
}),
|
||||||
/>
|
ActionsColumn(actions),
|
||||||
) : (
|
])}
|
||||||
<Button
|
/>
|
||||||
icon={<InboxOutlined />}
|
|
||||||
title={t("buttons.archive")}
|
|
||||||
size="small"
|
|
||||||
onClick={() => archiveSpoolPopup(record)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Space>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</Table>
|
|
||||||
</List>
|
</List>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
97
client/src/pages/vendors/list.tsx
vendored
97
client/src/pages/vendors/list.tsx
vendored
@@ -1,19 +1,22 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { IResourceComponentsProps, BaseRecord, useTranslate, useInvalidate } from "@refinedev/core";
|
import { IResourceComponentsProps, useTranslate, useInvalidate, useNavigation } from "@refinedev/core";
|
||||||
import { useTable, List, EditButton, ShowButton, CloneButton } from "@refinedev/antd";
|
import { useTable, List } from "@refinedev/antd";
|
||||||
import { Table, Space, Button, Dropdown } from "antd";
|
import { Table, 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 { 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, EyeOutlined, FilterOutlined, PlusSquareOutlined } from "@ant-design/icons";
|
||||||
import { DateColumn, RichColumn, SortedColumn } from "../../components/column";
|
import { DateColumn, RichColumn, SortedColumn, ActionsColumn } from "../../components/column";
|
||||||
import { useLiveify } from "../../components/liveify";
|
import { useLiveify } from "../../components/liveify";
|
||||||
|
import { removeUndefined } from "../../utils/filtering";
|
||||||
|
|
||||||
dayjs.extend(utc);
|
dayjs.extend(utc);
|
||||||
|
|
||||||
const namespace = "vendorList-v2";
|
const namespace = "vendorList-v2";
|
||||||
|
|
||||||
|
const allColumns: (keyof IVendor & string)[] = ["id", "name", "registered", "comment"];
|
||||||
|
|
||||||
export const VendorList: React.FC<IResourceComponentsProps> = () => {
|
export const VendorList: React.FC<IResourceComponentsProps> = () => {
|
||||||
const t = useTranslate();
|
const t = useTranslate();
|
||||||
const invalidate = useInvalidate();
|
const invalidate = useInvalidate();
|
||||||
@@ -50,7 +53,6 @@ export const VendorList: React.FC<IResourceComponentsProps> = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Create state for the columns to show
|
// Create state for the columns to show
|
||||||
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);
|
||||||
|
|
||||||
// Store state in local storage
|
// Store state in local storage
|
||||||
@@ -73,6 +75,13 @@ export const VendorList: React.FC<IResourceComponentsProps> = () => {
|
|||||||
tableProps.pagination.showSizeChanger = true;
|
tableProps.pagination.showSizeChanger = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { editUrl, showUrl, cloneUrl } = useNavigation();
|
||||||
|
const actions = (record: IVendor) => [
|
||||||
|
{ name: t("buttons.edit"), icon: <EditOutlined />, link: editUrl("vendor", record.id) },
|
||||||
|
{ name: t("buttons.show"), icon: <EyeOutlined />, link: showUrl("vendor", record.id) },
|
||||||
|
{ name: t("buttons.clone"), icon: <PlusSquareOutlined />, link: cloneUrl("vendor", record.id) },
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<List
|
<List
|
||||||
headerButtons={({ defaultButtons }) => (
|
headerButtons={({ defaultButtons }) => (
|
||||||
@@ -114,43 +123,45 @@ export const VendorList: React.FC<IResourceComponentsProps> = () => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Table {...tableProps} sticky scroll={{ x: "max-content" }} dataSource={dataSource} rowKey="id">
|
<Table
|
||||||
{SortedColumn({
|
{...tableProps}
|
||||||
id: "id",
|
sticky
|
||||||
i18ncat: "vendor",
|
scroll={{ x: "max-content" }}
|
||||||
dataSource,
|
dataSource={dataSource}
|
||||||
tableState,
|
rowKey="id"
|
||||||
width: 70,
|
columns={removeUndefined([
|
||||||
})}
|
SortedColumn({
|
||||||
{SortedColumn({
|
id: "id",
|
||||||
id: "name",
|
i18ncat: "vendor",
|
||||||
i18ncat: "vendor",
|
actions,
|
||||||
dataSource,
|
dataSource,
|
||||||
tableState,
|
tableState,
|
||||||
})}
|
width: 70,
|
||||||
{DateColumn({
|
}),
|
||||||
id: "registered",
|
SortedColumn({
|
||||||
i18ncat: "vendor",
|
id: "name",
|
||||||
dataSource,
|
i18ncat: "vendor",
|
||||||
tableState,
|
actions,
|
||||||
})}
|
dataSource,
|
||||||
{RichColumn({
|
tableState,
|
||||||
id: "comment",
|
}),
|
||||||
i18ncat: "vendor",
|
DateColumn({
|
||||||
dataSource,
|
id: "registered",
|
||||||
tableState,
|
i18ncat: "vendor",
|
||||||
})}
|
actions,
|
||||||
<Table.Column
|
dataSource,
|
||||||
title={t("table.actions")}
|
tableState,
|
||||||
render={(_, record: BaseRecord) => (
|
}),
|
||||||
<Space>
|
RichColumn({
|
||||||
<EditButton hideText title={t("buttons.edit")} size="small" recordItemId={record.id} />
|
id: "comment",
|
||||||
<ShowButton hideText title={t("buttons.show")} size="small" recordItemId={record.id} />
|
i18ncat: "vendor",
|
||||||
<CloneButton hideText title={t("buttons.clone")} size="small" recordItemId={record.id} />
|
actions,
|
||||||
</Space>
|
dataSource,
|
||||||
)}
|
tableState,
|
||||||
/>
|
}),
|
||||||
</Table>
|
ActionsColumn<IVendor>(actions),
|
||||||
|
])}
|
||||||
|
/>
|
||||||
</List>
|
</List>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,3 +28,10 @@ export function getFiltersForField<Obj, Field extends keyof Obj>(
|
|||||||
});
|
});
|
||||||
return filterValues;
|
return filterValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function that returns an array with all undefined values removed.
|
||||||
|
*/
|
||||||
|
export function removeUndefined<T>(array: (T | undefined)[]): T[] {
|
||||||
|
return array.filter((value) => value !== undefined) as T[];
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user