Add search to staff list

This commit is contained in:
dominik-zeglen 2019-09-12 12:46:23 +02:00
parent 796d1fc0e0
commit 3974b23b15
9 changed files with 301 additions and 126 deletions

View file

@ -1,4 +1,3 @@
import Card from "@material-ui/core/Card";
import {
createStyles,
Theme,
@ -84,108 +83,102 @@ const StaffList = withStyles(styles, { name: "StaffList" })(
const intl = useIntl();
return (
<Card>
<Table>
<TableHead>
<TableRow>
<TableCell className={classes.wideColumn}>
<FormattedMessage
defaultMessage="Name"
description="staff member full name"
/>
</TableCell>
<TableCell>
<FormattedMessage defaultMessage="Email Address" />
</TableCell>
</TableRow>
</TableHead>
<TableFooter>
<TableRow>
<TablePagination
colSpan={3}
settings={settings}
hasNextPage={
pageInfo && !disabled ? pageInfo.hasNextPage : undefined
}
onNextPage={onNextPage}
onUpdateListSettings={onUpdateListSettings}
hasPreviousPage={
pageInfo && !disabled ? pageInfo.hasPreviousPage : undefined
}
onPreviousPage={onPreviousPage}
<Table>
<TableHead>
<TableRow>
<TableCell className={classes.wideColumn}>
<FormattedMessage
defaultMessage="Name"
description="staff member full name"
/>
</TableRow>
</TableFooter>
<TableBody>
{renderCollection(
staffMembers,
staffMember => (
<TableRow
className={classNames({
[classes.tableRow]: !!staffMember
})}
hover={!!staffMember}
onClick={
!!staffMember ? onRowClick(staffMember.id) : undefined
}
key={staffMember ? staffMember.id : "skeleton"}
>
<TableCell>
<div className={classes.avatar}>
{maybe(() => staffMember.avatar.url) ? (
<img
className={classes.avatarImage}
src={maybe(() => staffMember.avatar.url)}
/>
) : (
<div className={classes.avatarDefault}>
<Typography>
{getUserInitials(staffMember)}
</Typography>
</div>
)}
</div>
<Typography>
{getUserName(staffMember) || <Skeleton />}
</Typography>
<Typography
variant={"caption"}
className={classes.statusText}
>
{maybe<React.ReactNode>(
() =>
staffMember.isActive
? intl.formatMessage({
defaultMessage: "Active",
description: "staff member status"
})
: intl.formatMessage({
defaultMessage: "Inactive",
description: "staff member status"
}),
<Skeleton />
)}
</Typography>
</TableCell>
<TableCell>
</TableCell>
<TableCell>
<FormattedMessage defaultMessage="Email Address" />
</TableCell>
</TableRow>
</TableHead>
<TableFooter>
<TableRow>
<TablePagination
colSpan={3}
settings={settings}
hasNextPage={
pageInfo && !disabled ? pageInfo.hasNextPage : undefined
}
onNextPage={onNextPage}
onUpdateListSettings={onUpdateListSettings}
hasPreviousPage={
pageInfo && !disabled ? pageInfo.hasPreviousPage : undefined
}
onPreviousPage={onPreviousPage}
/>
</TableRow>
</TableFooter>
<TableBody>
{renderCollection(
staffMembers,
staffMember => (
<TableRow
className={classNames({
[classes.tableRow]: !!staffMember
})}
hover={!!staffMember}
onClick={!!staffMember ? onRowClick(staffMember.id) : undefined}
key={staffMember ? staffMember.id : "skeleton"}
>
<TableCell>
<div className={classes.avatar}>
{maybe(() => staffMember.avatar.url) ? (
<img
className={classes.avatarImage}
src={maybe(() => staffMember.avatar.url)}
/>
) : (
<div className={classes.avatarDefault}>
<Typography>{getUserInitials(staffMember)}</Typography>
</div>
)}
</div>
<Typography>
{getUserName(staffMember) || <Skeleton />}
</Typography>
<Typography
variant={"caption"}
className={classes.statusText}
>
{maybe<React.ReactNode>(
() => staffMember.email,
() =>
staffMember.isActive
? intl.formatMessage({
defaultMessage: "Active",
description: "staff member status"
})
: intl.formatMessage({
defaultMessage: "Inactive",
description: "staff member status"
}),
<Skeleton />
)}
</TableCell>
</TableRow>
),
() => (
<TableRow>
<TableCell colSpan={3}>
<FormattedMessage defaultMessage="No staff members found" />
</TableCell>
</TableRow>
)
)}
</TableBody>
</Table>
</Card>
</Typography>
</TableCell>
<TableCell>
{maybe<React.ReactNode>(
() => staffMember.email,
<Skeleton />
)}
</TableCell>
</TableRow>
),
() => (
<TableRow>
<TableCell colSpan={3}>
<FormattedMessage defaultMessage="No staff members found" />
</TableCell>
</TableRow>
)
)}
</TableBody>
</Table>
);
}
);

View file

