From c3aeadda4aec65ea247fb154a5deb9334eb5daa0 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 28 Nov 2023 23:15:08 +1100 Subject: [PATCH] Add useTable hook (#6000) * Add new hook - useTable - Will replace useTableRefresh - More extensible (further functionality to follow) - Pass entire state through to final table - Defined interface for return type * Update BomTable * Update UsedInTable * Refactor attachment table * Update remaining tables * Clean StockItemTable * Fix NotificationTable * Remove unused import --- .../src/components/tables/InvenTreeTable.tsx | 17 +++++----- .../src/components/tables/bom/BomTable.tsx | 10 +++--- .../src/components/tables/bom/UsedInTable.tsx | 6 ++-- .../tables/build/BuildOrderTable.tsx | 6 ++-- .../tables/general/AttachmentTable.tsx | 16 ++++----- .../tables/general/CompanyTable.tsx | 6 ++-- .../notifications/NotificationsTable.tsx | 7 ++-- .../tables/part/PartCategoryTable.tsx | 6 ++-- .../tables/part/PartParameterTable.tsx | 12 +++---- .../part/PartParameterTemplateTable.tsx | 14 ++++---- .../src/components/tables/part/PartTable.tsx | 6 ++-- .../tables/part/RelatedPartTable.tsx | 10 +++--- .../tables/plugin/PluginListTable.tsx | 8 ++--- .../purchasing/PurchaseOrderLineItemTable.tsx | 12 +++---- .../tables/purchasing/PurchaseOrderTable.tsx | 6 ++-- .../tables/purchasing/SupplierPartTable.tsx | 12 +++---- .../tables/sales/ReturnOrderTable.tsx | 6 ++-- .../tables/sales/SalesOrderTable.tsx | 6 ++-- .../tables/settings/CurrencyTable.tsx | 8 ++--- .../tables/settings/CustomUnitsTable.tsx | 12 +++---- .../components/tables/settings/GroupTable.tsx | 12 +++---- .../tables/settings/ProjectCodeTable.tsx | 12 +++---- .../components/tables/settings/UserTable.tsx | 14 ++++---- .../tables/stock/StockItemTable.tsx | 17 +++------- .../tables/stock/StockLocationTable.tsx | 6 ++-- src/frontend/src/hooks/TableRefresh.tsx | 29 ---------------- src/frontend/src/hooks/UseTable.tsx | 34 +++++++++++++++++++ src/frontend/src/pages/Notifications.tsx | 18 +++++----- 28 files changed, 160 insertions(+), 168 deletions(-) delete mode 100644 src/frontend/src/hooks/TableRefresh.tsx create mode 100644 src/frontend/src/hooks/UseTable.tsx diff --git a/src/frontend/src/components/tables/InvenTreeTable.tsx b/src/frontend/src/components/tables/InvenTreeTable.tsx index 1ec524293f..63bb1243f9 100644 --- a/src/frontend/src/components/tables/InvenTreeTable.tsx +++ b/src/frontend/src/components/tables/InvenTreeTable.tsx @@ -9,6 +9,7 @@ import { DataTable, DataTableSortStatus } from 'mantine-datatable'; import { Fragment, useEffect, useMemo, useState } from 'react'; import { api } from '../../App'; +import { TableState } from '../../hooks/UseTable'; import { ButtonMenu } from '../buttons/ButtonMenu'; import { TableColumn } from './Column'; import { TableColumnSelect } from './ColumnSelect'; @@ -25,8 +26,7 @@ const defaultPageSize: number = 25; * Set of optional properties which can be passed to an InvenTreeTable component * * @param params : any - Base query parameters - * @param tableKey : string - Unique key for the table (used for local storage) - * @param refreshId : string - Unique ID for the table (used to trigger a refresh) + * @param tableState : TableState - State manager for the table * @param defaultSortColumn : string - Default column to sort by * @param noRecordsText : string - Text to display when no records are found * @param enableDownload : boolean - Enable download actions @@ -92,18 +92,19 @@ const defaultInvenTreeTableProps: InvenTreeTableProps = { */ export function InvenTreeTable({ url, - tableKey, + tableState, columns, props }: { url: string; - tableKey: string; + tableState: TableState; columns: TableColumn[]; props: InvenTreeTableProps; }) { // Use the first part of the table key as the table name const tableName: string = useMemo(() => { - return tableKey.split('-')[0]; + let key = tableState?.tableKey ?? 'table'; + return key.split('-')[0]; }, []); // Build table properties based on provided props (and default props) @@ -411,14 +412,12 @@ export function InvenTreeTable({ const [recordCount, setRecordCount] = useState(0); /* - * Reload the table whenever the refetch changes + * Reload the table whenever the tableKey changes * this allows us to programmatically refresh the table - * - * Implement this using the custom useTableRefresh hook */ useEffect(() => { refetch(); - }, [tableKey, props.params]); + }, [tableState?.tableKey, props.params]); return ( <> diff --git a/src/frontend/src/components/tables/bom/BomTable.tsx b/src/frontend/src/components/tables/bom/BomTable.tsx index ac46a504a4..ee7f340615 100644 --- a/src/frontend/src/components/tables/bom/BomTable.tsx +++ b/src/frontend/src/components/tables/bom/BomTable.tsx @@ -12,7 +12,7 @@ import { ApiPaths } from '../../../enums/ApiEndpoints'; import { UserRoles } from '../../../enums/Roles'; import { bomItemFields } from '../../../forms/BomForms'; import { openDeleteApiForm, openEditApiForm } from '../../../functions/forms'; -import { useTableRefresh } from '../../../hooks/TableRefresh'; +import { useTable } from '../../../hooks/UseTable'; import { apiUrl } from '../../../states/ApiState'; import { useUserState } from '../../../states/UserState'; import { Thumbnail } from '../../images/Thumbnail'; @@ -51,7 +51,7 @@ export function BomTable({ const user = useUserState(); - const { tableKey, refreshTable } = useTableRefresh('bom'); + const table = useTable('bom'); const tableColumns: TableColumn[] = useMemo(() => { return [ @@ -289,7 +289,7 @@ export function BomTable({ title: t`Edit Bom Item`, fields: bomItemFields(), successMessage: t`Bom item updated`, - onFormSuccess: refreshTable + onFormSuccess: table.refreshTable }); } }) @@ -305,7 +305,7 @@ export function BomTable({ pk: record.pk, title: t`Delete Bom Item`, successMessage: t`Bom item deleted`, - onFormSuccess: refreshTable, + onFormSuccess: table.refreshTable, preFormContent: ( {t`Are you sure you want to remove this BOM item?`} ) @@ -322,7 +322,7 @@ export function BomTable({ return ( { return [ @@ -86,7 +86,7 @@ export function UsedInTable({ return ( attachmentTableColumns(), []); @@ -122,7 +122,7 @@ export function AttachmentTable({ model: model, pk: record.pk, attachmentType: record.attachment ? 'file' : 'link', - callback: refreshTable + callback: table.refreshTable }); } }) @@ -136,7 +136,7 @@ export function AttachmentTable({ deleteAttachment({ endpoint: endpoint, pk: record.pk, - callback: refreshTable + callback: table.refreshTable }); } }) @@ -162,7 +162,7 @@ export function AttachmentTable({ color: 'green' }); - refreshTable(); + table.refreshTable(); return response; }) @@ -192,7 +192,7 @@ export function AttachmentTable({ model: model, pk: pk, attachmentType: 'file', - callback: refreshTable + callback: table.refreshTable }); }} > @@ -211,7 +211,7 @@ export function AttachmentTable({ model: model, pk: pk, attachmentType: 'link', - callback: refreshTable + callback: table.refreshTable }); }} > @@ -230,7 +230,7 @@ export function AttachmentTable({ RowAction[]; }) { const columns: TableColumn[] = useMemo(() => { @@ -42,7 +43,7 @@ export function NotificationTable({ return ( { return [ @@ -42,7 +42,7 @@ export function PartCategoryTable({ params = {} }: { params?: any }) { return ( {t`Are you sure you want to remove this parameter?`} ) @@ -170,7 +170,7 @@ export function PartParameterTable({ partId }: { partId: any }) { data: {} }, successMessage: t`Part parameter added`, - onFormSuccess: refreshTable + onFormSuccess: table.refreshTable }); }, [partId]); @@ -189,7 +189,7 @@ export function PartParameterTable({ partId }: { partId: any }) { return ( {t`Remove parameter template`} }); } @@ -95,7 +93,7 @@ export function PartParameterTemplateTable() { title: t`Create Parameter Template`, fields: partParameterTemplateFields(), successMessage: t`Parameter template created`, - onFormSuccess: refreshTable + onFormSuccess: table.refreshTable }); }, []); @@ -112,7 +110,7 @@ export function PartParameterTemplateTable() { return ( partTableColumns(), []); const tableFilters = useMemo(() => partTableFilters(), []); - const { tableKey, refreshTable } = useTableRefresh('part'); + const table = useTable('part-list'); const navigate = useNavigate(); return ( {t`Are you sure you want to remove this relationship?`} ), - onFormSuccess: refreshTable + onFormSuccess: table.refreshTable }); } }) @@ -127,7 +127,7 @@ export function RelatedPartTable({ partId }: { partId: number }): ReactNode { return ( [ @@ -147,7 +147,7 @@ export function PluginListTable({ props }: { props: InvenTreeTableProps }) { api .patch(url, { active: active }) .then(() => { - refreshTable(); + table.refreshTable(); notifications.hide(id); notifications.show({ title: t`Plugin updated`, @@ -203,7 +203,7 @@ export function PluginListTable({ props }: { props: InvenTreeTableProps }) { return ( {t`Are you sure you want to remove this supplier part?`} ) @@ -226,7 +226,7 @@ export function SupplierPartTable({ params }: { params: any }): ReactNode { {addSupplierPartModal} { return [ @@ -35,7 +35,7 @@ export function CurrencyTable() { api .post(apiUrl(ApiPaths.currency_refresh), {}) .then(() => { - refreshTable(); + table.refreshTable(); showNotification({ message: t`Exchange rates updated`, color: 'green' @@ -63,7 +63,7 @@ export function CurrencyTable() { return ( {t`Are you sure you want to remove this custom unit?`} ) @@ -98,7 +98,7 @@ export function CustomUnitsTable() { symbol: {} }, successMessage: t`Custom unit created`, - onFormSuccess: refreshTable + onFormSuccess: table.refreshTable }); }, []); @@ -116,7 +116,7 @@ export function CustomUnitsTable() { return ( { return [ @@ -42,7 +42,7 @@ export function GroupTable() { fields: { name: {} }, - onFormSuccess: refreshTable, + onFormSuccess: table.refreshTable, successMessage: t`Group updated` }); } @@ -54,7 +54,7 @@ export function GroupTable() { pk: record.pk, title: t`Delete group`, successMessage: t`Group deleted`, - onFormSuccess: refreshTable, + onFormSuccess: table.refreshTable, preFormContent: ( {t`Are you sure you want to delete this group?`} ) @@ -69,7 +69,7 @@ export function GroupTable() { url: ApiPaths.group_list, title: t`Add group`, fields: { name: {} }, - onFormSuccess: refreshTable, + onFormSuccess: table.refreshTable, successMessage: t`Added group` }); }, []); @@ -91,7 +91,7 @@ export function GroupTable() { return ( {t`Are you sure you want to remove this project code?`} ) @@ -87,7 +87,7 @@ export function ProjectCodeTable() { description: {}, responsible: {} }, - onFormSuccess: refreshTable, + onFormSuccess: table.refreshTable, successMessage: t`Added project code` }); }, []); @@ -105,7 +105,7 @@ export function ProjectCodeTable() { return ( (); @@ -103,7 +103,7 @@ export function UserTable() { first_name: {}, last_name: {} }, - onFormSuccess: refreshTable, + onFormSuccess: table.refreshTable, successMessage: t`User updated` }); } @@ -115,7 +115,7 @@ export function UserTable() { pk: record.pk, title: t`Delete user`, successMessage: t`User deleted`, - onFormSuccess: refreshTable, + onFormSuccess: table.refreshTable, preFormContent: ( {t`Are you sure you want to delete this user?`} ) @@ -135,7 +135,7 @@ export function UserTable() { first_name: {}, last_name: {} }, - onFormSuccess: refreshTable, + onFormSuccess: table.refreshTable, successMessage: t`Added user` }); }, []); @@ -155,12 +155,12 @@ export function UserTable() { + {text} {part.units && ( @@ -264,27 +263,19 @@ export function StockItemTable({ params = {} }: { params?: any }) { let tableColumns = useMemo(() => stockItemTableColumns(), []); let tableFilters = useMemo(() => stockItemTableFilters(), []); - const { tableKey, refreshTable } = useTableRefresh('stockitem'); - - function stockItemRowActions(record: any): RowAction[] { - let actions: RowAction[] = []; - - // TODO: Custom row actions for stock table - return actions; - } + const table = useTable('stockitems'); const navigate = useNavigate(); return ( navigate(`/stock/item/${record.pk}`), params: { ...params, diff --git a/src/frontend/src/components/tables/stock/StockLocationTable.tsx b/src/frontend/src/components/tables/stock/StockLocationTable.tsx index 7af9a8acb9..5c1e969e7d 100644 --- a/src/frontend/src/components/tables/stock/StockLocationTable.tsx +++ b/src/frontend/src/components/tables/stock/StockLocationTable.tsx @@ -3,7 +3,7 @@ import { useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import { ApiPaths } from '../../../enums/ApiEndpoints'; -import { useTableRefresh } from '../../../hooks/TableRefresh'; +import { useTable } from '../../../hooks/UseTable'; import { apiUrl } from '../../../states/ApiState'; import { YesNoButton } from '../../items/YesNoButton'; import { TableColumn } from '../Column'; @@ -14,7 +14,7 @@ import { InvenTreeTable } from '../InvenTreeTable'; * Stock location table */ export function StockLocationTable({ params = {} }: { params?: any }) { - const { tableKey, refreshTable } = useTableRefresh('stocklocation'); + const table = useTable('stocklocation'); const navigate = useNavigate(); @@ -64,7 +64,7 @@ export function StockLocationTable({ params = {} }: { params?: any }) { return ( - * - * @returns { tableKey, refreshTable } - * - * To use this hook: - * const { tableKey, refreshTable } = useTableRefresh(); - * - * Then, pass the refreshId to the InvenTreeTable component: - * - */ -export function useTableRefresh(tableName: string) { - const [tableKey, setTableKey] = useState(generateTableName()); - - function generateTableName() { - return `${tableName}-${randomId()}`; - } - - // Generate a new ID to refresh the table - const refreshTable = useCallback(function () { - setTableKey(generateTableName()); - }, []); - - return { tableKey, refreshTable }; -} diff --git a/src/frontend/src/hooks/UseTable.tsx b/src/frontend/src/hooks/UseTable.tsx new file mode 100644 index 0000000000..686fe74749 --- /dev/null +++ b/src/frontend/src/hooks/UseTable.tsx @@ -0,0 +1,34 @@ +import { randomId } from '@mantine/hooks'; +import { useCallback, useState } from 'react'; + +export type TableState = { + tableKey: string; + refreshTable: () => void; +}; + +/** + * A custom hook for managing the state of an component. + * + * tableKey: A unique key for the table. When this key changes, the table will be refreshed. + * refreshTable: A callback function to externally refresh the table. + * + */ + +export function useTable(tableName: string): TableState { + // Function to generate a new ID (to refresh the table) + function generateTableName() { + return `${tableName}-${randomId()}`; + } + + const [tableKey, setTableKey] = useState(generateTableName()); + + // Callback used to refresh (reload) the table + const refreshTable = useCallback(() => { + setTableKey(generateTableName()); + }, []); + + return { + tableKey, + refreshTable + }; +} diff --git a/src/frontend/src/pages/Notifications.tsx b/src/frontend/src/pages/Notifications.tsx index bf6e01a5fe..282f723b2a 100644 --- a/src/frontend/src/pages/Notifications.tsx +++ b/src/frontend/src/pages/Notifications.tsx @@ -14,12 +14,12 @@ import { PageDetail } from '../components/nav/PageDetail'; import { PanelGroup } from '../components/nav/PanelGroup'; import { NotificationTable } from '../components/tables/notifications/NotificationsTable'; import { ApiPaths } from '../enums/ApiEndpoints'; -import { useTableRefresh } from '../hooks/TableRefresh'; +import { useTable } from '../hooks/UseTable'; import { apiUrl } from '../states/ApiState'; export default function NotificationsPage() { - const unreadRefresh = useTableRefresh('unreadnotifications'); - const historyRefresh = useTableRefresh('readnotifications'); + const unreadTable = useTable('unreadnotifications'); + const readTable = useTable('readnotifications'); const notificationPanels = useMemo(() => { return [ @@ -30,7 +30,7 @@ export default function NotificationsPage() { content: ( [ { title: t`Mark as read`, @@ -43,7 +43,7 @@ export default function NotificationsPage() { read: true }) .then((response) => { - unreadRefresh.refreshTable(); + unreadTable.refreshTable(); }); } } @@ -58,7 +58,7 @@ export default function NotificationsPage() { content: ( [ { title: t`Mark as unread`, @@ -71,7 +71,7 @@ export default function NotificationsPage() { read: false }) .then((response) => { - historyRefresh.refreshTable(); + readTable.refreshTable(); }); } }, @@ -83,7 +83,7 @@ export default function NotificationsPage() { api .delete(apiUrl(ApiPaths.notifications_list, record.pk)) .then((response) => { - historyRefresh.refreshTable(); + readTable.refreshTable(); }); } } @@ -92,7 +92,7 @@ export default function NotificationsPage() { ) } ]; - }, [historyRefresh, unreadRefresh]); + }, [unreadTable, readTable]); return ( <>