Client: Added show/hide columns feature

This commit is contained in:
Donkie
2023-07-13 19:12:38 +02:00
parent 257c8b55ac
commit e5ef306aa2
6 changed files with 559 additions and 392 deletions

View File

@@ -109,35 +109,68 @@
"dashboard": { "dashboard": {
"title": "Dashboard" "title": "Dashboard"
}, },
"blog_posts": { "spools": {
"blog_posts": "Blog Posts", "spools": "Spools",
"fields": { "fields": {
"id": "Id", "id": "Id",
"title": "Title", "filament_name": "Filament Name",
"content": "Content", "used_weight": "Used Weight",
"status": "Status", "remaining_weight": "Remaining Weight",
"category": "Category", "location": "Location",
"createdAt": "Created At" "lot_nr": "Lot Nr",
"first_used": "First Used",
"last_used": "Last Used",
"comment": "Comment"
}, },
"titles": { "titles": {
"create": "Create Blog Post", "create": "Create Spool",
"edit": "Edit Blog Post", "clone": "Clone Spool",
"list": "Blog Posts", "edit": "Edit Spool",
"show": "Show Blog Post" "list": "Spools",
"show": "Show Spool"
} }
}, },
"categories": { "filaments": {
"categories": "Categories", "filaments": "Filaments",
"fields": { "fields": {
"id": "Id", "id": "Id",
"title": "Title", "vendor_name": "Vendor Name",
"createdAt": "Created At" "name": "Name",
"material": "Material",
"price": "Price",
"density": "Density",
"diameter": "Diameter",
"weight": "Weight",
"spool_weight": "Spool Weight",
"article_number": "Article Number",
"registered": "Registered",
"comment": "Comment",
"settings_extruder_temp": "Extruder Temp",
"settings_bed_temp": "Bed Temp",
"color_hex": "Color"
}, },
"titles": { "titles": {
"create": "Create Category", "create": "Create Filament",
"edit": "Edit Category", "clone": "Clone Filament",
"list": "Categories", "edit": "Edit Filament",
"show": "Show Category" "list": "Filaments",
"show": "Show Filament"
}
},
"vendors": {
"vendors": "Vendors",
"fields": {
"id": "Id",
"name": "Name",
"registered": "Registered",
"comment": "Comment"
},
"titles": {
"create": "Create Vendor",
"clone": "Clone Vendor",
"edit": "Edit Vendor",
"list": "Vendors",
"show": "Show Vendor"
} }
}, },
"table": { "table": {

View File

@@ -0,0 +1,170 @@
import { Col, Row, Table } from "antd";
import { ColumnProps as AntdColumnProps } from "antd/es/table";
import { ColumnFilterItem } from "antd/es/table/interface";
import {
getFiltersForField,
typeFilters,
useListFiltersForField,
} 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";
dayjs.extend(utc);
interface BaseColumnProps<Obj> {
id: keyof Obj & string;
title: string;
dataSource: Obj[];
tableState: TableState;
}
interface FilteredColumnProps {
filters?: ColumnFilterItem[];
filteredValue?: string[];
}
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>
) {
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: props.title,
sorter: true,
sortOrder: getSortOrderForField(typedSorters, props.id),
};
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 <Table.Column dataIndex={["comment"]} title="Comment" />;
return Column(props);
}
export function FilteredColumn<Obj>(props: BaseColumnProps<Obj>) {
const typedFilters = typeFilters<Obj>(props.tableState.filters);
const filters = useListFiltersForField(props.dataSource, props.id);
const filteredValue = getFiltersForField(typedFilters, props.id);
return Column({ ...props, filters, filteredValue });
}
interface NumberColumnProps<Obj> extends BaseColumnProps<Obj> {
unit: string;
decimals?: number;
}
export function NumberColumn<Obj>(props: NumberColumnProps<Obj>) {
return Column({
...props,
render: (value) => {
if (value === null || value === undefined) {
return <TextField value="Unknown" />;
}
return (
<NumberFieldUnit
value={value}
unit={props.unit}
options={{
maximumFractionDigits: 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 BaseColumnProps<Obj> {
color: (record: Obj) => string | undefined;
}
export function SpoolIconColumn<Obj>(props: SpoolIconColumnProps<Obj>) {
const typedFilters = typeFilters<Obj>(props.tableState.filters);
const filters = useListFiltersForField(props.dataSource, props.id);
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>
);
},
});
}

View File

@@ -5,35 +5,32 @@ import {
List, List,
EditButton, EditButton,
ShowButton, ShowButton,
DateField,
CloneButton, CloneButton,
} from "@refinedev/antd"; } from "@refinedev/antd";
import { Table, Space, Button, Row, Col } from "antd"; import { Table, Space, Button, Dropdown } from "antd";
import { NumberFieldUnit } from "../../components/numberField";
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 { import {
genericSorter, TableState,
getSortOrderForField,
typeSorters,
} from "../../utils/sorting";
import {
genericFilterer,
getFiltersForField,
typeFilters,
useListFiltersForField,
} from "../../utils/filtering";
import Icon, { FilterOutlined } from "@ant-design/icons";
import {
useInitialTableState, useInitialTableState,
useStoreInitialState, useStoreInitialState,
} from "../../utils/saveload"; } from "../../utils/saveload";
import { ReactComponent as SpoolIcon } from "../../icon_spool.svg"; import {
DateColumn,
FilteredColumn,
NumberColumn,
SortedColumn,
SpoolIconColumn,
} from "../../components/column";
import i18n from "../../i18n";
dayjs.extend(utc); dayjs.extend(utc);
interface IFilamentCollapsed extends IFilament { interface IFilamentCollapsed extends Omit<IFilament, "vendor"> {
vendor_name: string | null; vendor_name: string | null;
} }
@@ -69,16 +66,37 @@ export const FilamentList: React.FC<IResourceComponentsProps> = () => {
}, },
}); });
// 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",
"registered",
"comment",
];
const [showColumns, setShowColumns] = React.useState<string[]>(
initialState.showColumns ?? allColumns
);
// Type the sorters and filters // Type the sorters and filters
const typedSorters = typeSorters<IFilamentCollapsed>(sorters); const typedSorters = typeSorters<IFilamentCollapsed>(sorters);
const typedFilters = typeFilters<IFilamentCollapsed>(filters); const typedFilters = typeFilters<IFilamentCollapsed>(filters);
// Store state in local storage // Store state in local storage
useStoreInitialState("filamentList", { const tableState: TableState = {
sorters, sorters,
filters, filters,
pagination: { current, pageSize }, pagination: { current, pageSize },
}); showColumns,
};
useStoreInitialState("filamentList", 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(
@@ -117,6 +135,28 @@ export const FilamentList: React.FC<IResourceComponentsProps> = () => {
> >
Clear Filters Clear Filters
</Button> </Button>
<Dropdown
trigger={["click"]}
menu={{
items: allColumns.map((column_id) => ({
key: column_id,
label: i18n.t(`filaments.fields.${column_id}`),
})),
selectedKeys: showColumns,
selectable: true,
multiple: true,
onDeselect: (keys) => {
setShowColumns(keys.selectedKeys);
},
onSelect: (keys) => {
setShowColumns(keys.selectedKeys);
},
}}
>
<Button type="primary" icon={<EditOutlined />}>
Hide Columns
</Button>
</Dropdown>
{defaultButtons} {defaultButtons}
</> </>
)} )}
@@ -135,168 +175,87 @@ export const FilamentList: React.FC<IResourceComponentsProps> = () => {
}} }}
rowKey="id" rowKey="id"
> >
<Table.Column {SortedColumn({
dataIndex="id" id: "id",
title="Id" title: "Id",
sorter={true} dataSource,
sortOrder={getSortOrderForField(typedSorters, "id")} tableState,
/> })}
<Table.Column {FilteredColumn({
dataIndex="vendor_name" id: "vendor_name",
title="Vendor" title: "Vendor",
sorter={true} dataSource,
sortOrder={getSortOrderForField(typedSorters, "vendor_name")} tableState,
filters={useListFiltersForField(dataSource, "vendor_name")} })}
filteredValue={getFiltersForField(typedFilters, "vendor_name")} {SpoolIconColumn({
/> id: "name",
<Table.Column title: "Name",
dataIndex="name" color: (record: IFilamentCollapsed) => record.color_hex,
title="Name" dataSource,
width={150} tableState,
sorter={true} })}
sortOrder={getSortOrderForField(typedSorters, "name")} {FilteredColumn({
filters={useListFiltersForField(dataSource, "name")} id: "material",
filteredValue={getFiltersForField(typedFilters, "name")} title: "Material",
render={(value, record: IFilament) => { dataSource,
return { tableState,
props: { })}
style: { {SortedColumn({
paddingLeft: 0, id: "price",
paddingTop: 0, title: "Price",
paddingBottom: 0, dataSource,
}, tableState,
}, })}
children: ( {NumberColumn({
<Row wrap={false} justify="space-around" align="middle"> id: "density",
{record.color_hex && ( title: "Density",
<Col flex="none"> unit: "g/cm³",
<Icon decimals: 2,
component={SpoolIcon} dataSource,
style={{ tableState,
color: "#" + record.color_hex, })}
fontSize: 42, {NumberColumn({
marginRight: 0, id: "diameter",
}} title: "Diameter",
/> unit: "mm",
</Col> decimals: 2,
)} dataSource,
<Col flex="auto">{value}</Col> tableState,
</Row> })}
), {NumberColumn({
}; id: "weight",
}} title: "Weight",
/> unit: "g",
<Table.Column decimals: 1,
dataIndex="material" dataSource,
title="Material" tableState,
sorter={true} })}
sortOrder={getSortOrderForField(typedSorters, "material")} {NumberColumn({
filters={useListFiltersForField(dataSource, "material")} id: "spool_weight",
filteredValue={getFiltersForField(typedFilters, "material")} title: "Spool Weight",
/> unit: "g",
<Table.Column decimals: 1,
dataIndex="price" dataSource,
title="Price" tableState,
sorter={true} })}
sortOrder={getSortOrderForField(typedSorters, "price")} {FilteredColumn({
/> id: "article_number",
<Table.Column title: "Article Number",
dataIndex="density" dataSource,
title="Density" tableState,
sorter={true} })}
sortOrder={getSortOrderForField(typedSorters, "density")} {DateColumn({
render={(value) => ( id: "registered",
<NumberFieldUnit title: "Registered",
value={value} dataSource,
unit="g/cm³" tableState,
options={{ })}
maximumFractionDigits: 2, {SortedColumn({
}} id: "comment",
/> title: "Comment",
)} dataSource,
/> tableState,
<Table.Column })}
dataIndex="diameter"
title="Diameter"
sorter={true}
sortOrder={getSortOrderForField(typedSorters, "diameter")}
render={(value) => (
<NumberFieldUnit
value={value}
unit="mm"
options={{
maximumFractionDigits: 2,
}}
/>
)}
/>
<Table.Column
dataIndex="weight"
title="Weight"
sorter={true}
sortOrder={getSortOrderForField(typedSorters, "weight")}
render={(value) => {
if (value === null || value === undefined) {
return <></>;
}
return (
<NumberFieldUnit
value={value}
unit="g"
options={{
maximumFractionDigits: 1,
}}
/>
);
}}
/>
<Table.Column
dataIndex="spool_weight"
title="Spool Weight"
sorter={true}
sortOrder={getSortOrderForField(typedSorters, "spool_weight")}
render={(value) => {
if (value === null || value === undefined) {
return <></>;
}
return (
<NumberFieldUnit
value={value}
unit="g"
options={{
maximumFractionDigits: 1,
}}
/>
);
}}
/>
<Table.Column
dataIndex="article_number"
title="Article Number"
sorter={true}
sortOrder={getSortOrderForField(typedSorters, "article_number")}
filters={useListFiltersForField(dataSource, "article_number")}
filteredValue={getFiltersForField(typedFilters, "article_number")}
/>
<Table.Column
dataIndex={["registered"]}
title="Registered"
sorter={true}
sortOrder={getSortOrderForField(typedSorters, "registered")}
render={(value) => (
<DateField
value={dayjs.utc(value).local()}
title={dayjs.utc(value).local().format()}
format="YYYY-MM-DD HH:mm:ss"
/>
)}
/>
<Table.Column
dataIndex={["comment"]}
title="Comment"
sorter={true}
sortOrder={getSortOrderForField(typedSorters, "comment")}
/>
<Table.Column <Table.Column
title="Actions" title="Actions"
dataIndex="actions" dataIndex="actions"