@ -1,26 +1,37 @@
import Button from "@material-ui/core/Button";
import Card from "@material-ui/core/Card";
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import AppHeader from "@saleor/components/AppHeader";
import { Container } from "@saleor/components/Container";
import PageHeader from "@saleor/components/PageHeader";
import SearchBar from "@saleor/components/SearchBar";
import { sectionNames } from "@saleor/intl";
import { ListProps } from "@saleor/types";
import { ListProps, SearchPageProps, TabPageProps } from "@saleor/types";
import { StaffList_staffUsers_edges_node } from "../../types/StaffList";
import StaffList from "../StaffList/StaffList";
export interface StaffListPageProps extends ListProps {
export interface StaffListPageProps
extends ListProps,
SearchPageProps,
TabPageProps {
staffMembers: StaffList_staffUsers_edges_node[];
onAdd: () => void;
onBack: () => void;
}
const StaffListPage: React.StatelessComponent<StaffListPageProps> = ({
disabled,
currentTab,
initialSearch,
onAdd,
onAll,
onBack,
onSearchChange,
onTabChange,
onTabDelete,
onTabSave,
tabs,
...listProps
}) => {
const intl = useIntl();
@ -31,19 +42,33 @@ const StaffListPage: React.StatelessComponent<StaffListPageProps> = ({
{intl.formatMessage(sectionNames.configuration)}
</AppHeader>
<PageHeader title={intl.formatMessage(sectionNames.staff)}>
<Button
color="primary"
disabled={disabled}
variant="contained"
onClick={onAdd}
>
<Button color="primary" variant="contained" onClick={onAdd}>
<FormattedMessage
defaultMessage="Invite staff member"
description="button"
/>
</Button>
</PageHeader>
<StaffList disabled={disabled} {...listProps} />
<Card>
<SearchBar
allTabLabel={intl.formatMessage({
defaultMessage: "All Staff Members",
description: "tab name"
})}
currentTab={currentTab}
initialSearch={initialSearch}
searchPlaceholder={intl.formatMessage({
defaultMessage: "Search Staff Member"
})}
tabs={tabs}
onAll={onAll}
onSearchChange={onSearchChange}
onTabChange={onTabChange}
onTabDelete={onTabDelete}
onTabSave={onTabSave}
/>
<StaffList {...listProps} />
</Card>
</Container>
);
};

View file

@ -30,8 +30,20 @@ export const staffMemberDetailsFragment = gql`
`;
const staffList = gql`
${staffMemberFragment}
query StaffList($first: Int, $after: String, $last: Int, $before: String) {
staffUsers(before: $before, after: $after, first: $first, last: $last) {
query StaffList(
$first: Int
$after: String
$last: Int
$before: String
$filter: StaffUserInput
) {
staffUsers(
before: $before
after: $after
first: $first
last: $last
filter: $filter
) {
edges {
cursor
node {

View file

@ -2,7 +2,7 @@
/* eslint-disable */
// This file was automatically generated and should not be edited.
import { PermissionEnum } from "./../../types/globalTypes";
import { StaffUserInput, PermissionEnum } from "./../../types/globalTypes";
// ====================================================
// GraphQL query operation: StaffList
@ -64,4 +64,5 @@ export interface StaffListVariables {
after?: string | null;
last?: number | null;
before?: string | null;
filter?: StaffUserInput | null;
}

View file

@ -1,15 +1,28 @@
import { stringify as stringifyQs } from "qs";
import urlJoin from "url-join";
import { BulkAction, Dialog, Pagination } from "../types";
import {
ActiveTab,
BulkAction,
Dialog,
Filters,
Pagination,
TabActionDialog
} from "../types";
const staffSection = "/staff/";
export const staffListPath = staffSection;
export type StaffListUrlDialog = "add" | "remove";
export type StaffListUrlQueryParams = BulkAction &
export enum StaffListUrlFiltersEnum {
query = "query"
}
export type StaffListUrlFilters = Filters<StaffListUrlFiltersEnum>;
export type StaffListUrlDialog = "add" | "remove" | TabActionDialog;
export type StaffListUrlQueryParams = ActiveTab &
BulkAction &
Dialog<StaffListUrlDialog> &
Pagination;
Pagination &
StaffListUrlFilters;
export const staffListUrl = (params?: StaffListUrlQueryParams) =>
staffListPath + "?" + stringifyQs(params);

View file

@ -8,22 +8,36 @@ import usePaginator, {
} from "@saleor/hooks/usePaginator";
import { useIntl } from "react-intl";
import DeleteFilterTabDialog from "@saleor/components/DeleteFilterTabDialog";
import SaveFilterTabDialog, {
SaveFilterTabDialogFormData
} from "@saleor/components/SaveFilterTabDialog";
import { configurationMenuUrl } from "@saleor/configuration";
import { commonMessages } from "@saleor/intl";
import { getMutationState, maybe } from "@saleor/misc";
import { ListViews } from "@saleor/types";
import StaffAddMemberDialog, {
FormData as AddStaffMemberForm
} from "../components/StaffAddMemberDialog";
import StaffListPage from "../components/StaffListPage";
import { TypedStaffMemberAddMutation } from "../mutations";
import { TypedStaffListQuery } from "../queries";
import { StaffMemberAdd } from "../types/StaffMemberAdd";
} from "../../components/StaffAddMemberDialog";
import StaffListPage from "../../components/StaffListPage";
import { TypedStaffMemberAddMutation } from "../../mutations";
import { TypedStaffListQuery } from "../../queries";
import { StaffMemberAdd } from "../../types/StaffMemberAdd";
import {
staffListUrl,
StaffListUrlDialog,
StaffListUrlFilters,
StaffListUrlQueryParams,
staffMemberDetailsUrl
} from "../urls";
} from "../../urls";
import {
areFiltersApplied,
deleteFilterTab,
getActiveFilters,
getFilterTabs,
getFilterVariables,
saveFilterTab
} from "./filter";
interface StaffListProps {
params: StaffListUrlQueryParams;
@ -40,19 +54,72 @@ export const StaffList: React.StatelessComponent<StaffListProps> = ({
);
const intl = useIntl();
const tabs = getFilterTabs();
const currentTab =
params.activeTab === undefined
? areFiltersApplied(params)
? tabs.length + 1
: 0
: parseInt(params.activeTab, 0);
const changeFilterField = (filter: StaffListUrlFilters) =>
navigate(
staffListUrl({
...getActiveFilters(params),
...filter,
activeTab: undefined
})
);
const closeModal = () =>
navigate(
staffListUrl({
...params,
action: undefined,
ids: undefined
}),
true
})
);
const openModal = (action: StaffListUrlDialog, ids?: string[]) =>
navigate(
staffListUrl({
...params,
action,
ids
})
);
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 paginationState = createPaginationState(settings.rowNumber, params);
const queryVariables = React.useMemo(
() => ({
...paginationState,
filter: getFilterVariables(params)
}),
[params]
);
return (
<TypedStaffListQuery displayLoader variables={paginationState}>
<TypedStaffListQuery displayLoader variables={queryVariables}>
{({ data, loading }) => {
const handleStaffMemberAddSuccess = (data: StaffMemberAdd) => {
if (data.staffCreate.errors.length === 0) {
@ -97,6 +164,14 @@ export const StaffList: React.StatelessComponent<StaffListProps> = ({
return (
<>
<StaffListPage
currentTab={currentTab}
initialSearch={params.query || ""}
onSearchChange={query => changeFilterField({ query })}
onAll={() => navigate(staffListUrl())}
onTabChange={handleTabChange}
onTabDelete={() => openModal("delete-search")}
onTabSave={() => openModal("save-search")}
tabs={tabs.map(tab => tab.name)}
disabled={loading || addStaffMemberData.loading}
settings={settings}
pageInfo={pageInfo}
@ -126,6 +201,19 @@ export const StaffList: React.StatelessComponent<StaffListProps> = ({
onClose={closeModal}
onConfirm={handleStaffMemberAdd}
/>
<SaveFilterTabDialog
open={params.action === "save-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleTabSave}
/>
<DeleteFilterTabDialog
open={params.action === "delete-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleTabDelete}
tabName={maybe(() => tabs[currentTab - 1].name, "...")}
/>
</>
);
}}

View file

@ -0,0 +1,31 @@
import { StaffUserInput } from "@saleor/types/globalTypes";
import {
createFilterTabUtils,
createFilterUtils
} from "../../../utils/filters";
import {
StaffListUrlFilters,
StaffListUrlFiltersEnum,
StaffListUrlQueryParams
} from "../../urls";
export const STAFF_FILTERS_KEY = "staffFilters";
export function getFilterVariables(
params: StaffListUrlFilters
): StaffUserInput {
return {
search: params.query
};
}
export const {
deleteFilterTab,
getFilterTabs,
saveFilterTab
} = createFilterTabUtils<StaffListUrlFilters>(STAFF_FILTERS_KEY);
export const { areFiltersApplied, getActiveFilters } = createFilterUtils<
StaffListUrlQueryParams,
StaffListUrlFilters
>(StaffListUrlFiltersEnum);

View file

@ -0,0 +1,2 @@
export { default } from "./StaffList";
export * from "./StaffList";

View file

@ -197,6 +197,11 @@ export enum ShippingMethodTypeEnum {
WEIGHT = "WEIGHT",
}
export enum StaffMemberStatus {
ACTIVE = "ACTIVE",
DEACTIVATED = "DEACTIVATED",
}
export enum StockAvailability {
IN_STOCK = "IN_STOCK",
OUT_OF_STOCK = "OUT_OF_STOCK",
@ -659,6 +664,11 @@ export interface StaffInput {
permissions?: (PermissionEnum | null)[] | null;
}
export interface StaffUserInput {
status?: StaffMemberStatus | null;
search?: string | null;
}
export interface TranslationInput {
seoTitle?: string | null;
seoDescription?: string | null;