Add warehouse list view

This commit is contained in:
dominik-zeglen 2020-01-30 12:46:35 +01:00
parent e7f6bd1e77
commit 414f6d7a3d
25 changed files with 954 additions and 10 deletions

View file

@ -21,6 +21,7 @@ import { staffListUrl } from "@saleor/staff/urls";
import { countryListUrl } from "@saleor/taxes/urls"; import { countryListUrl } from "@saleor/taxes/urls";
import { languageListUrl } from "@saleor/translations/urls"; import { languageListUrl } from "@saleor/translations/urls";
import { webhookListUrl } from "@saleor/webhooks/urls"; import { webhookListUrl } from "@saleor/webhooks/urls";
import { warehouseListUrl } from "@saleor/warehouses/urls";
import { QuickSearchActionInput } from "../../types"; import { QuickSearchActionInput } from "../../types";
interface View { interface View {
@ -116,6 +117,10 @@ function searchInViews(
{ {
label: intl.formatMessage(sectionNames.webhooks), label: intl.formatMessage(sectionNames.webhooks),
url: webhookListUrl() url: webhookListUrl()
},
{
label: intl.formatMessage(sectionNames.warehouses),
url: warehouseListUrl()
} }
]; ];

View file

@ -34,6 +34,7 @@ export interface AppListViewSettings {
[ListViews.STAFF_MEMBERS_LIST]: ListSettings; [ListViews.STAFF_MEMBERS_LIST]: ListSettings;
[ListViews.PERMISSION_GROUP_LIST]: ListSettings; [ListViews.PERMISSION_GROUP_LIST]: ListSettings;
[ListViews.VOUCHER_LIST]: ListSettings; [ListViews.VOUCHER_LIST]: ListSettings;
[ListViews.WAREHOUSE_LIST]: ListSettings;
[ListViews.WEBHOOK_LIST]: ListSettings; [ListViews.WEBHOOK_LIST]: ListSettings;
} }
export const defaultListSettings: AppListViewSettings = { export const defaultListSettings: AppListViewSettings = {
@ -80,6 +81,9 @@ export const defaultListSettings: AppListViewSettings = {
[ListViews.VOUCHER_LIST]: { [ListViews.VOUCHER_LIST]: {
rowNumber: PAGINATE_BY rowNumber: PAGINATE_BY
}, },
[ListViews.WAREHOUSE_LIST]: {
rowNumber: PAGINATE_BY
},
[ListViews.WEBHOOK_LIST]: { [ListViews.WEBHOOK_LIST]: {
rowNumber: PAGINATE_BY rowNumber: PAGINATE_BY
} }

View file

@ -31,6 +31,8 @@ import { permissionGroupListUrl } from "@saleor/permissionGroups/urls";
import { taxSection } from "@saleor/taxes/urls"; import { taxSection } from "@saleor/taxes/urls";
import { PermissionEnum } from "@saleor/types/globalTypes"; import { PermissionEnum } from "@saleor/types/globalTypes";
import { webhookListUrl } from "@saleor/webhooks/urls"; import { webhookListUrl } from "@saleor/webhooks/urls";
import Warehouses from "@saleor/icons/Warehouses";
import { warehouseSection } from "@saleor/warehouses/urls";
import ConfigurationPage, { MenuSection } from "./ConfigurationPage"; import ConfigurationPage, { MenuSection } from "./ConfigurationPage";
export function createConfigurationMenu(intl: IntlShape): MenuSection[] { export function createConfigurationMenu(intl: IntlShape): MenuSection[] {
@ -67,16 +69,6 @@ export function createConfigurationMenu(intl: IntlShape): MenuSection[] {
defaultMessage: "Product Settings" defaultMessage: "Product Settings"
}), }),
menuItems: [ menuItems: [
{
description: intl.formatMessage({
defaultMessage: "Manage how you ship out orders",
id: "configurationMenuShipping"
}),
icon: <ShippingMethods fontSize="inherit" viewBox="0 0 44 44" />,
permission: PermissionEnum.MANAGE_SHIPPING,
title: intl.formatMessage(sectionNames.shipping),
url: shippingZonesListUrl()
},
{ {
description: intl.formatMessage({ description: intl.formatMessage({
defaultMessage: "Manage how your store charges tax", defaultMessage: "Manage how your store charges tax",
@ -117,6 +109,33 @@ export function createConfigurationMenu(intl: IntlShape): MenuSection[] {
} }
] ]
}, },
{
label: intl.formatMessage({
defaultMessage: "Product Settings"
}),
menuItems: [
{
description: intl.formatMessage({
defaultMessage: "Manage how you ship out orders",
id: "configurationMenuShipping"
}),
icon: <ShippingMethods fontSize="inherit" viewBox="0 0 44 44" />,
permission: PermissionEnum.MANAGE_SHIPPING,
title: intl.formatMessage(sectionNames.shipping),
url: shippingZonesListUrl()
},
{
description: intl.formatMessage({
defaultMessage: "Manage and update your warehouse information",
id: "configurationMenuWarehouses"
}),
icon: <Warehouses fontSize="inherit" viewBox="0 0 44 44" />,
permission: PermissionEnum.MANAGE_PRODUCTS,
title: intl.formatMessage(sectionNames.warehouses),
url: warehouseSection
}
]
},
{ {
label: intl.formatMessage({ label: intl.formatMessage({
defaultMessage: "Miscellaneous" defaultMessage: "Miscellaneous"

View file

@ -1,3 +1,4 @@
import { useEffect } from "react";
import useLocalStorage from "@saleor/hooks/useLocalStorage"; import useLocalStorage from "@saleor/hooks/useLocalStorage";
import { AppListViewSettings, defaultListSettings } from "./../config"; import { AppListViewSettings, defaultListSettings } from "./../config";
import { ListSettings, ListViews } from "./../types"; import { ListSettings, ListViews } from "./../types";
@ -14,6 +15,15 @@ export default function useListSettings<TColumns extends string = string>(
defaultListSettings defaultListSettings
); );
useEffect(() => {
if (settings[listName] === undefined) {
setListSettings(settings => ({
...settings,
[listName]: defaultListSettings[listName]
}));
}
}, []);
const updateListSettings = (key: keyof ListSettings, value: any) => const updateListSettings = (key: keyof ListSettings, value: any) =>
setListSettings(settings => ({ setListSettings(settings => ({
...settings, ...settings,

16
src/icons/Warehouses.tsx Normal file
View file

@ -0,0 +1,16 @@
import createSvgIcon from "@material-ui/icons/utils/createSvgIcon";
import React from "react";
const Warehouses = createSvgIcon(
<>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M5.23438 15.2344H34.7656V38.4375H38.2812V12.3121L19.9219 1.80048L1.5625 12.3121V38.4375H5.23438V15.2344ZM6.79688 40H0V11.4062L19.9219 0L39.8438 11.4062V40H33.2031V16.7969H6.79688V40ZM23.8281 9.84375H16.1719V11.7188H23.8281V9.84375ZM14.6094 8.28125V13.2812H25.3906V8.28125H14.6094ZM15.8594 32.3438H21.6406V38.4375H15.8594V32.3438ZM21.6406 30.7812V23.125H30.5469V30.7812V32.3438V40H23.2031H21.6406H14.2969V30.7812H21.6406ZM28.9844 32.3438H23.2031V38.4375H28.9844V32.3438ZM28.9844 30.7812V24.6875H23.2031V30.7812H28.9844Z"
fill="#06847B"
/>
</>,
"Warehouses"
);
export default Warehouses;

View file

@ -55,6 +55,8 @@ import TaxesSection from "./taxes";
import TranslationsSection from "./translations"; import TranslationsSection from "./translations";
import { PermissionEnum } from "./types/globalTypes"; import { PermissionEnum } from "./types/globalTypes";
import WebhooksSection from "./webhooks"; import WebhooksSection from "./webhooks";
import { warehouseSection } from "./warehouses/urls";
import WarehouseSection from "./warehouses";
interface ResponseError extends ErrorResponse { interface ResponseError extends ErrorResponse {
networkError?: Error & { networkError?: Error & {
@ -263,6 +265,11 @@ const Routes: React.FC = () => {
path={serviceSection} path={serviceSection}
component={ServiceSection} component={ServiceSection}
/> />
<SectionRoute
permissions={[PermissionEnum.MANAGE_PRODUCTS]}
path={warehouseSection}
component={WarehouseSection}
/>
{createConfigurationMenu(intl).filter(menu => {createConfigurationMenu(intl).filter(menu =>
menu.menuItems.map(item => menu.menuItems.map(item =>
hasPermission(item.permission, user) hasPermission(item.permission, user)

View file

@ -224,6 +224,10 @@ export const sectionNames = defineMessages({
defaultMessage: "Vouchers", defaultMessage: "Vouchers",
description: "vouchers section name" description: "vouchers section name"
}, },
warehouses: {
defaultMessage: "Warehouses",
description: "warehouses section name"
},
webhooks: { webhooks: {
defaultMessage: "Webhooks", defaultMessage: "Webhooks",
description: "webhooks section name" description: "webhooks section name"

View file

@ -0,0 +1,22 @@
/* tslint:disable */
/* eslint-disable */
// This file was automatically generated and should not be edited.
// ====================================================
// GraphQL fragment: StockFragment
// ====================================================
export interface StockFragment_warehouse {
__typename: "Warehouse";
id: string;
name: string;
}
export interface StockFragment {
__typename: "Stock";
id: string;
quantity: number;
quantityAllocated: number;
stockQuantity: number;
warehouse: StockFragment_warehouse;
}

View file

@ -37,6 +37,7 @@ export enum ListViews {
SHIPPING_METHODS_LIST = "SHIPPING_METHODS_LIST", SHIPPING_METHODS_LIST = "SHIPPING_METHODS_LIST",
STAFF_MEMBERS_LIST = "STAFF_MEMBERS_LIST", STAFF_MEMBERS_LIST = "STAFF_MEMBERS_LIST",
VOUCHER_LIST = "VOUCHER_LIST", VOUCHER_LIST = "VOUCHER_LIST",
WAREHOUSE_LIST = "WAREHOUSE_LIST",
WEBHOOK_LIST = "WEBHOOK_LIST" WEBHOOK_LIST = "WEBHOOK_LIST"
} }

View file

@ -746,6 +746,10 @@ export enum VoucherTypeEnum {
SPECIFIC_PRODUCT = "SPECIFIC_PRODUCT", SPECIFIC_PRODUCT = "SPECIFIC_PRODUCT",
} }
export enum WarehouseSortField {
NAME = "NAME",
}
export enum WebhookErrorCode { export enum WebhookErrorCode {
GRAPHQL_ERROR = "GRAPHQL_ERROR", GRAPHQL_ERROR = "GRAPHQL_ERROR",
INVALID = "INVALID", INVALID = "INVALID",
@ -1415,6 +1419,15 @@ export interface VoucherSortingInput {
field: VoucherSortField; field: VoucherSortField;
} }
export interface WarehouseFilterInput {
search?: string | null;
}
export interface WarehouseSortingInput {
direction: OrderDirection;
field: WarehouseSortField;
}
export interface WebhookCreateInput { export interface WebhookCreateInput {
name?: string | null; name?: string | null;
targetUrl?: string | null; targetUrl?: string | null;

View file

@ -0,0 +1,178 @@
import { makeStyles } from "@material-ui/core/styles";
import TableBody from "@material-ui/core/TableBody";
import TableCell from "@material-ui/core/TableCell";
import TableFooter from "@material-ui/core/TableFooter";
import TableRow from "@material-ui/core/TableRow";
import TableHead from "@material-ui/core/TableHead";
import React from "react";
import { FormattedMessage } from "react-intl";
import IconButton from "@material-ui/core/IconButton";
import DeleteIcon from "@material-ui/icons/Delete";
import EditIcon from "@material-ui/icons/Edit";
import { WarehouseFragment } from "@saleor/warehouses/types/WarehouseFragment";
import ResponsiveTable from "@saleor/components/ResponsiveTable";
import Skeleton from "@saleor/components/Skeleton";
import TablePagination from "@saleor/components/TablePagination";
import { maybe, renderCollection } from "@saleor/misc";
import { ListProps, SortPage } from "@saleor/types";
import { WarehouseListUrlSortField } from "@saleor/warehouses/urls";
import TableCellHeader from "@saleor/components/TableCellHeader";
import { getArrowDirection } from "@saleor/utils/sort";
const useStyles = makeStyles(
theme => ({
[theme.breakpoints.up("lg")]: {
colActions: {
width: 160
},
colName: {
width: 400
},
colZones: {
width: "auto"
}
},
actions: {
alignItems: "center",
display: "flex",
justifyContent: "flex-end",
position: "relative",
right: -theme.spacing(2)
},
colActions: {
textAlign: "right"
},
colName: {
paddingLeft: 0
},
colZones: {
paddingLeft: 0
},
tableRow: {
cursor: "pointer"
}
}),
{ name: "WarehouseList" }
);
interface WarehouseListProps
extends ListProps,
SortPage<WarehouseListUrlSortField> {
warehouses: WarehouseFragment[];
onAdd: () => void;
onRemove: (id: string) => void;
}
const numberOfColumns = 3;
const WarehouseList: React.FC<WarehouseListProps> = props => {
const {
warehouses,
disabled,
settings,
sort,
pageInfo,
onNextPage,
onPreviousPage,
onUpdateListSettings,
onRemove,
onRowClick,
onSort
} = props;
const classes = useStyles(props);
return (
<ResponsiveTable>
<TableHead>
<TableRow>
<TableCellHeader
direction={
sort.sort === WarehouseListUrlSortField.name
? getArrowDirection(sort.asc)
: undefined
}
arrowPosition="right"
className={classes.colName}
onClick={() => onSort(WarehouseListUrlSortField.name)}
>
<FormattedMessage defaultMessage="Name" description="warehouse" />
</TableCellHeader>
<TableCell className={classes.colZones}>
<FormattedMessage defaultMessage="Shipping Zones" />
</TableCell>
<TableCell className={classes.colActions}>
<FormattedMessage defaultMessage="Actions" />
</TableCell>
</TableRow>
</TableHead>
<TableFooter>
<TableRow>
<TablePagination
colSpan={numberOfColumns}
settings={settings}
hasNextPage={pageInfo && !disabled ? pageInfo.hasNextPage : false}
onNextPage={onNextPage}
onUpdateListSettings={onUpdateListSettings}
hasPreviousPage={
pageInfo && !disabled ? pageInfo.hasPreviousPage : false
}
onPreviousPage={onPreviousPage}
/>
</TableRow>
</TableFooter>
<TableBody>
{renderCollection(
warehouses,
warehouse => (
<TableRow
className={classes.tableRow}
hover={!!warehouse}
onClick={warehouse ? onRowClick(warehouse.id) : undefined}
key={warehouse ? warehouse.id : "skeleton"}
data-tc="id"
data-tc-id={maybe(() => warehouse.id)}
>
<TableCell className={classes.colName} data-tc="name">
{maybe<React.ReactNode>(() => warehouse.name, <Skeleton />)}
</TableCell>
<TableCell className={classes.colZones} data-tc="zones">
{maybe<React.ReactNode>(
() =>
warehouse.shippingZones.edges
.map(edge => edge.node.name)
.join(", "),
<Skeleton />
)}
</TableCell>
<TableCell className={classes.colActions}>
<div className={classes.actions}>
<IconButton color="primary">
<EditIcon />
</IconButton>
<IconButton
color="primary"
onClick={() => onRemove(warehouse.id)}
>
<DeleteIcon />
</IconButton>
</div>
</TableCell>
</TableRow>
),
() => (
<TableRow>
<TableCell colSpan={numberOfColumns}>
<FormattedMessage defaultMessage="No warehouses found" />
</TableCell>
</TableRow>
)
)}
</TableBody>
</ResponsiveTable>
);
};
WarehouseList.displayName = "WarehouseList";
export default WarehouseList;

View file

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

View file

@ -0,0 +1,36 @@
import { storiesOf } from "@storybook/react";
import React from "react";
import {
pageListProps,
tabPageProps,
sortPageProps,
searchPageProps
} from "@saleor/fixtures";
import WarehouseListPage, {
WarehouseListPageProps
} from "@saleor/warehouses/components/WarehouseListPage";
import Decorator from "@saleor/storybook/Decorator";
import { WarehouseListUrlSortField } from "@saleor/warehouses/urls";
import { warehouseList } from "../../fixtures";
const props: WarehouseListPageProps = {
...pageListProps.default,
...searchPageProps,
...sortPageProps,
...tabPageProps,
onBack: () => undefined,
sort: {
...sortPageProps.sort,
sort: WarehouseListUrlSortField.name
},
warehouses: warehouseList
};
storiesOf("Views / Warehouses / Warehouse list", module)
.addDecorator(Decorator)
.add("default", () => <WarehouseListPage {...props} />)
.add("loading", () => (
<WarehouseListPage {...props} disabled={true} warehouses={undefined} />
))
.add("no data", () => <WarehouseListPage {...props} warehouses={[]} />);

View file

@ -0,0 +1,104 @@
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 { WarehouseFragment } from "@saleor/warehouses/types/WarehouseFragment";
import Container from "@saleor/components/Container";
import PageHeader from "@saleor/components/PageHeader";
import SearchBar from "@saleor/components/SearchBar";
import { sectionNames } from "@saleor/intl";
import {
PageListProps,
SearchPageProps,
TabPageProps,
SortPage
} from "@saleor/types";
import { WarehouseListUrlSortField } from "@saleor/warehouses/urls";
import AppHeader from "@saleor/components/AppHeader";
import WarehouseList from "../WarehouseList";
export interface WarehouseListPageProps
extends PageListProps,
SearchPageProps,
SortPage<WarehouseListUrlSortField>,
TabPageProps {
warehouses: WarehouseFragment[];
onBack: () => void;
onRemove: (id: string) => void;
}
export const WarehouseListPage: React.FC<WarehouseListPageProps> = ({
warehouses,
currentTab,
disabled,
initialSearch,
pageInfo,
settings,
tabs,
onAdd,
onAll,
onBack,
onNextPage,
onPreviousPage,
onRemove,
onRowClick,
onSearchChange,
onTabChange,
onTabDelete,
onTabSave,
onUpdateListSettings,
...listProps
}) => {
const intl = useIntl();
return (
<Container>
<AppHeader onBack={onBack}>
<FormattedMessage {...sectionNames.configuration} />
</AppHeader>
<PageHeader title={intl.formatMessage(sectionNames.warehouses)}>
<Button color="primary" variant="contained" onClick={onAdd}>
<FormattedMessage
defaultMessage="Create Warehouse"
description="button"
/>
</Button>
</PageHeader>
<Card>
<SearchBar
allTabLabel={intl.formatMessage({
defaultMessage: "All Warehouses",
description: "tab name"
})}
currentTab={currentTab}
initialSearch={initialSearch}
searchPlaceholder={intl.formatMessage({
defaultMessage: "Search Warehouse"
})}
tabs={tabs}
onAll={onAll}
onSearchChange={onSearchChange}
onTabChange={onTabChange}
onTabDelete={onTabDelete}
onTabSave={onTabSave}
/>
<WarehouseList
warehouses={warehouses}
disabled={disabled}
pageInfo={pageInfo}
settings={settings}
onAdd={onAdd}
onNextPage={onNextPage}
onPreviousPage={onPreviousPage}
onRemove={onRemove}
onRowClick={onRowClick}
onUpdateListSettings={onUpdateListSettings}
{...listProps}
/>
</Card>
</Container>
);
};
WarehouseListPage.displayName = "WarehouseListPage";
export default WarehouseListPage;

View file

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

View file

@ -0,0 +1,53 @@
import { shippingZones } from "../shipping/fixtures";
import { WarehouseList_warehouses_edges_node } from "./types/WarehouseList";
export const warehouseList: WarehouseList_warehouses_edges_node[] = [
{
__typename: "Warehouse",
id: "V2FyZWhvdXNlOmEzMThmMGZlLTcwMmYtNDNjYy1hYmFjLWZmZmMzN2Y3ZTliYw==",
name: "C our wares",
shippingZones: {
__typename: "ShippingZoneCountableConnection",
edges: shippingZones.map(node => ({
__typename: "ShippingZoneCountableEdge",
node
}))
}
},
{
__typename: "Warehouse",
id: "V2FyZWhvdXNlOjJmN2UyOTlmLWEwMzMtNDhjZS1iYmM5LTFkZDM4NjU2ZjMwYw==",
name: "Be stocked",
shippingZones: {
__typename: "ShippingZoneCountableConnection",
edges: shippingZones.map(node => ({
__typename: "ShippingZoneCountableEdge",
node
}))
}
},
{
__typename: "Warehouse",
id: "V2FyZWhvdXNlOmM0ZmQ3Nzc0LWZlMjYtNDE1YS1hYjk1LWFlYTFjMjI0NTgwNg==",
name: "A Warehouse",
shippingZones: {
__typename: "ShippingZoneCountableConnection",
edges: shippingZones.map(node => ({
__typename: "ShippingZoneCountableEdge",
node
}))
}
},
{
__typename: "Warehouse",
id: "V2FyZWhvdXNlOmNlMmNiZDhhLWRkYmQtNDhiNS1hM2UxLTNmZGVkZGI5MWZkMg==",
name: "Darkwares",
shippingZones: {
__typename: "ShippingZoneCountableConnection",
edges: shippingZones.map(node => ({
__typename: "ShippingZoneCountableEdge",
node
}))
}
}
];

66
src/warehouses/index.tsx Normal file
View file

@ -0,0 +1,66 @@
import { parse as parseQs } from "qs";
import React from "react";
import { Route, RouteComponentProps, Switch } from "react-router-dom";
import { sectionNames } from "@saleor/intl";
import { useIntl } from "react-intl";
import { asSortParams } from "@saleor/utils/sort";
import { WindowTitle } from "../components/WindowTitle";
import {
// warehouseAddPath,
// WarehouseAddUrlQueryParams,
warehouseListPath,
WarehouseListUrlQueryParams,
// warehousePath,
// WarehouseUrlQueryParams,
WarehouseListUrlSortField
} from "./urls";
// import WarehouseCreateComponent from "./views/WarehouseCreate";
// import WarehouseDetailsComponent from "./views/WarehouseDetails";
import WarehouseListComponent from "./views/WarehouseList";
const WarehouseList: React.FC<RouteComponentProps> = ({ location }) => {
const qs = parseQs(location.search.substr(1));
const params: WarehouseListUrlQueryParams = asSortParams(
qs,
WarehouseListUrlSortField
);
return <WarehouseListComponent params={params} />;
};
// const WarehouseCreate: React.FC<RouteComponentProps<{}>> = ({ location }) => {
// const qs = parseQs(location.search.substr(1));
// const params: WarehouseAddUrlQueryParams = qs;
// return <WarehouseCreateComponent params={params} />;
// };
// const WarehouseDetails: React.FC<RouteComponentProps<{ id: string }>> = ({
// location,
// match
// }) => {
// const qs = parseQs(location.search.substr(1));
// const params: WarehouseUrlQueryParams = qs;
// return (
// <WarehouseDetailsComponent
// id={decodeURIComponent(match.params.id)}
// params={params}
// />
// );
// };
export const WarehouseSection: React.FC = () => {
const intl = useIntl();
return (
<>
<WindowTitle title={intl.formatMessage(sectionNames.warehouses)} />
<Switch>
<Route exact path={warehouseListPath} component={WarehouseList} />
{/* <Route exact path={warehouseAddPath} component={WarehouseCreate} />
<Route path={warehousePath(":id")} component={WarehouseDetails} /> */}
</Switch>
</>
);
};
export default WarehouseSection;

55
src/warehouses/queries.ts Normal file
View file

@ -0,0 +1,55 @@
import gql from "graphql-tag";
import makeQuery from "@saleor/hooks/makeQuery";
import { pageInfoFragment } from "@saleor/queries";
import { WarehouseList, WarehouseListVariables } from "./types/WarehouseList";
export const warehouseFragment = gql`
fragment WarehouseFragment on Warehouse {
id
name
shippingZones(first: 100) {
edges {
node {
id
name
}
}
}
}
`;
const warehouseList = gql`
${warehouseFragment}
${pageInfoFragment}
query WarehouseList(
$first: Int
$after: String
$last: Int
$before: String
$filter: WarehouseFilterInput
$sort: WarehouseSortingInput
) {
warehouses(
before: $before
after: $after
first: $first
last: $last
filter: $filter
sortBy: $sort
) {
edges {
node {
...WarehouseFragment
}
}
pageInfo {
...PageInfoFragment
}
}
}
`;
export const useWarehouseList = makeQuery<
WarehouseList,
WarehouseListVariables
>(warehouseList);

View file

@ -0,0 +1,30 @@
/* tslint:disable */
/* eslint-disable */
// This file was automatically generated and should not be edited.
// ====================================================
// GraphQL fragment: WarehouseFragment
// ====================================================
export interface WarehouseFragment_shippingZones_edges_node {
__typename: "ShippingZone";
id: string;
name: string;
}
export interface WarehouseFragment_shippingZones_edges {
__typename: "ShippingZoneCountableEdge";
node: WarehouseFragment_shippingZones_edges_node;
}
export interface WarehouseFragment_shippingZones {
__typename: "ShippingZoneCountableConnection";
edges: WarehouseFragment_shippingZones_edges[];
}
export interface WarehouseFragment {
__typename: "Warehouse";
id: string;
name: string;
shippingZones: WarehouseFragment_shippingZones;
}

View file

@ -0,0 +1,64 @@
/* tslint:disable */
/* eslint-disable */
// This file was automatically generated and should not be edited.
import { WarehouseFilterInput, WarehouseSortingInput } from "./../../types/globalTypes";
// ====================================================
// GraphQL query operation: WarehouseList
// ====================================================
export interface WarehouseList_warehouses_edges_node_shippingZones_edges_node {
__typename: "ShippingZone";
id: string;
name: string;
}
export interface WarehouseList_warehouses_edges_node_shippingZones_edges {
__typename: "ShippingZoneCountableEdge";
node: WarehouseList_warehouses_edges_node_shippingZones_edges_node;
}
export interface WarehouseList_warehouses_edges_node_shippingZones {
__typename: "ShippingZoneCountableConnection";
edges: WarehouseList_warehouses_edges_node_shippingZones_edges[];
}
export interface WarehouseList_warehouses_edges_node {
__typename: "Warehouse";
id: string;
name: string;
shippingZones: WarehouseList_warehouses_edges_node_shippingZones;
}
export interface WarehouseList_warehouses_edges {
__typename: "WarehouseCountableEdge";
node: WarehouseList_warehouses_edges_node;
}
export interface WarehouseList_warehouses_pageInfo {
__typename: "PageInfo";
endCursor: string | null;
hasNextPage: boolean;
hasPreviousPage: boolean;
startCursor: string | null;
}
export interface WarehouseList_warehouses {
__typename: "WarehouseCountableConnection";
edges: WarehouseList_warehouses_edges[];
pageInfo: WarehouseList_warehouses_pageInfo;
}
export interface WarehouseList {
warehouses: WarehouseList_warehouses | null;
}
export interface WarehouseListVariables {
first?: number | null;
after?: string | null;
last?: number | null;
before?: string | null;
filter?: WarehouseFilterInput | null;
sort?: WarehouseSortingInput | null;
}

42
src/warehouses/urls.ts Normal file
View file

@ -0,0 +1,42 @@
import { stringify as stringifyQs } from "qs";
import urlJoin from "url-join";
import {
ActiveTab,
Dialog,
Filters,
Pagination,
SingleAction,
TabActionDialog,
Sort
} from "../types";
export const warehouseSection = "/warehouses/";
export const warehouseListPath = warehouseSection;
export enum WarehouseListUrlFiltersEnum {
query = "query"
}
export type WarehouseListUrlFilters = Filters<WarehouseListUrlFiltersEnum>;
export type WarehouseListUrlDialog = "remove" | TabActionDialog;
export enum WarehouseListUrlSortField {
name = "name"
}
export type WarehouseListUrlSort = Sort<WarehouseListUrlSortField>;
export type WarehouseListUrlQueryParams = ActiveTab &
Dialog<WarehouseListUrlDialog> &
Pagination &
WarehouseListUrlFilters &
WarehouseListUrlSort &
SingleAction;
export const warehouseListUrl = (params?: WarehouseListUrlQueryParams) =>
warehouseListPath + "?" + stringifyQs(params);
export const warehousePath = (id: string) => urlJoin(warehouseSection, id);
export type WarehouseUrlDialog = "remove";
export type WarehouseUrlQueryParams = Dialog<WarehouseUrlDialog> & SingleAction;
export const warehouseUrl = (id: string, params?: WarehouseUrlQueryParams) =>
warehousePath(encodeURIComponent(id)) + "?" + stringifyQs(params);
export const warehouseAddPath = urlJoin(warehouseSection, "add");
export const warehouseAddUrl = warehouseAddPath;

View file

@ -0,0 +1,160 @@
import React from "react";
import { useIntl } from "react-intl";
import {
WarehouseListUrlQueryParams,
warehouseUrl,
WarehouseListUrlDialog,
warehouseListUrl,
warehouseAddUrl
} from "@saleor/warehouses/urls";
import useNavigator from "@saleor/hooks/useNavigator";
import { useWarehouseList } from "@saleor/warehouses/queries";
import usePaginator, {
createPaginationState
} from "@saleor/hooks/usePaginator";
import useNotifier from "@saleor/hooks/useNotifier";
import useListSettings from "@saleor/hooks/useListSettings";
import { ListViews } from "@saleor/types";
import { WindowTitle } from "@saleor/components/WindowTitle";
import { sectionNames } from "@saleor/intl";
import WarehouseListPage from "@saleor/warehouses/components/WarehouseListPage";
import SaveFilterTabDialog, {
SaveFilterTabDialogFormData
} from "@saleor/components/SaveFilterTabDialog";
import DeleteFilterTabDialog from "@saleor/components/DeleteFilterTabDialog";
import { maybe } from "@saleor/misc";
import { getSortParams } from "@saleor/utils/sort";
import createSortHandler from "@saleor/utils/handlers/sortHandler";
import createFilterHandlers from "@saleor/utils/handlers/filterHandlers";
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
import { configurationMenuUrl } from "@saleor/configuration";
import { getSortQueryVariables } from "./sort";
import {
getFilterVariables,
getFilterTabs,
areFiltersApplied,
deleteFilterTab,
saveFilterTab,
getActiveFilters
} from "./filters";
export interface WarehouseListProps {
params: WarehouseListUrlQueryParams;
}
const WarehouseList: React.FC<WarehouseListProps> = ({ params }) => {
const navigate = useNavigator();
const notify = useNotifier();
const paginate = usePaginator();
const { updateListSettings, settings } = useListSettings(
ListViews.SALES_LIST
);
const intl = useIntl();
const paginationState = createPaginationState(settings.rowNumber, params);
const queryVariables = React.useMemo(
() => ({
...paginationState,
filter: getFilterVariables(params),
sort: getSortQueryVariables(params)
}),
[params]
);
const { data, loading, refetch } = useWarehouseList({
displayLoader: true,
variables: queryVariables
});
const tabs = getFilterTabs();
const currentTab =
params.activeTab === undefined
? areFiltersApplied(params)
? tabs.length + 1
: 0
: parseInt(params.activeTab, 0);
const [, resetFilters, handleSearchChange] = createFilterHandlers({
createUrl: warehouseListUrl,
getFilterQueryParam: () => undefined,
navigate,
params
});
const [openModal, closeModal] = createDialogActionHandlers<
WarehouseListUrlDialog,
WarehouseListUrlQueryParams
>(navigate, warehouseListUrl, params);
const handleTabChange = (tab: number) =>
navigate(
warehouseListUrl({
activeTab: tab.toString(),
...getFilterTabs()[tab - 1].data
})
);
const handleTabDelete = () => {
deleteFilterTab(currentTab);
navigate(warehouseListUrl());
};
const handleTabSave = (data: SaveFilterTabDialogFormData) => {
saveFilterTab(data.name, getActiveFilters(params));
handleTabChange(tabs.length + 1);
};
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
maybe(() => data.warehouses.pageInfo),
paginationState,
params
);
const handleSort = createSortHandler(navigate, warehouseListUrl, params);
return (
<>
<WindowTitle title={intl.formatMessage(sectionNames.warehouses)} />
<WarehouseListPage
currentTab={currentTab}
initialSearch={params.query || ""}
onSearchChange={handleSearchChange}
onAll={resetFilters}
onBack={() => navigate(configurationMenuUrl)}
onTabChange={handleTabChange}
onTabDelete={() => openModal("delete-search")}
onTabSave={() => openModal("save-search")}
tabs={tabs.map(tab => tab.name)}
warehouses={maybe(() => data.warehouses.edges.map(edge => edge.node))}
settings={settings}
disabled={loading}
pageInfo={pageInfo}
onAdd={() => navigate(warehouseAddUrl)}
onNextPage={loadNextPage}
onPreviousPage={loadPreviousPage}
onRemove={() => undefined}
onSort={handleSort}
onUpdateListSettings={updateListSettings}
onRowClick={id => () => navigate(warehouseUrl(id))}
sort={getSortParams(params)}
/>
<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, "...")}
/>
</>
);
};
WarehouseList.displayName = "WarehouseList";
export default WarehouseList;

View file

@ -0,0 +1,31 @@
import { WarehouseFilterInput } from "@saleor/types/globalTypes";
import {
createFilterTabUtils,
createFilterUtils
} from "../../../utils/filters";
import {
WarehouseListUrlFilters,
WarehouseListUrlFiltersEnum,
WarehouseListUrlQueryParams
} from "../../urls";
export const WAREHOUSE_FILTERS_KEY = "warehouseFilters";
export function getFilterVariables(
params: WarehouseListUrlFilters
): WarehouseFilterInput {
return {
search: params.query
};
}
export const {
deleteFilterTab,
getFilterTabs,
saveFilterTab
} = createFilterTabUtils<WarehouseListUrlFilters>(WAREHOUSE_FILTERS_KEY);
export const { areFiltersApplied, getActiveFilters } = createFilterUtils<
WarehouseListUrlQueryParams,
WarehouseListUrlFilters
>(WarehouseListUrlFiltersEnum);

View file

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

View file

@ -0,0 +1,18 @@
import { WarehouseListUrlSortField } from "@saleor/warehouses/urls";
import { WarehouseSortField } from "@saleor/types/globalTypes";
import { createGetSortQueryVariables } from "@saleor/utils/sort";
export function getSortQueryField(
sort: WarehouseListUrlSortField
): WarehouseSortField {
switch (sort) {
case WarehouseListUrlSortField.name:
return WarehouseSortField.NAME;
default:
return undefined;
}
}
export const getSortQueryVariables = createGetSortQueryVariables(
getSortQueryField
);