From 0cce0d8e8f597d4c11c4e1cb8184e894a42b3973 Mon Sep 17 00:00:00 2001 From: Radoslaw Szwajkowski Date: Tue, 18 Jun 2024 18:30:54 +0200 Subject: [PATCH] :sparkles: Provide initial state for column management (#1964) Related improvements: 1. support restoring columns to initial configuration 2. clean up invalid columns stored in local storage Part-of: https://github.com/konveyor/tackle2-ui/issues/1931 Signed-off-by: Radoslaw Szwajkowski --- .../table-controls/column/useColumnState.ts | 44 ++++++++++++++++--- client/src/app/hooks/table-controls/types.ts | 9 ++++ .../table-controls/useTableControlState.ts | 10 +---- .../applications-table/applications-table.tsx | 4 ++ .../components/manage-columns-modal.tsx | 16 ++++--- .../components/manage-columns-toolbar.tsx | 3 ++ 6 files changed, 66 insertions(+), 20 deletions(-) diff --git a/client/src/app/hooks/table-controls/column/useColumnState.ts b/client/src/app/hooks/table-controls/column/useColumnState.ts index cdfaf29e2..ff7bc81c7 100644 --- a/client/src/app/hooks/table-controls/column/useColumnState.ts +++ b/client/src/app/hooks/table-controls/column/useColumnState.ts @@ -1,28 +1,58 @@ import { useLocalStorage } from "@migtools/lib-ui"; +import { ColumnSetting } from "../types"; +import { useEffect } from "react"; export interface ColumnState { id: TColumnKey; label: string; isVisible: boolean; + isIdentity?: boolean; } export interface IColumnState { columns: ColumnState[]; + defaultColumns: ColumnState[]; setColumns: (newColumns: ColumnState[]) => void; } interface IColumnStateArgs { - initialColumns: ColumnState[]; + initialColumns?: Partial>; columnsKey: string; + supportedColumns: Record; } -export const useColumnState = ( - args: IColumnStateArgs -): IColumnState => { +export const useColumnState = ({ + initialColumns, + supportedColumns, + columnsKey, +}: IColumnStateArgs): IColumnState => { + const defaultColumns = ( + Object.entries(supportedColumns) as [TColumnKey, string][] + ).map(([id, label]) => ({ + id, + label, + isVisible: initialColumns?.[id]?.isVisible ?? true, + isIdentity: initialColumns?.[id]?.isIdentity, + })); const [columns, setColumns] = useLocalStorage[]>({ - key: args.columnsKey, - defaultValue: args.initialColumns, + key: columnsKey, + defaultValue: defaultColumns, }); - return { columns, setColumns }; + useEffect(() => { + const valid = columns.filter(({ id }) => + defaultColumns.find((it) => id === it.id) + ); + if (valid.length !== defaultColumns.length) { + setColumns( + defaultColumns.map((it) => ({ + ...it, + isVisible: + valid.find(({ id }) => id === it.id)?.isVisible ?? it.isVisible, + })) + ); + } + }, [defaultColumns, columns, setColumns]); + + return { columns, setColumns, defaultColumns }; }; diff --git a/client/src/app/hooks/table-controls/types.ts b/client/src/app/hooks/table-controls/types.ts index 5aea4d7e5..ba874aded 100644 --- a/client/src/app/hooks/table-controls/types.ts +++ b/client/src/app/hooks/table-controls/types.ts @@ -104,6 +104,14 @@ export type IFeaturePersistenceArgs< */ persistTo?: PersistTarget; }; + +export interface ColumnSetting { + // visibility status, can change in time + isVisible?: boolean; + // column is always visible because it's needed to uniquely identify the row + isIdentity?: boolean; +} + /** * Table-level persistence-specific args * - Extra args needed for persisting state at the table level. @@ -151,6 +159,7 @@ export type IUseTableControlStateArgs< /** * Initial state for the columns feature. If omitted, all columns are enabled by default. */ + initialColumns?: Partial>; } & IFilterStateArgs & ISortStateArgs & IPaginationStateArgs & { diff --git a/client/src/app/hooks/table-controls/useTableControlState.ts b/client/src/app/hooks/table-controls/useTableControlState.ts index 980e35ac6..7e9d1ca65 100644 --- a/client/src/app/hooks/table-controls/useTableControlState.ts +++ b/client/src/app/hooks/table-controls/useTableControlState.ts @@ -68,17 +68,11 @@ export const useTableControlState = < persistTo: getPersistTo("activeItem"), }); - const { columnNames, tableName } = args; - - const initialColumns = Object.entries(columnNames).map(([id, label]) => ({ - id: id as TColumnKey, - label: label as string, - isVisible: true, - })); - + const { columnNames, tableName, initialColumns } = args; const columnState = useColumnState({ columnsKey: tableName, initialColumns, + supportedColumns: columnNames, }); return { ...args, diff --git a/client/src/app/pages/applications/applications-table/applications-table.tsx b/client/src/app/pages/applications/applications-table/applications-table.tsx index ed4ce863c..8ced4d2be 100644 --- a/client/src/app/pages/applications/applications-table/applications-table.tsx +++ b/client/src/app/pages/applications/applications-table/applications-table.tsx @@ -325,6 +325,9 @@ export const ApplicationsTable: React.FC = () => { isLoading: isFetchingApplications, sortableColumns: ["name", "businessService", "tags", "effort"], initialSort: { columnKey: "name", direction: "asc" }, + initialColumns: { + name: { isIdentity: true }, + }, initialFilterValues: deserializedFilterValues, getSortValues: (app) => ({ name: app.name, @@ -804,6 +807,7 @@ export const ApplicationsTable: React.FC = () => { diff --git a/client/src/app/pages/applications/applications-table/components/manage-columns-modal.tsx b/client/src/app/pages/applications/applications-table/components/manage-columns-modal.tsx index ffbbb8dd3..b17004bff 100644 --- a/client/src/app/pages/applications/applications-table/components/manage-columns-modal.tsx +++ b/client/src/app/pages/applications/applications-table/components/manage-columns-modal.tsx @@ -25,6 +25,8 @@ export interface ManagedColumnsProps { saveLabel?: string; cancelLabel?: string; title?: string; + restoreLabel?: string; + defaultColumns: ColumnState[]; } export const ManageColumnsModal = ({ @@ -35,6 +37,8 @@ export const ManageColumnsModal = ({ saveLabel = "Save", cancelLabel = "Cancel", title = "Manage Columns", + restoreLabel = "Restore defaults", + defaultColumns, }: ManagedColumnsProps) => { const [editedColumns, setEditedColumns] = useState[]>(columns); @@ -47,6 +51,7 @@ export const ManageColumnsModal = ({ })) ); }; + const restoreDefaults = () => setEditedColumns([...defaultColumns]); const onSave = () => { // If ordering is implemented, update accordingly @@ -72,19 +77,20 @@ export const ManageColumnsModal = ({ , + , ]} > - {editedColumns.map(({ id, label, isVisible }, index) => ( + {editedColumns.map(({ id, label, isVisible, isIdentity }, index) => ( diff --git a/client/src/app/pages/applications/applications-table/components/manage-columns-toolbar.tsx b/client/src/app/pages/applications/applications-table/components/manage-columns-toolbar.tsx index 8298c0df5..387e5ccb8 100644 --- a/client/src/app/pages/applications/applications-table/components/manage-columns-toolbar.tsx +++ b/client/src/app/pages/applications/applications-table/components/manage-columns-toolbar.tsx @@ -13,12 +13,14 @@ import { ColumnsIcon } from "@patternfly/react-icons"; interface ManageColumnsToolbarProps { columns: ColumnState[]; + defaultColumns: ColumnState[]; setColumns: (newColumns: ColumnState[]) => void; } export const ManageColumnsToolbar = ({ columns, setColumns, + defaultColumns, }: ManageColumnsToolbarProps) => { const { t } = useTranslation(); const [isOpen, setIsOpen] = useState(false); @@ -46,6 +48,7 @@ export const ManageColumnsToolbar = ({ saveLabel={t("actions.save")} cancelLabel={t("actions.cancel")} title={t("dialog.title.manageColumns")} + defaultColumns={defaultColumns} /> )}