Skip to content

Commit

Permalink
✨ Provide initial state for column management (#1964)
Browse files Browse the repository at this point in the history
Related improvements:
1. support restoring columns to initial configuration
2. clean up invalid columns stored in local storage
    
Part-of: #1931

Signed-off-by: Radoslaw Szwajkowski <[email protected]>
  • Loading branch information
rszwajko authored Jun 18, 2024
1 parent f66bee0 commit 0cce0d8
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 20 deletions.
44 changes: 37 additions & 7 deletions client/src/app/hooks/table-controls/column/useColumnState.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,58 @@
import { useLocalStorage } from "@migtools/lib-ui";
import { ColumnSetting } from "../types";
import { useEffect } from "react";

export interface ColumnState<TColumnKey extends string> {
id: TColumnKey;
label: string;
isVisible: boolean;
isIdentity?: boolean;
}

export interface IColumnState<TColumnKey extends string> {
columns: ColumnState<TColumnKey>[];
defaultColumns: ColumnState<TColumnKey>[];
setColumns: (newColumns: ColumnState<TColumnKey>[]) => void;
}

interface IColumnStateArgs<TColumnKey extends string> {
initialColumns: ColumnState<TColumnKey>[];
initialColumns?: Partial<Record<TColumnKey, ColumnSetting>>;
columnsKey: string;
supportedColumns: Record<TColumnKey, string>;
}

export const useColumnState = <TColumnKey extends string>(
args: IColumnStateArgs<TColumnKey>
): IColumnState<TColumnKey> => {
export const useColumnState = <TColumnKey extends string>({
initialColumns,
supportedColumns,
columnsKey,
}: IColumnStateArgs<TColumnKey>): IColumnState<TColumnKey> => {
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<ColumnState<TColumnKey>[]>({
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 };
};
9 changes: 9 additions & 0 deletions client/src/app/hooks/table-controls/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -151,6 +159,7 @@ export type IUseTableControlStateArgs<
/**
* Initial state for the columns feature. If omitted, all columns are enabled by default.
*/
initialColumns?: Partial<Record<TColumnKey, ColumnSetting>>;
} & IFilterStateArgs<TItem, TFilterCategoryKey> &
ISortStateArgs<TSortableColumnKey> &
IPaginationStateArgs & {
Expand Down
10 changes: 2 additions & 8 deletions client/src/app/hooks/table-controls/useTableControlState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<TColumnKey>({
columnsKey: tableName,
initialColumns,
supportedColumns: columnNames,
});
return {
...args,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -804,6 +807,7 @@ export const ApplicationsTable: React.FC = () => {
<ManageColumnsToolbar
columns={columnState.columns}
setColumns={columnState.setColumns}
defaultColumns={columnState.defaultColumns}
/>
</ToolbarGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export interface ManagedColumnsProps<TColumnKey extends string> {
saveLabel?: string;
cancelLabel?: string;
title?: string;
restoreLabel?: string;
defaultColumns: ColumnState<TColumnKey>[];
}

export const ManageColumnsModal = <TColumnKey extends string>({
Expand All @@ -35,6 +37,8 @@ export const ManageColumnsModal = <TColumnKey extends string>({
saveLabel = "Save",
cancelLabel = "Cancel",
title = "Manage Columns",
restoreLabel = "Restore defaults",
defaultColumns,
}: ManagedColumnsProps<TColumnKey>) => {
const [editedColumns, setEditedColumns] =
useState<ColumnState<TColumnKey>[]>(columns);
Expand All @@ -47,6 +51,7 @@ export const ManageColumnsModal = <TColumnKey extends string>({
}))
);
};
const restoreDefaults = () => setEditedColumns([...defaultColumns]);

const onSave = () => {
// If ordering is implemented, update accordingly
Expand All @@ -72,19 +77,20 @@ export const ManageColumnsModal = <TColumnKey extends string>({
<Button key="cancel" variant="secondary" onClick={onClose}>
{cancelLabel}
</Button>,
<Button key="restore" variant="link" onClick={restoreDefaults}>
{restoreLabel}
</Button>,
]}
>
<DataList aria-label={title} id="table-column-management" isCompact>
{editedColumns.map(({ id, label, isVisible }, index) => (
{editedColumns.map(({ id, label, isVisible, isIdentity }, index) => (
<DataListItem key={index}>
<DataListItemRow className="custom-data-list-item-row">
<DataListControl>
<DataListCheck
aria-labelledby={`check-${id}`}
checked={isVisible}
//TODO: Dynamic disable logic based on idProperty definition in useTableControlState.
// Currently, any column whose name is 'name' will not be allowed to be hidden
isDisabled={id === "name"}
checked={isVisible || isIdentity}
isDisabled={isIdentity}
onChange={(e, checked) => onSelect(id, checked)}
/>
</DataListControl>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ import { ColumnsIcon } from "@patternfly/react-icons";

interface ManageColumnsToolbarProps<TColumnKey extends string> {
columns: ColumnState<TColumnKey>[];
defaultColumns: ColumnState<TColumnKey>[];
setColumns: (newColumns: ColumnState<TColumnKey>[]) => void;
}

export const ManageColumnsToolbar = <TColumnKey extends string>({
columns,
setColumns,
defaultColumns,
}: ManageColumnsToolbarProps<TColumnKey>) => {
const { t } = useTranslation();
const [isOpen, setIsOpen] = useState(false);
Expand Down Expand Up @@ -46,6 +48,7 @@ export const ManageColumnsToolbar = <TColumnKey extends string>({
saveLabel={t("actions.save")}
cancelLabel={t("actions.cancel")}
title={t("dialog.title.manageColumns")}
defaultColumns={defaultColumns}
/>
)}
</>
Expand Down

0 comments on commit 0cce0d8

Please sign in to comment.