Introduce datagrid on staff members list view (#4044)
This commit is contained in:
parent
4c43976270
commit
f14ba5bcfd
19 changed files with 458 additions and 323 deletions
5
.changeset/cold-tigers-argue.md
Normal file
5
.changeset/cold-tigers-argue.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"saleor-dashboard": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Introduce datagrid on staff members list view
|
|
@ -12,11 +12,7 @@ import {
|
||||||
STAFF_MEMBERS_LIST_SELECTORS,
|
STAFF_MEMBERS_LIST_SELECTORS,
|
||||||
} from "../elements/";
|
} from "../elements/";
|
||||||
import { LOGIN_SELECTORS } from "../elements/account/login-selectors";
|
import { LOGIN_SELECTORS } from "../elements/account/login-selectors";
|
||||||
import {
|
import { MESSAGES, TEST_ADMIN_USER, urlList } from "../fixtures";
|
||||||
MESSAGES,
|
|
||||||
TEST_ADMIN_USER,
|
|
||||||
urlList,
|
|
||||||
} from "../fixtures";
|
|
||||||
import { userDetailsUrl } from "../fixtures/urlList";
|
import { userDetailsUrl } from "../fixtures/urlList";
|
||||||
import {
|
import {
|
||||||
activatePlugin,
|
activatePlugin,
|
||||||
|
@ -28,6 +24,7 @@ import {
|
||||||
getMailActivationLinkForUserAndSubject,
|
getMailActivationLinkForUserAndSubject,
|
||||||
inviteStaffMemberWithFirstPermission,
|
inviteStaffMemberWithFirstPermission,
|
||||||
} from "../support/api/utils/";
|
} from "../support/api/utils/";
|
||||||
|
import { ensureCanvasStatic } from "../support/customCommands/sharedElementsOperations/canvas";
|
||||||
import {
|
import {
|
||||||
expectMainMenuAvailableSections,
|
expectMainMenuAvailableSections,
|
||||||
expectWelcomeMessageIncludes,
|
expectWelcomeMessageIncludes,
|
||||||
|
@ -72,10 +69,11 @@ describe("Staff members", () => {
|
||||||
const firstName = faker.name.firstName();
|
const firstName = faker.name.firstName();
|
||||||
const emailInvite = `${startsWith}${firstName}@example.com`;
|
const emailInvite = `${startsWith}${firstName}@example.com`;
|
||||||
|
|
||||||
cy.visit(urlList.staffMembers)
|
cy.visit(urlList.staffMembers);
|
||||||
.expectSkeletonIsVisible()
|
ensureCanvasStatic(SHARED_ELEMENTS.dataGridTable);
|
||||||
.get(STAFF_MEMBERS_LIST_SELECTORS.inviteStaffMemberButton)
|
cy.get(STAFF_MEMBERS_LIST_SELECTORS.inviteStaffMemberButton).click({
|
||||||
.click({ force: true });
|
force: true,
|
||||||
|
});
|
||||||
fillUpUserDetailsAndAddFirstPermission(firstName, lastName, emailInvite);
|
fillUpUserDetailsAndAddFirstPermission(firstName, lastName, emailInvite);
|
||||||
getMailActivationLinkForUser(emailInvite).then(urlLink => {
|
getMailActivationLinkForUser(emailInvite).then(urlLink => {
|
||||||
cy.clearSessionData().visit(urlLink);
|
cy.clearSessionData().visit(urlLink);
|
||||||
|
@ -185,10 +183,11 @@ describe("Staff members", () => {
|
||||||
() => {
|
() => {
|
||||||
const firstName = faker.name.firstName();
|
const firstName = faker.name.firstName();
|
||||||
const emailInvite = TEST_ADMIN_USER.email;
|
const emailInvite = TEST_ADMIN_USER.email;
|
||||||
cy.visit(urlList.staffMembers)
|
cy.visit(urlList.staffMembers);
|
||||||
.expectSkeletonIsVisible()
|
ensureCanvasStatic(SHARED_ELEMENTS.dataGridTable);
|
||||||
.get(STAFF_MEMBERS_LIST_SELECTORS.inviteStaffMemberButton)
|
cy.get(STAFF_MEMBERS_LIST_SELECTORS.inviteStaffMemberButton).click({
|
||||||
.click({ force: true });
|
force: true,
|
||||||
|
});
|
||||||
fillUpOnlyUserDetails(firstName, lastName, emailInvite);
|
fillUpOnlyUserDetails(firstName, lastName, emailInvite);
|
||||||
cy.get(INVITE_STAFF_MEMBER_FORM_SELECTORS.emailValidationMessage).should(
|
cy.get(INVITE_STAFF_MEMBER_FORM_SELECTORS.emailValidationMessage).should(
|
||||||
"be.visible",
|
"be.visible",
|
||||||
|
|
|
@ -3673,6 +3673,10 @@
|
||||||
"context": "header",
|
"context": "header",
|
||||||
"string": "{webhookName} Details"
|
"string": "{webhookName} Details"
|
||||||
},
|
},
|
||||||
|
"OTDo9I": {
|
||||||
|
"context": "tab name",
|
||||||
|
"string": "All staff members"
|
||||||
|
},
|
||||||
"OVOU1z": {
|
"OVOU1z": {
|
||||||
"context": "section header",
|
"context": "section header",
|
||||||
"string": "Metadata"
|
"string": "Metadata"
|
||||||
|
@ -5133,10 +5137,6 @@
|
||||||
"context": "no address is set in draft order",
|
"context": "no address is set in draft order",
|
||||||
"string": "Not set"
|
"string": "Not set"
|
||||||
},
|
},
|
||||||
"YJ4TXc": {
|
|
||||||
"context": "tab name",
|
|
||||||
"string": "All Staff Members"
|
|
||||||
},
|
|
||||||
"YKyNm9": {
|
"YKyNm9": {
|
||||||
"context": "label",
|
"context": "label",
|
||||||
"string": "Gift Card"
|
"string": "Gift Card"
|
||||||
|
@ -5386,9 +5386,6 @@
|
||||||
"context": "subheader",
|
"context": "subheader",
|
||||||
"string": "Here is some information we gathered about your store"
|
"string": "Here is some information we gathered about your store"
|
||||||
},
|
},
|
||||||
"aDbrOK": {
|
|
||||||
"string": "Search Staff Member"
|
|
||||||
},
|
|
||||||
"aEc9Ar": {
|
"aEc9Ar": {
|
||||||
"context": "gift card history message",
|
"context": "gift card history message",
|
||||||
"string": "Gift card balance was reset by {resetBy}"
|
"string": "Gift card balance was reset by {resetBy}"
|
||||||
|
@ -7258,6 +7255,9 @@
|
||||||
"context": "attributes, section header",
|
"context": "attributes, section header",
|
||||||
"string": "Variant Selection Attributes"
|
"string": "Variant Selection Attributes"
|
||||||
},
|
},
|
||||||
|
"o68j+t": {
|
||||||
|
"string": "Search staff members..."
|
||||||
|
},
|
||||||
"o8S0Ac": {
|
"o8S0Ac": {
|
||||||
"context": "usage limit uses left caption",
|
"context": "usage limit uses left caption",
|
||||||
"string": "Uses left"
|
"string": "Uses left"
|
||||||
|
|
|
@ -121,6 +121,7 @@ export const defaultListSettings: AppListViewSettings = {
|
||||||
},
|
},
|
||||||
[ListViews.STAFF_MEMBERS_LIST]: {
|
[ListViews.STAFF_MEMBERS_LIST]: {
|
||||||
rowNumber: PAGINATE_BY,
|
rowNumber: PAGINATE_BY,
|
||||||
|
columns: ["name", "email", "status"],
|
||||||
},
|
},
|
||||||
[ListViews.PERMISSION_GROUP_LIST]: {
|
[ListViews.PERMISSION_GROUP_LIST]: {
|
||||||
rowNumber: PAGINATE_BY,
|
rowNumber: PAGINATE_BY,
|
||||||
|
|
|
@ -31,7 +31,7 @@ export const useFilterPresets = <
|
||||||
getUrl,
|
getUrl,
|
||||||
}: {
|
}: {
|
||||||
params: T;
|
params: T;
|
||||||
reset: () => void;
|
reset?: () => void;
|
||||||
getUrl: () => string;
|
getUrl: () => string;
|
||||||
storageUtils: StorageUtils<string>;
|
storageUtils: StorageUtils<string>;
|
||||||
}): UseFilterPresets => {
|
}): UseFilterPresets => {
|
||||||
|
@ -47,7 +47,7 @@ export const useFilterPresets = <
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const onPresetChange = (index: number) => {
|
const onPresetChange = (index: number) => {
|
||||||
reset();
|
reset?.();
|
||||||
const currentPresets = storageUtils.getFilterTabs();
|
const currentPresets = storageUtils.getFilterTabs();
|
||||||
const qs = new URLSearchParams(currentPresets[index - 1]?.data ?? "");
|
const qs = new URLSearchParams(currentPresets[index - 1]?.data ?? "");
|
||||||
qs.append("activeTab", index.toString());
|
qs.append("activeTab", index.toString());
|
||||||
|
@ -65,7 +65,7 @@ export const useFilterPresets = <
|
||||||
}
|
}
|
||||||
|
|
||||||
storageUtils.deleteFilterTab(presetIdToDelete);
|
storageUtils.deleteFilterTab(presetIdToDelete);
|
||||||
reset();
|
reset?.();
|
||||||
|
|
||||||
// When deleting the current tab, navigate to the All products
|
// When deleting the current tab, navigate to the All products
|
||||||
if (presetIdToDelete === selectedPreset || !selectedPreset) {
|
if (presetIdToDelete === selectedPreset || !selectedPreset) {
|
||||||
|
|
|
@ -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<StaffListUrlSortField> {
|
|
||||||
staffMembers: RelayToFlat<StaffListQuery["staffUsers"]>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const numberOfColumns = 2;
|
|
||||||
|
|
||||||
const StaffList: React.FC<StaffListProps> = props => {
|
|
||||||
const {
|
|
||||||
settings,
|
|
||||||
disabled,
|
|
||||||
onUpdateListSettings,
|
|
||||||
onSort,
|
|
||||||
sort,
|
|
||||||
staffMembers,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const classes = useStyles(props);
|
|
||||||
const intl = useIntl();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ResponsiveTable>
|
|
||||||
<colgroup>
|
|
||||||
<col />
|
|
||||||
<col className={classes.colEmail} />
|
|
||||||
</colgroup>
|
|
||||||
<TableHead>
|
|
||||||
<TableRowLink>
|
|
||||||
<TableCellHeader
|
|
||||||
direction={
|
|
||||||
sort.sort === StaffListUrlSortField.name
|
|
||||||
? getArrowDirection(sort.asc)
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
arrowPosition="right"
|
|
||||||
onClick={() => onSort(StaffListUrlSortField.name)}
|
|
||||||
className={classes.wideColumn}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id="W32xfN"
|
|
||||||
defaultMessage="Name"
|
|
||||||
description="staff member full name"
|
|
||||||
/>
|
|
||||||
</TableCellHeader>
|
|
||||||
<TableCellHeader
|
|
||||||
direction={
|
|
||||||
sort.sort === StaffListUrlSortField.email
|
|
||||||
? getArrowDirection(sort.asc)
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
onClick={() => onSort(StaffListUrlSortField.email)}
|
|
||||||
>
|
|
||||||
<FormattedMessage id="xxQxLE" defaultMessage="Email Address" />
|
|
||||||
</TableCellHeader>
|
|
||||||
</TableRowLink>
|
|
||||||
</TableHead>
|
|
||||||
<TableFooter>
|
|
||||||
<TableRowLink>
|
|
||||||
<TablePaginationWithContext
|
|
||||||
colSpan={numberOfColumns}
|
|
||||||
disabled={disabled}
|
|
||||||
settings={settings}
|
|
||||||
onUpdateListSettings={onUpdateListSettings}
|
|
||||||
/>
|
|
||||||
</TableRowLink>
|
|
||||||
</TableFooter>
|
|
||||||
<TableBody>
|
|
||||||
{renderCollection(
|
|
||||||
staffMembers,
|
|
||||||
staffMember => (
|
|
||||||
<TableRowLink
|
|
||||||
className={clsx({
|
|
||||||
[classes.tableRow]: !!staffMember,
|
|
||||||
})}
|
|
||||||
hover={!!staffMember}
|
|
||||||
href={staffMember && staffMemberDetailsUrl(staffMember.id)}
|
|
||||||
key={staffMember ? staffMember.id : "skeleton"}
|
|
||||||
>
|
|
||||||
<TableCell>
|
|
||||||
<Box display="flex" alignItems="center" gap={2}>
|
|
||||||
<UserAvatar
|
|
||||||
url={staffMember?.avatar?.url}
|
|
||||||
initials={getUserInitials(staffMember)}
|
|
||||||
/>
|
|
||||||
<Box display="flex" flexDirection="column">
|
|
||||||
<Text>{getUserName(staffMember) || <Skeleton />}</Text>
|
|
||||||
<Text
|
|
||||||
variant="caption"
|
|
||||||
data-test-id="staffStatusText"
|
|
||||||
color="textNeutralSubdued"
|
|
||||||
>
|
|
||||||
{staffMember?.isActive
|
|
||||||
? intl.formatMessage(commonStatusMessages.active)
|
|
||||||
: intl.formatMessage(commonStatusMessages.notActive)}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<Text size="small" data-test-id="user-mail">
|
|
||||||
{staffMember?.email}
|
|
||||||
</Text>
|
|
||||||
</TableCell>
|
|
||||||
</TableRowLink>
|
|
||||||
),
|
|
||||||
() => (
|
|
||||||
<TableRowLink>
|
|
||||||
<TableCell colSpan={numberOfColumns}>
|
|
||||||
<FormattedMessage
|
|
||||||
id="xJQX5t"
|
|
||||||
defaultMessage="No staff members found"
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRowLink>
|
|
||||||
),
|
|
||||||
)}
|
|
||||||
</TableBody>
|
|
||||||
</ResponsiveTable>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
StaffList.displayName = "StaffList";
|
|
||||||
export default StaffList;
|
|
|
@ -1,2 +0,0 @@
|
||||||
export { default } from "./StaffList";
|
|
||||||
export * from "./StaffList";
|
|
137
src/staff/components/StaffListDatagrid/StaffListDatagrid.tsx
Normal file
137
src/staff/components/StaffListDatagrid/StaffListDatagrid.tsx
Normal file
|
@ -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<StaffListUrlSortField> {
|
||||||
|
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 (
|
||||||
|
<DatagridChangeStateContext.Provider value={datagridState}>
|
||||||
|
<Datagrid
|
||||||
|
readonly
|
||||||
|
loading={disabled}
|
||||||
|
rowMarkers="none"
|
||||||
|
columnSelect="single"
|
||||||
|
hasRowHover={true}
|
||||||
|
onColumnMoved={handlers.onMove}
|
||||||
|
onColumnResize={handlers.onResize}
|
||||||
|
verticalBorder={col => 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}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box paddingX={6}>
|
||||||
|
<TablePaginationWithContext
|
||||||
|
component="div"
|
||||||
|
settings={settings}
|
||||||
|
disabled={disabled}
|
||||||
|
onUpdateListSettings={onUpdateListSettings}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</DatagridChangeStateContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
104
src/staff/components/StaffListDatagrid/datagrid.ts
Normal file
104
src/staff/components/StaffListDatagrid/datagrid.ts
Normal file
|
@ -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<StaffListUrlSortField>,
|
||||||
|
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("");
|
||||||
|
}
|
||||||
|
};
|
1
src/staff/components/StaffListDatagrid/index.ts
Normal file
1
src/staff/components/StaffListDatagrid/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * from "./StaffListDatagrid";
|
24
src/staff/components/StaffListDatagrid/messages.ts
Normal file
24
src/staff/components/StaffListDatagrid/messages.ts
Normal file
|
@ -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",
|
||||||
|
},
|
||||||
|
});
|
|
@ -1,12 +1,12 @@
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import {
|
import {
|
||||||
filterPageProps,
|
filterPageProps,
|
||||||
|
filterPresetsProps,
|
||||||
limits,
|
limits,
|
||||||
limitsReached,
|
limitsReached,
|
||||||
pageListProps,
|
pageListProps,
|
||||||
searchPageProps,
|
searchPageProps,
|
||||||
sortPageProps,
|
sortPageProps,
|
||||||
tabPageProps,
|
|
||||||
} from "@dashboard/fixtures";
|
} from "@dashboard/fixtures";
|
||||||
import { StaffMemberStatus } from "@dashboard/graphql";
|
import { StaffMemberStatus } from "@dashboard/graphql";
|
||||||
import { staffMembers } from "@dashboard/staff/fixtures";
|
import { staffMembers } from "@dashboard/staff/fixtures";
|
||||||
|
@ -20,8 +20,8 @@ const props: StaffListPageProps = {
|
||||||
...pageListProps.default,
|
...pageListProps.default,
|
||||||
...searchPageProps,
|
...searchPageProps,
|
||||||
...sortPageProps,
|
...sortPageProps,
|
||||||
...tabPageProps,
|
|
||||||
...filterPageProps,
|
...filterPageProps,
|
||||||
|
...filterPresetsProps,
|
||||||
filterOpts: {
|
filterOpts: {
|
||||||
status: {
|
status: {
|
||||||
active: false,
|
active: false,
|
||||||
|
@ -35,6 +35,10 @@ const props: StaffListPageProps = {
|
||||||
sort: StaffListUrlSortField.name,
|
sort: StaffListUrlSortField.name,
|
||||||
},
|
},
|
||||||
staffMembers,
|
staffMembers,
|
||||||
|
settings: {
|
||||||
|
rowNumber: 10,
|
||||||
|
columns: ["name", "email", "status"],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const meta: Meta<typeof StaffListPage> = {
|
const meta: Meta<typeof StaffListPage> = {
|
||||||
|
|
|
@ -1,27 +1,25 @@
|
||||||
// @ts-strict-ignore
|
import { ListFilters } from "@dashboard/components/AppLayout/ListFilters";
|
||||||
import { LimitsInfo } from "@dashboard/components/AppLayout/LimitsInfo";
|
|
||||||
import { TopNav } from "@dashboard/components/AppLayout/TopNav";
|
import { TopNav } from "@dashboard/components/AppLayout/TopNav";
|
||||||
import { Button } from "@dashboard/components/Button";
|
import { FilterPresetsSelect } from "@dashboard/components/FilterPresetsSelect";
|
||||||
import FilterBar from "@dashboard/components/FilterBar";
|
|
||||||
import { ListPageLayout } from "@dashboard/components/Layouts";
|
import { ListPageLayout } from "@dashboard/components/Layouts";
|
||||||
import LimitReachedAlert from "@dashboard/components/LimitReachedAlert";
|
import LimitReachedAlert from "@dashboard/components/LimitReachedAlert";
|
||||||
import { configurationMenuUrl } from "@dashboard/configuration";
|
import { configurationMenuUrl } from "@dashboard/configuration";
|
||||||
import { RefreshLimitsQuery, StaffListQuery } from "@dashboard/graphql";
|
import { RefreshLimitsQuery } from "@dashboard/graphql";
|
||||||
import { sectionNames } from "@dashboard/intl";
|
import { sectionNames } from "@dashboard/intl";
|
||||||
|
import { StaffMembers } from "@dashboard/staff/types";
|
||||||
import { StaffListUrlSortField } from "@dashboard/staff/urls";
|
import { StaffListUrlSortField } from "@dashboard/staff/urls";
|
||||||
import {
|
import {
|
||||||
FilterPageProps,
|
FilterPagePropsWithPresets,
|
||||||
ListProps,
|
ListProps,
|
||||||
RelayToFlat,
|
|
||||||
SortPage,
|
SortPage,
|
||||||
TabPageProps,
|
|
||||||
} from "@dashboard/types";
|
} from "@dashboard/types";
|
||||||
import { hasLimits, isLimitReached } from "@dashboard/utils/limits";
|
import { hasLimits, isLimitReached } from "@dashboard/utils/limits";
|
||||||
import { Card } from "@material-ui/core";
|
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 { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
import StaffList from "../StaffList/StaffList";
|
import { StaffListDatagrid } from "../StaffListDatagrid";
|
||||||
import {
|
import {
|
||||||
createFilterStructure,
|
createFilterStructure,
|
||||||
StaffFilterKeys,
|
StaffFilterKeys,
|
||||||
|
@ -30,30 +28,33 @@ import {
|
||||||
|
|
||||||
export interface StaffListPageProps
|
export interface StaffListPageProps
|
||||||
extends ListProps,
|
extends ListProps,
|
||||||
FilterPageProps<StaffFilterKeys, StaffListFilterOpts>,
|
FilterPagePropsWithPresets<StaffFilterKeys, StaffListFilterOpts>,
|
||||||
SortPage<StaffListUrlSortField>,
|
SortPage<StaffListUrlSortField> {
|
||||||
TabPageProps {
|
limits: RefreshLimitsQuery["shop"]["limits"] | undefined;
|
||||||
limits: RefreshLimitsQuery["shop"]["limits"];
|
staffMembers: StaffMembers;
|
||||||
staffMembers: RelayToFlat<StaffListQuery["staffUsers"]>;
|
|
||||||
onAdd: () => void;
|
onAdd: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StaffListPage: React.FC<StaffListPageProps> = ({
|
const StaffListPage: React.FC<StaffListPageProps> = ({
|
||||||
currentTab,
|
|
||||||
filterOpts,
|
filterOpts,
|
||||||
initialSearch,
|
initialSearch,
|
||||||
limits,
|
limits,
|
||||||
|
currencySymbol,
|
||||||
|
filterPresets,
|
||||||
|
selectedFilterPreset,
|
||||||
onAdd,
|
onAdd,
|
||||||
onAll,
|
|
||||||
onFilterChange,
|
onFilterChange,
|
||||||
onSearchChange,
|
onSearchChange,
|
||||||
onTabChange,
|
hasPresetsChanged,
|
||||||
onTabDelete,
|
onFilterPresetChange,
|
||||||
onTabSave,
|
onFilterPresetDelete,
|
||||||
tabs,
|
onFilterPresetPresetSave,
|
||||||
|
onFilterPresetUpdate,
|
||||||
|
onFilterPresetsAll,
|
||||||
...listProps
|
...listProps
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const [isFilterPresetOpen, setFilterPresetOpen] = useState(false);
|
||||||
|
|
||||||
const structure = createFilterStructure(intl, filterOpts);
|
const structure = createFilterStructure(intl, filterOpts);
|
||||||
const reachedLimit = isLimitReached(limits, "staffUsers");
|
const reachedLimit = isLimitReached(limits, "staffUsers");
|
||||||
|
@ -63,35 +64,69 @@ const StaffListPage: React.FC<StaffListPageProps> = ({
|
||||||
<TopNav
|
<TopNav
|
||||||
href={configurationMenuUrl}
|
href={configurationMenuUrl}
|
||||||
title={intl.formatMessage(sectionNames.staff)}
|
title={intl.formatMessage(sectionNames.staff)}
|
||||||
|
isAlignToRight={false}
|
||||||
|
withoutBorder
|
||||||
>
|
>
|
||||||
<Button
|
<Box
|
||||||
data-test-id="invite-staff-member"
|
__flex={1}
|
||||||
disabled={reachedLimit}
|
display="flex"
|
||||||
variant="primary"
|
justifyContent="space-between"
|
||||||
onClick={onAdd}
|
alignItems="center"
|
||||||
>
|
>
|
||||||
<FormattedMessage
|
<Box display="flex">
|
||||||
id="4JcNaA"
|
<Box marginX={3} display="flex" alignItems="center">
|
||||||
defaultMessage="Invite staff member"
|
<ChevronRightIcon />
|
||||||
description="button"
|
</Box>
|
||||||
/>
|
|
||||||
</Button>
|
<FilterPresetsSelect
|
||||||
{hasLimits(limits, "staffUsers") && (
|
presetsChanged={hasPresetsChanged()}
|
||||||
<LimitsInfo
|
onSelect={onFilterPresetChange}
|
||||||
text={intl.formatMessage(
|
onRemove={onFilterPresetDelete}
|
||||||
{
|
onUpdate={onFilterPresetUpdate}
|
||||||
id: "9xlPgt",
|
savedPresets={filterPresets}
|
||||||
defaultMessage: "{count}/{max} members",
|
activePreset={selectedFilterPreset}
|
||||||
description: "used staff users counter",
|
onSelectAll={onFilterPresetsAll}
|
||||||
},
|
onSave={onFilterPresetPresetSave}
|
||||||
{
|
isOpen={isFilterPresetOpen}
|
||||||
count: limits.currentUsage.staffUsers,
|
onOpenChange={setFilterPresetOpen}
|
||||||
max: limits.allowedUsage.staffUsers,
|
selectAllLabel={intl.formatMessage({
|
||||||
},
|
id: "OTDo9I",
|
||||||
)}
|
defaultMessage: "All staff members",
|
||||||
/>
|
description: "tab name",
|
||||||
)}
|
})}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Button
|
||||||
|
data-test-id="invite-staff-member"
|
||||||
|
disabled={reachedLimit}
|
||||||
|
variant="primary"
|
||||||
|
onClick={onAdd}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="4JcNaA"
|
||||||
|
defaultMessage="Invite staff member"
|
||||||
|
description="button"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
</TopNav>
|
</TopNav>
|
||||||
|
{hasLimits(limits, "staffUsers") && (
|
||||||
|
<Box gridColumn="8" marginLeft={6} marginBottom={reachedLimit ? 0 : 3}>
|
||||||
|
{intl.formatMessage(
|
||||||
|
{
|
||||||
|
id: "9xlPgt",
|
||||||
|
defaultMessage: "{count}/{max} members",
|
||||||
|
description: "used staff users counter",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
count: limits?.currentUsage?.staffUsers ?? 0,
|
||||||
|
max: limits?.allowedUsage?.staffUsers ?? 0,
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
{reachedLimit && (
|
{reachedLimit && (
|
||||||
<LimitReachedAlert
|
<LimitReachedAlert
|
||||||
title={intl.formatMessage({
|
title={intl.formatMessage({
|
||||||
|
@ -107,28 +142,19 @@ const StaffListPage: React.FC<StaffListPageProps> = ({
|
||||||
</LimitReachedAlert>
|
</LimitReachedAlert>
|
||||||
)}
|
)}
|
||||||
<Card>
|
<Card>
|
||||||
<FilterBar
|
<ListFilters<StaffFilterKeys>
|
||||||
allTabLabel={intl.formatMessage({
|
currencySymbol={currencySymbol}
|
||||||
id: "YJ4TXc",
|
|
||||||
defaultMessage: "All Staff Members",
|
|
||||||
description: "tab name",
|
|
||||||
})}
|
|
||||||
currentTab={currentTab}
|
|
||||||
filterStructure={structure}
|
|
||||||
initialSearch={initialSearch}
|
initialSearch={initialSearch}
|
||||||
searchPlaceholder={intl.formatMessage({
|
|
||||||
id: "aDbrOK",
|
|
||||||
defaultMessage: "Search Staff Member",
|
|
||||||
})}
|
|
||||||
tabs={tabs}
|
|
||||||
onAll={onAll}
|
|
||||||
onFilterChange={onFilterChange}
|
onFilterChange={onFilterChange}
|
||||||
onSearchChange={onSearchChange}
|
onSearchChange={onSearchChange}
|
||||||
onTabChange={onTabChange}
|
filterStructure={structure}
|
||||||
onTabDelete={onTabDelete}
|
searchPlaceholder={intl.formatMessage({
|
||||||
onTabSave={onTabSave}
|
id: "o68j+t",
|
||||||
|
defaultMessage: "Search staff members...",
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
<StaffList {...listProps} />
|
|
||||||
|
<StaffListDatagrid {...listProps} />
|
||||||
</Card>
|
</Card>
|
||||||
</ListPageLayout>
|
</ListPageLayout>
|
||||||
);
|
);
|
||||||
|
|
|
@ -9,7 +9,7 @@ export enum StaffFilterKeys {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StaffListFilterOpts {
|
export interface StaffListFilterOpts {
|
||||||
status: FilterOpts<StaffMemberStatus>;
|
status: FilterOpts<StaffMemberStatus | null>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
|
@ -39,7 +39,7 @@ export function createFilterStructure(
|
||||||
...createOptionsField(
|
...createOptionsField(
|
||||||
StaffFilterKeys.status,
|
StaffFilterKeys.status,
|
||||||
intl.formatMessage(messages.status),
|
intl.formatMessage(messages.status),
|
||||||
[opts.status.value],
|
[opts.status.value ?? ""],
|
||||||
false,
|
false,
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
|
7
src/staff/types.ts
Normal file
7
src/staff/types.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { StaffListQuery } from "@dashboard/graphql";
|
||||||
|
import { RelayToFlat } from "@dashboard/types";
|
||||||
|
|
||||||
|
export type StaffMembers = RelayToFlat<
|
||||||
|
NonNullable<StaffListQuery["staffUsers"]>
|
||||||
|
>;
|
||||||
|
export type StaffMember = StaffMembers[number];
|
|
@ -1,15 +1,13 @@
|
||||||
// @ts-strict-ignore
|
|
||||||
import { newPasswordUrl } from "@dashboard/auth/urls";
|
import { newPasswordUrl } from "@dashboard/auth/urls";
|
||||||
import DeleteFilterTabDialog from "@dashboard/components/DeleteFilterTabDialog";
|
import DeleteFilterTabDialog from "@dashboard/components/DeleteFilterTabDialog";
|
||||||
import SaveFilterTabDialog, {
|
import SaveFilterTabDialog from "@dashboard/components/SaveFilterTabDialog";
|
||||||
SaveFilterTabDialogFormData,
|
|
||||||
} from "@dashboard/components/SaveFilterTabDialog";
|
|
||||||
import { useShopLimitsQuery } from "@dashboard/components/Shop/queries";
|
import { useShopLimitsQuery } from "@dashboard/components/Shop/queries";
|
||||||
import { DEFAULT_INITIAL_SEARCH_DATA } from "@dashboard/config";
|
import { DEFAULT_INITIAL_SEARCH_DATA } from "@dashboard/config";
|
||||||
import {
|
import {
|
||||||
useStaffListQuery,
|
useStaffListQuery,
|
||||||
useStaffMemberAddMutation,
|
useStaffMemberAddMutation,
|
||||||
} from "@dashboard/graphql";
|
} from "@dashboard/graphql";
|
||||||
|
import { useFilterPresets } from "@dashboard/hooks/useFilterPresets";
|
||||||
import useListSettings from "@dashboard/hooks/useListSettings";
|
import useListSettings from "@dashboard/hooks/useListSettings";
|
||||||
import useNavigator from "@dashboard/hooks/useNavigator";
|
import useNavigator from "@dashboard/hooks/useNavigator";
|
||||||
import useNotifier from "@dashboard/hooks/useNotifier";
|
import useNotifier from "@dashboard/hooks/useNotifier";
|
||||||
|
@ -19,7 +17,6 @@ import usePaginator, {
|
||||||
PaginatorContext,
|
PaginatorContext,
|
||||||
} from "@dashboard/hooks/usePaginator";
|
} from "@dashboard/hooks/usePaginator";
|
||||||
import { commonMessages } from "@dashboard/intl";
|
import { commonMessages } from "@dashboard/intl";
|
||||||
import { getStringOrPlaceholder } from "@dashboard/misc";
|
|
||||||
import usePermissionGroupSearch from "@dashboard/searches/usePermissionGroupSearch";
|
import usePermissionGroupSearch from "@dashboard/searches/usePermissionGroupSearch";
|
||||||
import { ListViews } from "@dashboard/types";
|
import { ListViews } from "@dashboard/types";
|
||||||
import createDialogActionHandlers from "@dashboard/utils/handlers/dialogActionHandlers";
|
import createDialogActionHandlers from "@dashboard/utils/handlers/dialogActionHandlers";
|
||||||
|
@ -43,14 +40,10 @@ import {
|
||||||
staffMemberDetailsUrl,
|
staffMemberDetailsUrl,
|
||||||
} from "../../urls";
|
} from "../../urls";
|
||||||
import {
|
import {
|
||||||
deleteFilterTab,
|
|
||||||
getActiveFilters,
|
|
||||||
getFilterOpts,
|
getFilterOpts,
|
||||||
getFilterQueryParam,
|
getFilterQueryParam,
|
||||||
getFiltersCurrentTab,
|
|
||||||
getFilterTabs,
|
|
||||||
getFilterVariables,
|
getFilterVariables,
|
||||||
saveFilterTab,
|
storageUtils,
|
||||||
} from "./filters";
|
} from "./filters";
|
||||||
import { getSortQueryVariables } from "./sort";
|
import { getSortQueryVariables } from "./sort";
|
||||||
|
|
||||||
|
@ -89,27 +82,39 @@ export const StaffList: React.FC<StaffListProps> = ({ params }) => {
|
||||||
|
|
||||||
const [addStaffMember, addStaffMemberData] = useStaffMemberAddMutation({
|
const [addStaffMember, addStaffMemberData] = useStaffMemberAddMutation({
|
||||||
onCompleted: data => {
|
onCompleted: data => {
|
||||||
if (data.staffCreate.errors.length === 0) {
|
if (data?.staffCreate?.errors?.length === 0) {
|
||||||
notify({
|
notify({
|
||||||
status: "success",
|
status: "success",
|
||||||
text: intl.formatMessage(commonMessages.savedChanges),
|
text: intl.formatMessage(commonMessages.savedChanges),
|
||||||
});
|
});
|
||||||
navigate(staffMemberDetailsUrl(data.staffCreate.user.id));
|
navigate(staffMemberDetailsUrl(data?.staffCreate?.user?.id ?? ""));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const paginationValues = usePaginator({
|
const paginationValues = usePaginator({
|
||||||
pageInfo: staffQueryData?.staffUsers.pageInfo,
|
pageInfo: staffQueryData?.staffUsers?.pageInfo,
|
||||||
paginationState,
|
paginationState,
|
||||||
queryString: params,
|
queryString: params,
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSort = createSortHandler(navigate, staffListUrl, params);
|
const handleSort = createSortHandler(navigate, staffListUrl, params);
|
||||||
|
|
||||||
const tabs = getFilterTabs();
|
const {
|
||||||
|
hasPresetsChanged,
|
||||||
const currentTab = getFiltersCurrentTab(params, tabs);
|
onPresetChange,
|
||||||
|
onPresetDelete,
|
||||||
|
onPresetSave,
|
||||||
|
onPresetUpdate,
|
||||||
|
selectedPreset,
|
||||||
|
presets,
|
||||||
|
getPresetNameToDelete,
|
||||||
|
setPresetIdToDelete,
|
||||||
|
} = useFilterPresets({
|
||||||
|
getUrl: staffListUrl,
|
||||||
|
params,
|
||||||
|
storageUtils,
|
||||||
|
});
|
||||||
|
|
||||||
const [changeFilters, resetFilters, handleSearchChange] =
|
const [changeFilters, resetFilters, handleSearchChange] =
|
||||||
createFilterHandlers({
|
createFilterHandlers({
|
||||||
|
@ -117,6 +122,7 @@ export const StaffList: React.FC<StaffListProps> = ({ params }) => {
|
||||||
getFilterQueryParam,
|
getFilterQueryParam,
|
||||||
navigate,
|
navigate,
|
||||||
params,
|
params,
|
||||||
|
keepActiveTab: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [openModal, closeModal] = createDialogActionHandlers<
|
const [openModal, closeModal] = createDialogActionHandlers<
|
||||||
|
@ -124,25 +130,6 @@ export const StaffList: React.FC<StaffListProps> = ({ params }) => {
|
||||||
StaffListUrlQueryParams
|
StaffListUrlQueryParams
|
||||||
>(navigate, staffListUrl, params);
|
>(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 {
|
const {
|
||||||
loadMore: loadMorePermissionGroups,
|
loadMore: loadMorePermissionGroups,
|
||||||
search: searchPermissionGroups,
|
search: searchPermissionGroups,
|
||||||
|
@ -171,55 +158,65 @@ export const StaffList: React.FC<StaffListProps> = ({ params }) => {
|
||||||
return (
|
return (
|
||||||
<PaginatorContext.Provider value={paginationValues}>
|
<PaginatorContext.Provider value={paginationValues}>
|
||||||
<StaffListPage
|
<StaffListPage
|
||||||
currentTab={currentTab}
|
|
||||||
filterOpts={getFilterOpts(params)}
|
filterOpts={getFilterOpts(params)}
|
||||||
initialSearch={params.query || ""}
|
initialSearch={params.query || ""}
|
||||||
onSearchChange={handleSearchChange}
|
onSearchChange={handleSearchChange}
|
||||||
onFilterChange={changeFilters}
|
onFilterChange={changeFilters}
|
||||||
onAll={resetFilters}
|
onFilterPresetsAll={resetFilters}
|
||||||
onTabChange={handleTabChange}
|
onFilterPresetDelete={id => {
|
||||||
onTabDelete={() => openModal("delete-search")}
|
setPresetIdToDelete(id);
|
||||||
onTabSave={() => openModal("save-search")}
|
openModal("delete-search");
|
||||||
tabs={tabs.map(tab => tab.name)}
|
}}
|
||||||
|
selectedFilterPreset={selectedPreset}
|
||||||
|
onFilterPresetChange={onPresetChange}
|
||||||
|
onFilterPresetUpdate={onPresetUpdate}
|
||||||
|
hasPresetsChanged={hasPresetsChanged}
|
||||||
|
onFilterPresetPresetSave={() => openModal("save-search")}
|
||||||
|
filterPresets={presets.map(preset => preset.name)}
|
||||||
disabled={loading || addStaffMemberData.loading || limitOpts.loading}
|
disabled={loading || addStaffMemberData.loading || limitOpts.loading}
|
||||||
limits={limitOpts.data?.shop.limits}
|
limits={limitOpts.data?.shop?.limits}
|
||||||
settings={settings}
|
settings={settings}
|
||||||
sort={getSortParams(params)}
|
sort={getSortParams(params)}
|
||||||
staffMembers={mapEdgesToItems(staffQueryData?.staffUsers)}
|
staffMembers={mapEdgesToItems(staffQueryData?.staffUsers) ?? []}
|
||||||
onAdd={() => openModal("add")}
|
onAdd={() => openModal("add")}
|
||||||
onUpdateListSettings={updateListSettings}
|
onUpdateListSettings={updateListSettings}
|
||||||
onSort={handleSort}
|
onSort={handleSort}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<StaffAddMemberDialog
|
<StaffAddMemberDialog
|
||||||
availablePermissionGroups={mapEdgesToItems(
|
availablePermissionGroups={
|
||||||
searchPermissionGroupsOpts?.data?.search,
|
mapEdgesToItems(searchPermissionGroupsOpts?.data?.search) ?? []
|
||||||
)}
|
}
|
||||||
confirmButtonState={addStaffMemberData.status}
|
confirmButtonState={addStaffMemberData.status}
|
||||||
initialSearch=""
|
initialSearch=""
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
errors={addStaffMemberData.data?.staffCreate.errors || []}
|
errors={addStaffMemberData.data?.staffCreate?.errors || []}
|
||||||
open={params.action === "add"}
|
open={params.action === "add"}
|
||||||
onClose={closeModal}
|
onClose={closeModal}
|
||||||
onConfirm={handleStaffMemberAdd}
|
onConfirm={handleStaffMemberAdd}
|
||||||
fetchMorePermissionGroups={{
|
fetchMorePermissionGroups={{
|
||||||
hasMore: searchPermissionGroupsOpts.data?.search.pageInfo.hasNextPage,
|
hasMore:
|
||||||
|
searchPermissionGroupsOpts.data?.search?.pageInfo?.hasNextPage ??
|
||||||
|
false,
|
||||||
loading: searchPermissionGroupsOpts.loading,
|
loading: searchPermissionGroupsOpts.loading,
|
||||||
onFetchMore: loadMorePermissionGroups,
|
onFetchMore: loadMorePermissionGroups,
|
||||||
}}
|
}}
|
||||||
onSearchChange={searchPermissionGroups}
|
onSearchChange={searchPermissionGroups}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SaveFilterTabDialog
|
<SaveFilterTabDialog
|
||||||
open={params.action === "save-search"}
|
open={params.action === "save-search"}
|
||||||
confirmButtonState="default"
|
confirmButtonState="default"
|
||||||
onClose={closeModal}
|
onClose={closeModal}
|
||||||
onSubmit={handleTabSave}
|
onSubmit={onPresetSave}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DeleteFilterTabDialog
|
<DeleteFilterTabDialog
|
||||||
open={params.action === "delete-search"}
|
open={params.action === "delete-search"}
|
||||||
confirmButtonState="default"
|
confirmButtonState="default"
|
||||||
onClose={closeModal}
|
onClose={closeModal}
|
||||||
onSubmit={handleTabDelete}
|
onSubmit={onPresetDelete}
|
||||||
tabName={getStringOrPlaceholder(tabs[currentTab - 1]?.name)}
|
tabName={getPresetNameToDelete()}
|
||||||
/>
|
/>
|
||||||
</PaginatorContext.Provider>
|
</PaginatorContext.Provider>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
// @ts-strict-ignore
|
|
||||||
import {
|
import {
|
||||||
FilterElement,
|
FilterElement,
|
||||||
FilterElementRegular,
|
FilterElementRegular,
|
||||||
} from "@dashboard/components/Filter";
|
} from "@dashboard/components/Filter";
|
||||||
import { StaffMemberStatus, StaffUserInput } from "@dashboard/graphql";
|
import { StaffMemberStatus, StaffUserInput } from "@dashboard/graphql";
|
||||||
import { findValueInEnum, maybe } from "@dashboard/misc";
|
import { findValueInEnum } from "@dashboard/misc";
|
||||||
import {
|
import {
|
||||||
StaffFilterKeys,
|
StaffFilterKeys,
|
||||||
StaffListFilterOpts,
|
StaffListFilterOpts,
|
||||||
|
@ -28,8 +27,10 @@ export function getFilterOpts(
|
||||||
): StaffListFilterOpts {
|
): StaffListFilterOpts {
|
||||||
return {
|
return {
|
||||||
status: {
|
status: {
|
||||||
active: maybe(() => params.status !== undefined, false),
|
active: params?.status !== undefined ?? false,
|
||||||
value: maybe(() => findValueInEnum(params.status, StaffMemberStatus)),
|
value: params?.status
|
||||||
|
? findValueInEnum(params.status, StaffMemberStatus)
|
||||||
|
: null,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -60,8 +61,7 @@ export function getFilterQueryParam(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const { deleteFilterTab, getFilterTabs, saveFilterTab } =
|
export const storageUtils = createFilterTabUtils<string>(STAFF_FILTERS_KEY);
|
||||||
createFilterTabUtils<StaffListUrlFilters>(STAFF_FILTERS_KEY);
|
|
||||||
|
|
||||||
export const { areFiltersApplied, getActiveFilters, getFiltersCurrentTab } =
|
export const { areFiltersApplied, getActiveFilters, getFiltersCurrentTab } =
|
||||||
createFilterUtils<StaffListUrlQueryParams, StaffListUrlFilters>(
|
createFilterUtils<StaffListUrlQueryParams, StaffListUrlFilters>(
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
// @ts-strict-ignore
|
|
||||||
import { UserSortField } from "@dashboard/graphql";
|
import { UserSortField } from "@dashboard/graphql";
|
||||||
import { StaffListUrlSortField } from "@dashboard/staff/urls";
|
import { StaffListUrlSortField } from "@dashboard/staff/urls";
|
||||||
import { createGetSortQueryVariables } from "@dashboard/utils/sort";
|
import { createGetSortQueryVariables } from "@dashboard/utils/sort";
|
||||||
|
|
||||||
export function getSortQueryField(sort: StaffListUrlSortField): UserSortField {
|
export function getSortQueryField(
|
||||||
|
sort: StaffListUrlSortField,
|
||||||
|
): UserSortField | undefined {
|
||||||
switch (sort) {
|
switch (sort) {
|
||||||
case StaffListUrlSortField.name:
|
case StaffListUrlSortField.name:
|
||||||
return UserSortField.LAST_NAME;
|
return UserSortField.LAST_NAME;
|
||||||
|
|
|
@ -68,7 +68,7 @@ interface SortingInput<T extends string> {
|
||||||
}
|
}
|
||||||
type GetSortQueryField<TUrlField extends string, TSortField extends string> = (
|
type GetSortQueryField<TUrlField extends string, TSortField extends string> = (
|
||||||
sort: TUrlField,
|
sort: TUrlField,
|
||||||
) => TSortField;
|
) => TSortField | undefined;
|
||||||
type GetSortQueryVariables<
|
type GetSortQueryVariables<
|
||||||
TSortField extends string,
|
TSortField extends string,
|
||||||
TParams extends Record<any, any>,
|
TParams extends Record<any, any>,
|
||||||
|
|
Loading…
Reference in a new issue