Files
spoolman2/client/src/components/column.tsx
Donkie f62f9d899b Client: Now uses server side sorting
Still some stuff left to do #98
2023-09-16 11:13:30 +02:00

212 lines
5.6 KiB
TypeScript

import { Col, Row, Table } from "antd";
import { ColumnProps as AntdColumnProps } from "antd/es/table";
import { ColumnFilterItem } from "antd/es/table/interface";
import { getFiltersForField, typeFilters } from "../utils/filtering";
import { TableState } from "../utils/saveload";
import { getSortOrderForField, typeSorters } from "../utils/sorting";
import { NumberFieldUnit } from "./numberField";
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import { DateField, TextField } from "@refinedev/antd";
import Icon from "@ant-design/icons";
import { ReactComponent as SpoolIcon } from "../icon_spool.svg";
import { useTranslate } from "@refinedev/core";
import { enrichText } from "../utils/parsing";
import { UseQueryResult } from "@tanstack/react-query";
dayjs.extend(utc);
interface BaseColumnProps<Obj> {
id: keyof Obj & string;
i18ncat?: string;
i18nkey?: string;
dataSource: Obj[];
tableState: TableState;
}
interface FilteredColumnProps {
filters?: ColumnFilterItem[];
filteredValue?: string[];
allowMultipleFilters?: boolean;
}
interface CustomColumnProps<Obj> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
render?: (value: any, record: Obj, index: number) => React.ReactNode;
onCell?: (
data: Obj,
index?: number
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) => React.HTMLAttributes<any> | React.TdHTMLAttributes<any>;
}
function Column<Obj>(props: BaseColumnProps<Obj> & FilteredColumnProps & CustomColumnProps<Obj>) {
const t = useTranslate();
if (props.tableState.showColumns && !props.tableState.showColumns.includes(props.id)) {
return <></>;
}
const typedSorters = typeSorters<Obj>(props.tableState.sorters);
const columnProps: AntdColumnProps<Obj> = {
dataIndex: props.id,
title: t(props.i18nkey ?? `${props.i18ncat}.fields.${props.id}`),
sorter: true,
sortOrder: getSortOrderForField(typedSorters, props.id),
filterMultiple: props.allowMultipleFilters ?? true,
};
if (props.filters && props.filteredValue) {
columnProps.filters = props.filters;
columnProps.filteredValue = props.filteredValue;
}
if (props.render) {
columnProps.render = props.render;
}
if (props.onCell) {
columnProps.onCell = props.onCell;
}
return <Table.Column<Obj> {...columnProps} />;
}
export function SortedColumn<Obj>(props: BaseColumnProps<Obj>) {
return Column(props);
}
export function RichColumn<Obj>(props: BaseColumnProps<Obj>) {
return Column({
...props,
render: (value: string | undefined) => {
return enrichText(value);
},
});
}
interface FilteredQueryColumnProps<Obj> extends BaseColumnProps<Obj> {
filterValueQuery: UseQueryResult<string[]>;
allowMultipleFilters?: boolean;
}
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);
return Column({ ...props, filters, filteredValue });
}
interface NumberColumnProps<Obj> extends BaseColumnProps<Obj> {
unit: string;
decimals?: number;
defaultText?: string;
}
export function NumberColumn<Obj>(props: NumberColumnProps<Obj>) {
return Column({
...props,
render: (value) => {
if (value === null || value === undefined) {
return <TextField value={props.defaultText ?? ""} />;
}
return (
<NumberFieldUnit
value={value}
unit={props.unit}
options={{
maximumFractionDigits: props.decimals ?? 0,
minimumFractionDigits: props.decimals ?? 0,
}}
/>
);
},
});
}
export function DateColumn<Obj>(props: BaseColumnProps<Obj>) {
return Column({
...props,
render: (value) => {
return (
<DateField
hidden={!value}
value={dayjs.utc(value).local()}
title={dayjs.utc(value).local().format()}
format="YYYY-MM-DD HH:mm:ss"
/>
);
},
});
}
interface SpoolIconColumnProps<Obj> extends FilteredQueryColumnProps<Obj> {
color: (record: Obj) => string | undefined;
}
export function SpoolIconColumn<Obj>(props: SpoolIconColumnProps<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);
return Column({
...props,
filters,
filteredValue,
onCell: () => {
return {
style: {
paddingLeft: 0,
paddingTop: 0,
paddingBottom: 0,
},
};
},
render: (value, record: Obj) => {
const colorStr = props.color(record);
return (
<Row wrap={false} justify="space-around" align="middle">
{colorStr && (
<Col flex="none">
<Icon
component={SpoolIcon}
style={{
color: "#" + colorStr,
fontSize: 42,
marginRight: 0,
}}
/>
</Col>
)}
<Col flex="auto">{value}</Col>
</Row>
);
},
});
}