Add warehouse list view
This commit is contained in:
parent
e7f6bd1e77
commit
414f6d7a3d
25 changed files with 954 additions and 10 deletions
|
@ -21,6 +21,7 @@ import { staffListUrl } from "@saleor/staff/urls";
|
|||
import { countryListUrl } from "@saleor/taxes/urls";
|
||||
import { languageListUrl } from "@saleor/translations/urls";
|
||||
import { webhookListUrl } from "@saleor/webhooks/urls";
|
||||
import { warehouseListUrl } from "@saleor/warehouses/urls";
|
||||
import { QuickSearchActionInput } from "../../types";
|
||||
|
||||
interface View {
|
||||
|
@ -116,6 +117,10 @@ function searchInViews(
|
|||
{
|
||||
label: intl.formatMessage(sectionNames.webhooks),
|
||||
url: webhookListUrl()
|
||||
},
|
||||
{
|
||||
label: intl.formatMessage(sectionNames.warehouses),
|
||||
url: warehouseListUrl()
|
||||
}
|
||||
];
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ export interface AppListViewSettings {
|
|||
[ListViews.STAFF_MEMBERS_LIST]: ListSettings;
|
||||
[ListViews.PERMISSION_GROUP_LIST]: ListSettings;
|
||||
[ListViews.VOUCHER_LIST]: ListSettings;
|
||||
[ListViews.WAREHOUSE_LIST]: ListSettings;
|
||||
[ListViews.WEBHOOK_LIST]: ListSettings;
|
||||
}
|
||||
export const defaultListSettings: AppListViewSettings = {
|
||||
|
@ -80,6 +81,9 @@ export const defaultListSettings: AppListViewSettings = {
|
|||
[ListViews.VOUCHER_LIST]: {
|
||||
rowNumber: PAGINATE_BY
|
||||
},
|
||||
[ListViews.WAREHOUSE_LIST]: {
|
||||
rowNumber: PAGINATE_BY
|
||||
},
|
||||
[ListViews.WEBHOOK_LIST]: {
|
||||
rowNumber: PAGINATE_BY
|
||||
}
|
||||
|
|
|
@ -31,6 +31,8 @@ import { permissionGroupListUrl } from "@saleor/permissionGroups/urls";
|
|||
import { taxSection } from "@saleor/taxes/urls";
|
||||
import { PermissionEnum } from "@saleor/types/globalTypes";
|
||||
import { webhookListUrl } from "@saleor/webhooks/urls";
|
||||
import Warehouses from "@saleor/icons/Warehouses";
|
||||
import { warehouseSection } from "@saleor/warehouses/urls";
|
||||
import ConfigurationPage, { MenuSection } from "./ConfigurationPage";
|
||||
|
||||
export function createConfigurationMenu(intl: IntlShape): MenuSection[] {
|
||||
|
@ -67,16 +69,6 @@ export function createConfigurationMenu(intl: IntlShape): MenuSection[] {
|
|||
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 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({
|
||||
defaultMessage: "Miscellaneous"
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { useEffect } from "react";
|
||||
import useLocalStorage from "@saleor/hooks/useLocalStorage";
|
||||
import { AppListViewSettings, defaultListSettings } from "./../config";
|
||||
import { ListSettings, ListViews } from "./../types";
|
||||
|
@ -14,6 +15,15 @@ export default function useListSettings<TColumns extends string = string>(
|
|||
defaultListSettings
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (settings[listName] === undefined) {
|
||||
setListSettings(settings => ({
|
||||
...settings,
|
||||
[listName]: defaultListSettings[listName]
|
||||
}));
|
||||
}
|
||||
}, []);
|
||||
|
||||
const updateListSettings = (key: keyof ListSettings, value: any) =>
|
||||
setListSettings(settings => ({
|
||||
...settings,
|
||||
|
|
16
src/icons/Warehouses.tsx
Normal file
16
src/icons/Warehouses.tsx
Normal 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;
|
|
@ -55,6 +55,8 @@ import TaxesSection from "./taxes";
|
|||
import TranslationsSection from "./translations";
|
||||
import { PermissionEnum } from "./types/globalTypes";
|
||||
import WebhooksSection from "./webhooks";
|
||||
import { warehouseSection } from "./warehouses/urls";
|
||||
import WarehouseSection from "./warehouses";
|
||||
|
||||
interface ResponseError extends ErrorResponse {
|
||||
networkError?: Error & {
|
||||
|
@ -263,6 +265,11 @@ const Routes: React.FC = () => {
|
|||
path={serviceSection}
|
||||
component={ServiceSection}
|
||||
/>
|
||||
<SectionRoute
|
||||
permissions={[PermissionEnum.MANAGE_PRODUCTS]}
|
||||
path={warehouseSection}
|
||||
component={WarehouseSection}
|
||||
/>
|
||||
{createConfigurationMenu(intl).filter(menu =>
|
||||
menu.menuItems.map(item =>
|
||||
hasPermission(item.permission, user)
|
||||
|
|
|
@ -224,6 +224,10 @@ export const sectionNames = defineMessages({
|
|||
defaultMessage: "Vouchers",
|
||||
description: "vouchers section name"
|
||||
},
|
||||
warehouses: {
|
||||
defaultMessage: "Warehouses",
|
||||
description: "warehouses section name"
|
||||
},
|
||||
webhooks: {
|
||||
defaultMessage: "Webhooks",
|
||||
description: "webhooks section name"
|
||||
|
|
22
src/products/types/StockFragment.ts
Normal file
22
src/products/types/StockFragment.ts
Normal 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;
|
||||
}
|
|
@ -37,6 +37,7 @@ export enum ListViews {
|
|||
SHIPPING_METHODS_LIST = "SHIPPING_METHODS_LIST",
|
||||
STAFF_MEMBERS_LIST = "STAFF_MEMBERS_LIST",
|
||||
VOUCHER_LIST = "VOUCHER_LIST",
|
||||
WAREHOUSE_LIST = "WAREHOUSE_LIST",
|
||||
WEBHOOK_LIST = "WEBHOOK_LIST"
|
||||
}
|
||||
|
||||
|
|
|
@ -746,6 +746,10 @@ export enum VoucherTypeEnum {
|
|||
SPECIFIC_PRODUCT = "SPECIFIC_PRODUCT",
|
||||
}
|
||||
|
||||
export enum WarehouseSortField {
|
||||
NAME = "NAME",
|
||||
}
|
||||
|
||||
export enum WebhookErrorCode {
|
||||
GRAPHQL_ERROR = "GRAPHQL_ERROR",
|
||||
INVALID = "INVALID",
|
||||
|
@ -1415,6 +1419,15 @@ export interface VoucherSortingInput {
|
|||
field: VoucherSortField;
|
||||
}
|
||||
|
||||
export interface WarehouseFilterInput {
|
||||
search?: string | null;
|
||||
}
|
||||
|
||||
export interface WarehouseSortingInput {
|
||||
direction: OrderDirection;
|
||||
field: WarehouseSortField;
|
||||
}
|
||||
|
||||
export interface WebhookCreateInput {
|
||||
name?: string | null;
|
||||
targetUrl?: string | null;
|
||||
|
|
178
src/warehouses/components/WarehouseList/WarehouseList.tsx
Normal file
178
src/warehouses/components/WarehouseList/WarehouseList.tsx
Normal 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;
|
2
src/warehouses/components/WarehouseList/index.ts
Normal file
2
src/warehouses/components/WarehouseList/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./WarehouseList";
|
||||
export * from "./WarehouseList";
|
|
@ -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={[]} />);
|
|
@ -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;
|
2
src/warehouses/components/WarehouseListPage/index.ts
Normal file
2
src/warehouses/components/WarehouseListPage/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./WarehouseListPage";
|
||||
export * from "./WarehouseListPage";
|
53
src/warehouses/fixtures.ts
Normal file
53
src/warehouses/fixtures.ts
Normal 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
66
src/warehouses/index.tsx
Normal 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
55
src/warehouses/queries.ts
Normal 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);
|
30
src/warehouses/types/WarehouseFragment.ts
Normal file
30
src/warehouses/types/WarehouseFragment.ts
Normal 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;
|
||||
}
|
64
src/warehouses/types/WarehouseList.ts
Normal file
64
src/warehouses/types/WarehouseList.ts
Normal 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
42
src/warehouses/urls.ts
Normal 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;
|
160
src/warehouses/views/WarehouseList/WarehouseList.tsx
Normal file
160
src/warehouses/views/WarehouseList/WarehouseList.tsx
Normal 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;
|
31
src/warehouses/views/WarehouseList/filters.ts
Normal file
31
src/warehouses/views/WarehouseList/filters.ts
Normal 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);
|
2
src/warehouses/views/WarehouseList/index.ts
Normal file
2
src/warehouses/views/WarehouseList/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./WarehouseList";
|
||||
export * from "./WarehouseList";
|
18
src/warehouses/views/WarehouseList/sort.ts
Normal file
18
src/warehouses/views/WarehouseList/sort.ts
Normal 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
|
||||
);
|
Loading…
Reference in a new issue