From f14ba5bcfdab68c2f0c2fe6250a53f60ab4f4f39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Thu, 3 Aug 2023 09:52:39 +0200 Subject: [PATCH] Introduce datagrid on staff members list view (#4044) --- .changeset/cold-tigers-argue.md | 5 + cypress/e2e/staffMembers.js | 25 ++- locale/defaultMessages.json | 14 +- src/config.ts | 1 + .../useFilterPresets/useFilterPresets.ts | 6 +- src/staff/components/StaffList/StaffList.tsx | 169 ------------------ src/staff/components/StaffList/index.ts | 2 - .../StaffListDatagrid/StaffListDatagrid.tsx | 137 ++++++++++++++ .../components/StaffListDatagrid/datagrid.ts | 104 +++++++++++ .../components/StaffListDatagrid/index.ts | 1 + .../components/StaffListDatagrid/messages.ts | 24 +++ .../StaffListPage/StaffListPage.stories.tsx | 8 +- .../StaffListPage/StaffListPage.tsx | 156 +++++++++------- src/staff/components/StaffListPage/filters.ts | 4 +- src/staff/types.ts | 7 + src/staff/views/StaffList/StaffList.tsx | 99 +++++----- src/staff/views/StaffList/filters.ts | 12 +- src/staff/views/StaffList/sort.ts | 5 +- src/utils/sort.ts | 2 +- 19 files changed, 458 insertions(+), 323 deletions(-) create mode 100644 .changeset/cold-tigers-argue.md delete mode 100644 src/staff/components/StaffList/StaffList.tsx delete mode 100644 src/staff/components/StaffList/index.ts create mode 100644 src/staff/components/StaffListDatagrid/StaffListDatagrid.tsx create mode 100644 src/staff/components/StaffListDatagrid/datagrid.ts create mode 100644 src/staff/components/StaffListDatagrid/index.ts create mode 100644 src/staff/components/StaffListDatagrid/messages.ts create mode 100644 src/staff/types.ts diff --git a/.changeset/cold-tigers-argue.md b/.changeset/cold-tigers-argue.md new file mode 100644 index 000000000..cdb2832ed --- /dev/null +++ b/.changeset/cold-tigers-argue.md @@ -0,0 +1,5 @@ +--- +"saleor-dashboard": minor +--- + +Introduce datagrid on staff members list view diff --git a/cypress/e2e/staffMembers.js b/cypress/e2e/staffMembers.js index 20b77a8ca..c36d8061b 100644 --- a/cypress/e2e/staffMembers.js +++ b/cypress/e2e/staffMembers.js @@ -12,11 +12,7 @@ import { STAFF_MEMBERS_LIST_SELECTORS, } from "../elements/"; import { LOGIN_SELECTORS } from "../elements/account/login-selectors"; -import { - MESSAGES, - TEST_ADMIN_USER, - urlList, -} from "../fixtures"; +import { MESSAGES, TEST_ADMIN_USER, urlList } from "../fixtures"; import { userDetailsUrl } from "../fixtures/urlList"; import { activatePlugin, @@ -28,6 +24,7 @@ import { getMailActivationLinkForUserAndSubject, inviteStaffMemberWithFirstPermission, } from "../support/api/utils/"; +import { ensureCanvasStatic } from "../support/customCommands/sharedElementsOperations/canvas"; import { expectMainMenuAvailableSections, expectWelcomeMessageIncludes, @@ -72,10 +69,11 @@ describe("Staff members", () => { const firstName = faker.name.firstName(); const emailInvite = `${startsWith}${firstName}@example.com`; - cy.visit(urlList.staffMembers) - .expectSkeletonIsVisible() - .get(STAFF_MEMBERS_LIST_SELECTORS.inviteStaffMemberButton) - .click({ force: true }); + cy.visit(urlList.staffMembers); + ensureCanvasStatic(SHARED_ELEMENTS.dataGridTable); + cy.get(STAFF_MEMBERS_LIST_SELECTORS.inviteStaffMemberButton).click({ + force: true, + }); fillUpUserDetailsAndAddFirstPermission(firstName, lastName, emailInvite); getMailActivationLinkForUser(emailInvite).then(urlLink => { cy.clearSessionData().visit(urlLink); @@ -185,10 +183,11 @@ describe("Staff members", () => { () => { const firstName = faker.name.firstName(); const emailInvite = TEST_ADMIN_USER.email; - cy.visit(urlList.staffMembers) - .expectSkeletonIsVisible() - .get(STAFF_MEMBERS_LIST_SELECTORS.inviteStaffMemberButton) - .click({ force: true }); + cy.visit(urlList.staffMembers); + ensureCanvasStatic(SHARED_ELEMENTS.dataGridTable); + cy.get(STAFF_MEMBERS_LIST_SELECTORS.inviteStaffMemberButton).click({ + force: true, + }); fillUpOnlyUserDetails(firstName, lastName, emailInvite); cy.get(INVITE_STAFF_MEMBER_FORM_SELECTORS.emailValidationMessage).should( "be.visible", diff --git a/locale/defaultMessages.json b/locale/defaultMessages.json index 81a082aeb..94293f89b 100644 --- a/locale/defaultMessages.json +++ b/locale/defaultMessages.json @@ -3673,6 +3673,10 @@ "context": "header", "string": "{webhookName} Details" }, + "OTDo9I": { + "context": "tab name", + "string": "All staff members" + }, "OVOU1z": { "context": "section header", "string": "Metadata" @@ -5133,10 +5137,6 @@ "context": "no address is set in draft order", "string": "Not set" }, - "YJ4TXc": { - "context": "tab name", - "string": "All Staff Members" - }, "YKyNm9": { "context": "label", "string": "Gift Card" @@ -5386,9 +5386,6 @@ "context": "subheader", "string": "Here is some information we gathered about your store" }, - "aDbrOK": { - "string": "Search Staff Member" - }, "aEc9Ar": { "context": "gift card history message", "string": "Gift card balance was reset by {resetBy}" @@ -7258,6 +7255,9 @@ "context": "attributes, section header", "string": "Variant Selection Attributes" }, + "o68j+t": { + "string": "Search staff members..." + }, "o8S0Ac": { "context": "usage limit uses left caption", "string": "Uses left" diff --git a/src/config.ts b/src/config.ts index 87495e8b9..e01626dc8 100644 --- a/src/config.ts +++ b/src/config.ts @@ -121,6 +121,7 @@ export const defaultListSettings: AppListViewSettings = { }, [ListViews.STAFF_MEMBERS_LIST]: { rowNumber: PAGINATE_BY, + columns: ["name", "email", "status"], }, [ListViews.PERMISSION_GROUP_LIST]: { rowNumber: PAGINATE_BY, diff --git a/src/hooks/useFilterPresets/useFilterPresets.ts b/src/hooks/useFilterPresets/useFilterPresets.ts index a43c21276..054b6f888 100644 --- a/src/hooks/useFilterPresets/useFilterPresets.ts +++ b/src/hooks/useFilterPresets/useFilterPresets.ts @@ -31,7 +31,7 @@ export const useFilterPresets = < getUrl, }: { params: T; - reset: () => void; + reset?: () => void; getUrl: () => string; storageUtils: StorageUtils; }): UseFilterPresets => { @@ -47,7 +47,7 @@ export const useFilterPresets = < : undefined; const onPresetChange = (index: number) => { - reset(); + reset?.(); const currentPresets = storageUtils.getFilterTabs(); const qs = new URLSearchParams(currentPresets[index - 1]?.data ?? ""); qs.append("activeTab", index.toString()); @@ -65,7 +65,7 @@ export const useFilterPresets = < } storageUtils.deleteFilterTab(presetIdToDelete); - reset(); + reset?.(); // When deleting the current tab, navigate to the All products if (presetIdToDelete === selectedPreset || !selectedPreset) { diff --git a/src/staff/components/StaffList/StaffList.tsx b/src/staff/components/StaffList/StaffList.tsx deleted file mode 100644 index 8d78d6959..000000000 --- a/src/staff/components/StaffList/StaffList.tsx +++ /dev/null @@ -1,169 +0,0 @@ -// @ts-strict-ignore -import ResponsiveTable from "@dashboard/components/ResponsiveTable"; -import Skeleton from "@dashboard/components/Skeleton"; -import TableCellHeader from "@dashboard/components/TableCellHeader"; -import { TablePaginationWithContext } from "@dashboard/components/TablePagination"; -import TableRowLink from "@dashboard/components/TableRowLink"; -import { UserAvatar } from "@dashboard/components/UserAvatar"; -import { StaffListQuery } from "@dashboard/graphql"; -import { commonStatusMessages } from "@dashboard/intl"; -import { - getUserInitials, - getUserName, - renderCollection, -} from "@dashboard/misc"; -import { - StaffListUrlSortField, - staffMemberDetailsUrl, -} from "@dashboard/staff/urls"; -import { ListProps, RelayToFlat, SortPage } from "@dashboard/types"; -import { getArrowDirection } from "@dashboard/utils/sort"; -import { - TableBody, - TableCell, - TableFooter, - TableHead, -} from "@material-ui/core"; -import { makeStyles } from "@saleor/macaw-ui"; -import { Box, Text } from "@saleor/macaw-ui/next"; -import clsx from "clsx"; -import React from "react"; -import { FormattedMessage, useIntl } from "react-intl"; - -const useStyles = makeStyles( - { - colEmail: { - width: 400, - }, - - tableRow: { - cursor: "pointer", - }, - wideColumn: { - width: "80%", - }, - }, - { name: "StaffList" }, -); - -interface StaffListProps extends ListProps, SortPage { - staffMembers: RelayToFlat; -} - -const numberOfColumns = 2; - -const StaffList: React.FC = props => { - const { - settings, - disabled, - onUpdateListSettings, - onSort, - sort, - staffMembers, - } = props; - - const classes = useStyles(props); - const intl = useIntl(); - - return ( - - - - - - - - onSort(StaffListUrlSortField.name)} - className={classes.wideColumn} - > - - - onSort(StaffListUrlSortField.email)} - > - - - - - - - - - - - {renderCollection( - staffMembers, - staffMember => ( - - - - - - {getUserName(staffMember) || } - - {staffMember?.isActive - ? intl.formatMessage(commonStatusMessages.active) - : intl.formatMessage(commonStatusMessages.notActive)} - - - - - - - {staffMember?.email} - - - - ), - () => ( - - - - - - ), - )} - - - ); -}; -StaffList.displayName = "StaffList"; -export default StaffList; diff --git a/src/staff/components/StaffList/index.ts b/src/staff/components/StaffList/index.ts deleted file mode 100644 index 524579391..000000000 --- a/src/staff/components/StaffList/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from "./StaffList"; -export * from "./StaffList"; diff --git a/src/staff/components/StaffListDatagrid/StaffListDatagrid.tsx b/src/staff/components/StaffListDatagrid/StaffListDatagrid.tsx new file mode 100644 index 000000000..ec4e471e5 --- /dev/null +++ b/src/staff/components/StaffListDatagrid/StaffListDatagrid.tsx @@ -0,0 +1,137 @@ +import { useColumns } from "@dashboard/components/Datagrid/ColumnPicker/useColumns"; +import Datagrid from "@dashboard/components/Datagrid/Datagrid"; +import { + DatagridChangeStateContext, + useDatagridChangeState, +} from "@dashboard/components/Datagrid/hooks/useDatagridChange"; +import { useEmptyColumn } from "@dashboard/components/Datagrid/hooks/useEmptyColumn"; +import { TablePaginationWithContext } from "@dashboard/components/TablePagination"; +import useNavigator from "@dashboard/hooks/useNavigator"; +import { StaffMember, StaffMembers } from "@dashboard/staff/types"; +import { + StaffListUrlSortField, + staffMemberDetailsUrl, +} from "@dashboard/staff/urls"; +import { ListProps, SortPage } from "@dashboard/types"; +import { Item } from "@glideapps/glide-data-grid"; +import { Box, useTheme } from "@saleor/macaw-ui/next"; +import React, { useCallback, useMemo } from "react"; +import { useIntl } from "react-intl"; + +import { + createGetCellContent, + staffMemebersListStaticColumnsAdapter, +} from "./datagrid"; +import { messages } from "./messages"; + +interface StaffListDatagridProps + extends ListProps, + SortPage { + staffMembers: StaffMembers; +} + +export const StaffListDatagrid = ({ + staffMembers, + settings, + sort, + disabled, + onSort, + onUpdateListSettings, +}: StaffListDatagridProps) => { + const datagridState = useDatagridChangeState(); + const navigate = useNavigator(); + const intl = useIntl(); + const { themeValues } = useTheme(); + + const emptyColumn = useEmptyColumn(); + const staffMemebersListStaticColumns = useMemo( + () => staffMemebersListStaticColumnsAdapter(intl, sort, emptyColumn), + [intl, sort, emptyColumn], + ); + + const onColumnChange = useCallback( + (picked: string[]) => { + if (onUpdateListSettings) { + onUpdateListSettings("columns", picked.filter(Boolean)); + } + }, + [onUpdateListSettings], + ); + + const { handlers, visibleColumns, recentlyAddedColumn } = useColumns({ + selectedColumns: settings?.columns ?? [], + staticColumns: staffMemebersListStaticColumns, + onSave: onColumnChange, + }); + + const getCellContent = useCallback( + createGetCellContent({ + staffMembers, + columns: visibleColumns, + intl, + currentTheme: themeValues, + }), + [staffMembers, intl, visibleColumns], + ); + + const handleRowClick = useCallback( + ([_, row]: Item) => { + const rowData: StaffMember = staffMembers[row]; + + if (rowData) { + navigate(staffMemberDetailsUrl(rowData?.id)); + } + }, + [staffMembers], + ); + + const handleRowAnchor = useCallback( + ([, row]: Item) => staffMemberDetailsUrl(staffMembers[row]?.id), + [staffMembers], + ); + const handleHeaderClick = useCallback( + (col: number) => { + const columnName = visibleColumns[col].id as StaffListUrlSortField; + + if (Object.values(StaffListUrlSortField).includes(columnName)) { + onSort(columnName); + } + }, + [visibleColumns, onSort], + ); + + return ( + + col > 1} + rows={staffMembers?.length ?? 0} + availableColumns={visibleColumns} + emptyText={intl.formatMessage(messages.empty)} + getCellContent={getCellContent} + getCellError={() => false} + selectionActions={() => null} + menuItems={() => []} + onRowClick={handleRowClick} + onHeaderClicked={handleHeaderClick} + rowAnchor={handleRowAnchor} + recentlyAddedColumn={recentlyAddedColumn} + /> + + + + + + ); +}; diff --git a/src/staff/components/StaffListDatagrid/datagrid.ts b/src/staff/components/StaffListDatagrid/datagrid.ts new file mode 100644 index 000000000..fa19976cb --- /dev/null +++ b/src/staff/components/StaffListDatagrid/datagrid.ts @@ -0,0 +1,104 @@ +import { PLACEHOLDER } from "@dashboard/components/Datagrid/const"; +import { + readonlyTextCell, + tagsCell, + thumbnailCell, +} from "@dashboard/components/Datagrid/customCells/cells"; +import { AvailableColumn } from "@dashboard/components/Datagrid/types"; +import { commonStatusMessages } from "@dashboard/intl"; +import { getStatusColor, getUserName } from "@dashboard/misc"; +import { StaffMember, StaffMembers } from "@dashboard/staff/types"; +import { StaffListUrlSortField } from "@dashboard/staff/urls"; +import { Sort } from "@dashboard/types"; +import { getColumnSortDirectionIcon } from "@dashboard/utils/columns/getColumnSortDirectionIcon"; +import { GridCell, Item } from "@glideapps/glide-data-grid"; +import { ThemeTokensValues } from "@saleor/macaw-ui/next"; +import { IntlShape } from "react-intl"; + +import { columnsMessages } from "./messages"; + +export const staffMemebersListStaticColumnsAdapter = ( + intl: IntlShape, + sort: Sort, + emptyColumn: AvailableColumn, +) => + [ + emptyColumn, + { + id: "name", + title: intl.formatMessage(columnsMessages.name), + width: 400, + }, + { + id: "status", + title: intl.formatMessage(columnsMessages.status), + width: 150, + }, + { + id: "email", + title: intl.formatMessage(columnsMessages.email), + width: 400, + }, + ].map(column => ({ + ...column, + icon: getColumnSortDirectionIcon(sort, column.id), + })); + +export const createGetCellContent = + ({ + staffMembers, + columns, + intl, + currentTheme, + }: { + staffMembers: StaffMembers; + columns: AvailableColumn[]; + intl: IntlShape; + currentTheme: ThemeTokensValues; + }) => + ([column, row]: Item): GridCell => { + const rowData: StaffMember | undefined = staffMembers[row]; + const columnId = columns[column]?.id; + + if (!columnId || !rowData) { + return readonlyTextCell(""); + } + + switch (columnId) { + case "name": + return thumbnailCell( + getUserName(rowData) ?? "", + rowData?.avatar?.url ?? "", + { + cursor: "pointer", + }, + ); + case "status": + const isActive = rowData?.isActive; + const status = isActive + ? intl.formatMessage(commonStatusMessages.active) + : intl.formatMessage(commonStatusMessages.notActive); + const statusColor = getStatusColor(isActive ? "success" : "error"); + + return tagsCell( + [ + { + tag: status, + color: + currentTheme.colors.background[ + statusColor as keyof typeof currentTheme.colors.background + ], + }, + ], + [status], + { + readonly: true, + allowOverlay: false, + }, + ); + case "email": + return readonlyTextCell(rowData?.email ?? PLACEHOLDER); + default: + return readonlyTextCell(""); + } + }; diff --git a/src/staff/components/StaffListDatagrid/index.ts b/src/staff/components/StaffListDatagrid/index.ts new file mode 100644 index 000000000..b353b4310 --- /dev/null +++ b/src/staff/components/StaffListDatagrid/index.ts @@ -0,0 +1 @@ +export * from "./StaffListDatagrid"; diff --git a/src/staff/components/StaffListDatagrid/messages.ts b/src/staff/components/StaffListDatagrid/messages.ts new file mode 100644 index 000000000..ae1603348 --- /dev/null +++ b/src/staff/components/StaffListDatagrid/messages.ts @@ -0,0 +1,24 @@ +import { defineMessages } from "react-intl"; + +export const columnsMessages = defineMessages({ + name: { + id: "W32xfN", + defaultMessage: "Name", + description: "staff member full name", + }, + email: { + id: "xxQxLE", + defaultMessage: "Email Address", + }, + status: { + id: "tzMNF3", + defaultMessage: "Status", + }, +}); + +export const messages = defineMessages({ + empty: { + id: "xJQX5t", + defaultMessage: "No staff members found", + }, +}); diff --git a/src/staff/components/StaffListPage/StaffListPage.stories.tsx b/src/staff/components/StaffListPage/StaffListPage.stories.tsx index 181c7194a..5f689c119 100644 --- a/src/staff/components/StaffListPage/StaffListPage.stories.tsx +++ b/src/staff/components/StaffListPage/StaffListPage.stories.tsx @@ -1,12 +1,12 @@ // @ts-strict-ignore import { filterPageProps, + filterPresetsProps, limits, limitsReached, pageListProps, searchPageProps, sortPageProps, - tabPageProps, } from "@dashboard/fixtures"; import { StaffMemberStatus } from "@dashboard/graphql"; import { staffMembers } from "@dashboard/staff/fixtures"; @@ -20,8 +20,8 @@ const props: StaffListPageProps = { ...pageListProps.default, ...searchPageProps, ...sortPageProps, - ...tabPageProps, ...filterPageProps, + ...filterPresetsProps, filterOpts: { status: { active: false, @@ -35,6 +35,10 @@ const props: StaffListPageProps = { sort: StaffListUrlSortField.name, }, staffMembers, + settings: { + rowNumber: 10, + columns: ["name", "email", "status"], + }, }; const meta: Meta = { diff --git a/src/staff/components/StaffListPage/StaffListPage.tsx b/src/staff/components/StaffListPage/StaffListPage.tsx index ee4b8c22b..50a314d09 100644 --- a/src/staff/components/StaffListPage/StaffListPage.tsx +++ b/src/staff/components/StaffListPage/StaffListPage.tsx @@ -1,27 +1,25 @@ -// @ts-strict-ignore -import { LimitsInfo } from "@dashboard/components/AppLayout/LimitsInfo"; +import { ListFilters } from "@dashboard/components/AppLayout/ListFilters"; import { TopNav } from "@dashboard/components/AppLayout/TopNav"; -import { Button } from "@dashboard/components/Button"; -import FilterBar from "@dashboard/components/FilterBar"; +import { FilterPresetsSelect } from "@dashboard/components/FilterPresetsSelect"; import { ListPageLayout } from "@dashboard/components/Layouts"; import LimitReachedAlert from "@dashboard/components/LimitReachedAlert"; import { configurationMenuUrl } from "@dashboard/configuration"; -import { RefreshLimitsQuery, StaffListQuery } from "@dashboard/graphql"; +import { RefreshLimitsQuery } from "@dashboard/graphql"; import { sectionNames } from "@dashboard/intl"; +import { StaffMembers } from "@dashboard/staff/types"; import { StaffListUrlSortField } from "@dashboard/staff/urls"; import { - FilterPageProps, + FilterPagePropsWithPresets, ListProps, - RelayToFlat, SortPage, - TabPageProps, } from "@dashboard/types"; import { hasLimits, isLimitReached } from "@dashboard/utils/limits"; import { Card } from "@material-ui/core"; -import React from "react"; +import { Box, Button, ChevronRightIcon } from "@saleor/macaw-ui/next"; +import React, { useState } from "react"; import { FormattedMessage, useIntl } from "react-intl"; -import StaffList from "../StaffList/StaffList"; +import { StaffListDatagrid } from "../StaffListDatagrid"; import { createFilterStructure, StaffFilterKeys, @@ -30,30 +28,33 @@ import { export interface StaffListPageProps extends ListProps, - FilterPageProps, - SortPage, - TabPageProps { - limits: RefreshLimitsQuery["shop"]["limits"]; - staffMembers: RelayToFlat; + FilterPagePropsWithPresets, + SortPage { + limits: RefreshLimitsQuery["shop"]["limits"] | undefined; + staffMembers: StaffMembers; onAdd: () => void; } const StaffListPage: React.FC = ({ - currentTab, filterOpts, initialSearch, limits, + currencySymbol, + filterPresets, + selectedFilterPreset, onAdd, - onAll, onFilterChange, onSearchChange, - onTabChange, - onTabDelete, - onTabSave, - tabs, + hasPresetsChanged, + onFilterPresetChange, + onFilterPresetDelete, + onFilterPresetPresetSave, + onFilterPresetUpdate, + onFilterPresetsAll, ...listProps }) => { const intl = useIntl(); + const [isFilterPresetOpen, setFilterPresetOpen] = useState(false); const structure = createFilterStructure(intl, filterOpts); const reachedLimit = isLimitReached(limits, "staffUsers"); @@ -63,35 +64,69 @@ const StaffListPage: React.FC = ({ - - {hasLimits(limits, "staffUsers") && ( - - )} + + + + + + + + + + + + {hasLimits(limits, "staffUsers") && ( + + {intl.formatMessage( + { + id: "9xlPgt", + defaultMessage: "{count}/{max} members", + description: "used staff users counter", + }, + { + count: limits?.currentUsage?.staffUsers ?? 0, + max: limits?.allowedUsage?.staffUsers ?? 0, + }, + )} + + )} {reachedLimit && ( = ({ )} - + currencySymbol={currencySymbol} initialSearch={initialSearch} - searchPlaceholder={intl.formatMessage({ - id: "aDbrOK", - defaultMessage: "Search Staff Member", - })} - tabs={tabs} - onAll={onAll} onFilterChange={onFilterChange} onSearchChange={onSearchChange} - onTabChange={onTabChange} - onTabDelete={onTabDelete} - onTabSave={onTabSave} + filterStructure={structure} + searchPlaceholder={intl.formatMessage({ + id: "o68j+t", + defaultMessage: "Search staff members...", + })} /> - + + ); diff --git a/src/staff/components/StaffListPage/filters.ts b/src/staff/components/StaffListPage/filters.ts index d50903b51..6e969d4e0 100644 --- a/src/staff/components/StaffListPage/filters.ts +++ b/src/staff/components/StaffListPage/filters.ts @@ -9,7 +9,7 @@ export enum StaffFilterKeys { } export interface StaffListFilterOpts { - status: FilterOpts; + status: FilterOpts; } const messages = defineMessages({ @@ -39,7 +39,7 @@ export function createFilterStructure( ...createOptionsField( StaffFilterKeys.status, intl.formatMessage(messages.status), - [opts.status.value], + [opts.status.value ?? ""], false, [ { diff --git a/src/staff/types.ts b/src/staff/types.ts new file mode 100644 index 000000000..a193a82d1 --- /dev/null +++ b/src/staff/types.ts @@ -0,0 +1,7 @@ +import { StaffListQuery } from "@dashboard/graphql"; +import { RelayToFlat } from "@dashboard/types"; + +export type StaffMembers = RelayToFlat< + NonNullable +>; +export type StaffMember = StaffMembers[number]; diff --git a/src/staff/views/StaffList/StaffList.tsx b/src/staff/views/StaffList/StaffList.tsx index 2fd89ffbd..5accd9d6d 100644 --- a/src/staff/views/StaffList/StaffList.tsx +++ b/src/staff/views/StaffList/StaffList.tsx @@ -1,15 +1,13 @@ -// @ts-strict-ignore import { newPasswordUrl } from "@dashboard/auth/urls"; import DeleteFilterTabDialog from "@dashboard/components/DeleteFilterTabDialog"; -import SaveFilterTabDialog, { - SaveFilterTabDialogFormData, -} from "@dashboard/components/SaveFilterTabDialog"; +import SaveFilterTabDialog from "@dashboard/components/SaveFilterTabDialog"; import { useShopLimitsQuery } from "@dashboard/components/Shop/queries"; import { DEFAULT_INITIAL_SEARCH_DATA } from "@dashboard/config"; import { useStaffListQuery, useStaffMemberAddMutation, } from "@dashboard/graphql"; +import { useFilterPresets } from "@dashboard/hooks/useFilterPresets"; import useListSettings from "@dashboard/hooks/useListSettings"; import useNavigator from "@dashboard/hooks/useNavigator"; import useNotifier from "@dashboard/hooks/useNotifier"; @@ -19,7 +17,6 @@ import usePaginator, { PaginatorContext, } from "@dashboard/hooks/usePaginator"; import { commonMessages } from "@dashboard/intl"; -import { getStringOrPlaceholder } from "@dashboard/misc"; import usePermissionGroupSearch from "@dashboard/searches/usePermissionGroupSearch"; import { ListViews } from "@dashboard/types"; import createDialogActionHandlers from "@dashboard/utils/handlers/dialogActionHandlers"; @@ -43,14 +40,10 @@ import { staffMemberDetailsUrl, } from "../../urls"; import { - deleteFilterTab, - getActiveFilters, getFilterOpts, getFilterQueryParam, - getFiltersCurrentTab, - getFilterTabs, getFilterVariables, - saveFilterTab, + storageUtils, } from "./filters"; import { getSortQueryVariables } from "./sort"; @@ -89,27 +82,39 @@ export const StaffList: React.FC = ({ params }) => { const [addStaffMember, addStaffMemberData] = useStaffMemberAddMutation({ onCompleted: data => { - if (data.staffCreate.errors.length === 0) { + if (data?.staffCreate?.errors?.length === 0) { notify({ status: "success", text: intl.formatMessage(commonMessages.savedChanges), }); - navigate(staffMemberDetailsUrl(data.staffCreate.user.id)); + navigate(staffMemberDetailsUrl(data?.staffCreate?.user?.id ?? "")); } }, }); const paginationValues = usePaginator({ - pageInfo: staffQueryData?.staffUsers.pageInfo, + pageInfo: staffQueryData?.staffUsers?.pageInfo, paginationState, queryString: params, }); const handleSort = createSortHandler(navigate, staffListUrl, params); - const tabs = getFilterTabs(); - - const currentTab = getFiltersCurrentTab(params, tabs); + const { + hasPresetsChanged, + onPresetChange, + onPresetDelete, + onPresetSave, + onPresetUpdate, + selectedPreset, + presets, + getPresetNameToDelete, + setPresetIdToDelete, + } = useFilterPresets({ + getUrl: staffListUrl, + params, + storageUtils, + }); const [changeFilters, resetFilters, handleSearchChange] = createFilterHandlers({ @@ -117,6 +122,7 @@ export const StaffList: React.FC = ({ params }) => { getFilterQueryParam, navigate, params, + keepActiveTab: true, }); const [openModal, closeModal] = createDialogActionHandlers< @@ -124,25 +130,6 @@ export const StaffList: React.FC = ({ params }) => { StaffListUrlQueryParams >(navigate, staffListUrl, params); - const handleTabChange = (tab: number) => { - navigate( - staffListUrl({ - activeTab: tab.toString(), - ...getFilterTabs()[tab - 1].data, - }), - ); - }; - - const handleTabDelete = () => { - deleteFilterTab(currentTab); - navigate(staffListUrl()); - }; - - const handleTabSave = (data: SaveFilterTabDialogFormData) => { - saveFilterTab(data.name, getActiveFilters(params)); - handleTabChange(tabs.length + 1); - }; - const { loadMore: loadMorePermissionGroups, search: searchPermissionGroups, @@ -171,55 +158,65 @@ export const StaffList: React.FC = ({ params }) => { return ( openModal("delete-search")} - onTabSave={() => openModal("save-search")} - tabs={tabs.map(tab => tab.name)} + onFilterPresetsAll={resetFilters} + onFilterPresetDelete={id => { + setPresetIdToDelete(id); + openModal("delete-search"); + }} + selectedFilterPreset={selectedPreset} + onFilterPresetChange={onPresetChange} + onFilterPresetUpdate={onPresetUpdate} + hasPresetsChanged={hasPresetsChanged} + onFilterPresetPresetSave={() => openModal("save-search")} + filterPresets={presets.map(preset => preset.name)} disabled={loading || addStaffMemberData.loading || limitOpts.loading} - limits={limitOpts.data?.shop.limits} + limits={limitOpts.data?.shop?.limits} settings={settings} sort={getSortParams(params)} - staffMembers={mapEdgesToItems(staffQueryData?.staffUsers)} + staffMembers={mapEdgesToItems(staffQueryData?.staffUsers) ?? []} onAdd={() => openModal("add")} onUpdateListSettings={updateListSettings} onSort={handleSort} /> + + + ); diff --git a/src/staff/views/StaffList/filters.ts b/src/staff/views/StaffList/filters.ts index 6df7b1830..4bd358688 100644 --- a/src/staff/views/StaffList/filters.ts +++ b/src/staff/views/StaffList/filters.ts @@ -1,10 +1,9 @@ -// @ts-strict-ignore import { FilterElement, FilterElementRegular, } from "@dashboard/components/Filter"; import { StaffMemberStatus, StaffUserInput } from "@dashboard/graphql"; -import { findValueInEnum, maybe } from "@dashboard/misc"; +import { findValueInEnum } from "@dashboard/misc"; import { StaffFilterKeys, StaffListFilterOpts, @@ -28,8 +27,10 @@ export function getFilterOpts( ): StaffListFilterOpts { return { status: { - active: maybe(() => params.status !== undefined, false), - value: maybe(() => findValueInEnum(params.status, StaffMemberStatus)), + active: params?.status !== undefined ?? false, + value: params?.status + ? findValueInEnum(params.status, StaffMemberStatus) + : null, }, }; } @@ -60,8 +61,7 @@ export function getFilterQueryParam( } } -export const { deleteFilterTab, getFilterTabs, saveFilterTab } = - createFilterTabUtils(STAFF_FILTERS_KEY); +export const storageUtils = createFilterTabUtils(STAFF_FILTERS_KEY); export const { areFiltersApplied, getActiveFilters, getFiltersCurrentTab } = createFilterUtils( diff --git a/src/staff/views/StaffList/sort.ts b/src/staff/views/StaffList/sort.ts index db946efe8..c36122d81 100644 --- a/src/staff/views/StaffList/sort.ts +++ b/src/staff/views/StaffList/sort.ts @@ -1,9 +1,10 @@ -// @ts-strict-ignore import { UserSortField } from "@dashboard/graphql"; import { StaffListUrlSortField } from "@dashboard/staff/urls"; import { createGetSortQueryVariables } from "@dashboard/utils/sort"; -export function getSortQueryField(sort: StaffListUrlSortField): UserSortField { +export function getSortQueryField( + sort: StaffListUrlSortField, +): UserSortField | undefined { switch (sort) { case StaffListUrlSortField.name: return UserSortField.LAST_NAME; diff --git a/src/utils/sort.ts b/src/utils/sort.ts index 9423199f0..f8ac52314 100644 --- a/src/utils/sort.ts +++ b/src/utils/sort.ts @@ -68,7 +68,7 @@ interface SortingInput { } type GetSortQueryField = ( sort: TUrlField, -) => TSortField; +) => TSortField | undefined; type GetSortQueryVariables< TSortField extends string, TParams extends Record,