View File

@@ -5,32 +5,28 @@ import {
List, List,
EditButton, EditButton,
ShowButton, ShowButton,
DateField,
TextField,
CloneButton, CloneButton,
} from "@refinedev/antd"; } from "@refinedev/antd";
import { Table, Space, Button, Row, Col } from "antd"; import { Table, Space, Button, Dropdown } from "antd";
import { NumberFieldUnit } from "../../components/numberField";
import dayjs from "dayjs"; import dayjs from "dayjs";
import utc from "dayjs/plugin/utc"; import utc from "dayjs/plugin/utc";
import { import { genericSorter, typeSorters } from "../../utils/sorting";
genericSorter, import { genericFilterer, typeFilters } from "../../utils/filtering";
getSortOrderForField,
typeSorters,
} from "../../utils/sorting";
import {
genericFilterer,
getFiltersForField,
typeFilters,
useListFiltersForField,
} from "../../utils/filtering";
import { ISpool } from "./model"; import { ISpool } from "./model";
import { import {
TableState,
useInitialTableState, useInitialTableState,
useStoreInitialState, useStoreInitialState,
} from "../../utils/saveload"; } from "../../utils/saveload";
import Icon, { FilterOutlined } from "@ant-design/icons"; import { EditOutlined, FilterOutlined } from "@ant-design/icons";
import { ReactComponent as SpoolIcon } from "../../icon_spool.svg"; import {
DateColumn,
FilteredColumn,
NumberColumn,
SortedColumn,
SpoolIconColumn,
} from "../../components/column";
import i18n from "../../i18n";
dayjs.extend(utc); dayjs.extend(utc);
@@ -70,16 +66,34 @@ export const SpoolList: React.FC<IResourceComponentsProps> = () => {
}, },
}); });
// Create state for the columns to show
const allColumns: (keyof ISpoolCollapsed & string)[] = [
"id",
"filament_name",
"used_weight",
"remaining_weight",
"location",
"lot_nr",
"first_used",
"last_used",
"comment",
];
const [showColumns, setShowColumns] = React.useState<string[]>(
initialState.showColumns ?? allColumns
);
// Type the sorters and filters // Type the sorters and filters
const typedSorters = typeSorters<ISpoolCollapsed>(sorters); const typedSorters = typeSorters<ISpoolCollapsed>(sorters);
const typedFilters = typeFilters<ISpoolCollapsed>(filters); const typedFilters = typeFilters<ISpoolCollapsed>(filters);
// Store state in local storage // Store state in local storage
useStoreInitialState("spoolList", { const tableState: TableState = {
sorters, sorters,
filters, filters,
pagination: { current, pageSize }, pagination: { current, pageSize },
}); showColumns,
};
useStoreInitialState("spoolList", 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(
@@ -119,6 +133,28 @@ export const SpoolList: React.FC<IResourceComponentsProps> = () => {
> >
Clear Filters Clear Filters
</Button> </Button>
<Dropdown
trigger={["click"]}
menu={{
items: allColumns.map((column) => ({
key: column,
label: i18n.t(`spools.fields.${column}`),
})),
selectedKeys: showColumns,
selectable: true,
multiple: true,
onDeselect: (keys) => {
setShowColumns(keys.selectedKeys);
},
onSelect: (keys) => {
setShowColumns(keys.selectedKeys);
},
}}
>
<Button type="primary" icon={<EditOutlined />}>
Hide Columns
</Button>
</Dropdown>
{defaultButtons} {defaultButtons}
</> </>
)} )}
@@ -137,138 +173,67 @@ export const SpoolList: React.FC<IResourceComponentsProps> = () => {
}} }}
rowKey="id" rowKey="id"
> >
<Table.Column {SortedColumn({
dataIndex="id" id: "id",
title="Id" title: "Id",
sorter={true} dataSource,
sortOrder={getSortOrderForField(typedSorters, "id")} tableState,
/> })}
<Table.Column {SpoolIconColumn({
dataIndex="filament_name" id: "filament_name",
title="Filament" title: "Filament",
sorter={true} color: (record: ISpoolCollapsed) => record.filament.color_hex,
sortOrder={getSortOrderForField(typedSorters, "filament_name")} dataSource,
filters={useListFiltersForField(dataSource, "filament_name")} tableState,
filteredValue={getFiltersForField(typedFilters, "filament_name")} })}
render={(value, record: ISpool) => { {NumberColumn({
return { id: "used_weight",
props: { title: "Used Weight",
style: { unit: "g",
paddingLeft: 0, decimals: 1,
paddingTop: 0, dataSource,
paddingBottom: 0, tableState,
}, })}
}, {NumberColumn({
children: ( id: "remaining_weight",
<Row wrap={false} justify="space-around" align="middle"> title: "Estimated Remaining Weight",
{record.filament.color_hex && ( unit: "g",
<Col flex="none"> decimals: 1,
<Icon dataSource,
component={SpoolIcon} tableState,
style={{ })}
color: "#" + record.filament.color_hex, {FilteredColumn({
fontSize: 42, id: "location",
marginRight: 0, title: "Location",
}} dataSource,
/> tableState,
</Col> })}
)} {FilteredColumn({
<Col flex="auto">{value}</Col> id: "lot_nr",
</Row> title: "Lot Nr",
), dataSource,
}; tableState,
}} })}
/> {DateColumn({
<Table.Column id: "first_used",
dataIndex="used_weight" title: "First Used",
title="Used Weight" dataSource,
sorter={true} tableState,
sortOrder={getSortOrderForField(typedSorters, "used_weight")} })}
render={(value) => { {DateColumn({
return ( id: "last_used",
<NumberFieldUnit title: "Last Used",
value={value} dataSource,
unit="g" tableState,
options={{ })}
maximumFractionDigits: 1, {SortedColumn({
}} id: "comment",
/> title: "Comment",
); dataSource,
}} tableState,
/> })}
<Table.Column
dataIndex="remaining_weight"
title="Estimated Remaining Weight"
sorter={true}
sortOrder={getSortOrderForField(typedSorters, "remaining_weight")}
render={(value) => {
if (value === null || value === undefined) {
return <TextField value="Unknown" />;
}
return (
<NumberFieldUnit
value={Math.max(value, 0)}
unit="g"
options={{
maximumFractionDigits: 1,
}}
/>
);
}}
/>
<Table.Column
dataIndex="location"
title="Location"
sorter={true}
sortOrder={getSortOrderForField(typedSorters, "location")}
filters={useListFiltersForField(dataSource, "location")}
filteredValue={getFiltersForField(typedFilters, "location")}
/>
<Table.Column
dataIndex="lot_nr"
title="Lot Nr"
sorter={true}
sortOrder={getSortOrderForField(typedSorters, "lot_nr")}
filters={useListFiltersForField(dataSource, "lot_nr")}
filteredValue={getFiltersForField(typedFilters, "lot_nr")}
/>
<Table.Column
dataIndex={["first_used"]}
title="First Used"
sorter={true}
sortOrder={getSortOrderForField(typedSorters, "first_used")}
render={(value) => (
<DateField
hidden={!value}
value={dayjs.utc(value).local()}
title={dayjs.utc(value).local().format()}
format="YYYY-MM-DD HH:mm:ss"
/>
)}
/>
<Table.Column
dataIndex={["last_used"]}
title="Last Used"
sorter={true}
sortOrder={getSortOrderForField(typedSorters, "last_used")}
render={(value) => (
<DateField
hidden={!value}
value={dayjs.utc(value).local()}
title={dayjs.utc(value).local().format()}
format="YYYY-MM-DD HH:mm:ss"
/>
)}
/>
<Table.Column
dataIndex={["comment"]}
title="Comment"
sorter={true}
sortOrder={getSortOrderForField(typedSorters, "comment")}
/>
<Table.Column <Table.Column
title="Actions" title="Actions"
dataIndex="actions"
render={(_, record: BaseRecord) => ( render={(_, record: BaseRecord) => (
<Space> <Space>
<EditButton <EditButton

View File

@@ -5,24 +5,22 @@ import {
List, List,
EditButton, EditButton,
ShowButton, ShowButton,
DateField,
CloneButton, CloneButton,
} from "@refinedev/antd"; } from "@refinedev/antd";
import { Table, Space, Button } 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 { import { genericSorter, typeSorters } from "../../utils/sorting";
genericSorter,
getSortOrderForField,
typeSorters,
} from "../../utils/sorting";
import { genericFilterer, typeFilters } from "../../utils/filtering"; import { genericFilterer, typeFilters } from "../../utils/filtering";
import { IVendor } from "./model"; import { IVendor } from "./model";
import { import {
TableState,
useInitialTableState, useInitialTableState,
useStoreInitialState, useStoreInitialState,
} from "../../utils/saveload"; } from "../../utils/saveload";
import { FilterOutlined } from "@ant-design/icons"; import { EditOutlined, FilterOutlined } from "@ant-design/icons";
import i18n from "../../i18n";
import { DateColumn, SortedColumn } from "../../components/column";
dayjs.extend(utc); dayjs.extend(utc);
@@ -58,16 +56,29 @@ export const VendorList: React.FC<IResourceComponentsProps> = () => {
}, },
}); });
// 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
);
// Type the sorters and filters // Type the sorters and filters
const typedSorters = typeSorters<IVendor>(sorters); const typedSorters = typeSorters<IVendor>(sorters);
const typedFilters = typeFilters<IVendor>(filters); const typedFilters = typeFilters<IVendor>(filters);
// Store state in local storage // Store state in local storage
useStoreInitialState("vendorList", { const tableState: TableState = {
sorters, sorters,
filters, filters,
pagination: { current, pageSize }, pagination: { current, pageSize },
}); showColumns,
};
useStoreInitialState("vendorList", 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(
@@ -97,6 +108,28 @@ export const VendorList: React.FC<IResourceComponentsProps> = () => {
> >
Clear Filters Clear Filters
</Button> </Button>
<Dropdown
trigger={["click"]}
menu={{
items: allColumns.map((column) => ({
key: column,
label: i18n.t(`vendors.fields.${column}`),
})),
selectedKeys: showColumns,
selectable: true,
multiple: true,
onDeselect: (keys) => {
setShowColumns(keys.selectedKeys);
},
onSelect: (keys) => {
setShowColumns(keys.selectedKeys);
},
}}
>
<Button type="primary" icon={<EditOutlined />}>
Hide Columns
</Button>
</Dropdown>
{defaultButtons} {defaultButtons}
</> </>
)} )}
@@ -115,37 +148,30 @@ export const VendorList: React.FC<IResourceComponentsProps> = () => {
}} }}
rowKey="id" rowKey="id"
> >
<Table.Column {SortedColumn({
dataIndex="id" id: "id",
title="Id" title: "Id",
sorter={true} dataSource,
sortOrder={getSortOrderForField(typedSorters, "id")} tableState,
/> })}
<Table.Column {SortedColumn({
dataIndex="name" id: "name",
title="Name" title: "Name",
sorter={true} dataSource,
sortOrder={getSortOrderForField(typedSorters, "name")} tableState,
/> })}
<Table.Column {DateColumn({
dataIndex={["registered"]} id: "registered",
title="Registered" title: "Registered",
sorter={true} dataSource,
sortOrder={getSortOrderForField(typedSorters, "registered")} tableState,
render={(value) => ( })}
<DateField {SortedColumn({
value={dayjs.utc(value).local()} id: "comment",
title={dayjs.utc(value).local().format()} title: "Comment",
format="YYYY-MM-DD HH:mm:ss" dataSource,
/> tableState,
)} })}
/>
<Table.Column
dataIndex={["comment"]}
title="Comment"
sorter={true}
sortOrder={getSortOrderForField(typedSorters, "comment")}
/>
<Table.Column <Table.Column
title="Actions" title="Actions"
dataIndex="actions" dataIndex="actions"

View File

@@ -7,10 +7,11 @@ interface Pagination {
pageSize: number; pageSize: number;
} }
interface TableState { export interface TableState {
sorters: CrudSort[]; sorters: CrudSort[];
filters: CrudFilter[]; filters: CrudFilter[];
pagination: Pagination; pagination: Pagination;
showColumns?: string[];
} }
export function useInitialTableState(tableId: string): TableState { export function useInitialTableState(tableId: string): TableState {
@@ -18,10 +19,13 @@ export function useInitialTableState(tableId: string): TableState {
const savedSorters = isLocalStorageAvailable ? localStorage.getItem(`${tableId}-sorters`) : null; const savedSorters = isLocalStorageAvailable ? localStorage.getItem(`${tableId}-sorters`) : null;
const savedFilters = isLocalStorageAvailable ? localStorage.getItem(`${tableId}-filters`) : null; const savedFilters = isLocalStorageAvailable ? localStorage.getItem(`${tableId}-filters`) : null;
const savedPagination = isLocalStorageAvailable ? localStorage.getItem(`${tableId}-pagination`) : null; const savedPagination = isLocalStorageAvailable ? localStorage.getItem(`${tableId}-pagination`) : null;
const savedShowColumns = isLocalStorageAvailable ? localStorage.getItem(`${tableId}-showColumns`) : null;
const sorters = savedSorters ? JSON.parse(savedSorters) : [{ field: "id", order: "asc" }]; const sorters = savedSorters ? JSON.parse(savedSorters) : [{ field: "id", order: "asc" }];
const filters = savedFilters ? JSON.parse(savedFilters) : []; const filters = savedFilters ? JSON.parse(savedFilters) : [];
const pagination = savedPagination ? JSON.parse(savedPagination) : { page: 1, pageSize: 20 }; const pagination = savedPagination ? JSON.parse(savedPagination) : { page: 1, pageSize: 20 };
return { sorters, filters, pagination }; const showColumns = savedShowColumns ? JSON.parse(savedShowColumns) : undefined;
return { sorters, filters, pagination, showColumns };
}); });
return initialState; return initialState;
} }
@@ -44,4 +48,14 @@ export function useStoreInitialState(tableId: string, state: TableState) {
localStorage.setItem(`${tableId}-pagination`, JSON.stringify(state.pagination)); localStorage.setItem(`${tableId}-pagination`, JSON.stringify(state.pagination));
} }
}, [tableId, state.pagination]); }, [tableId, state.pagination]);
React.useEffect(() => {
if (isLocalStorageAvailable) {
if (state.showColumns === undefined) {
localStorage.removeItem(`${tableId}-showColumns`);
} else {
localStorage.setItem(`${tableId}-showColumns`, JSON.stringify(state.showColumns));
}
}
}, [tableId, state.showColumns]);
} }