diff --git a/src/components/Navigator/modes/default/views.ts b/src/components/Navigator/modes/default/views.ts index c2cea32c5..c489ce6fb 100644 --- a/src/components/Navigator/modes/default/views.ts +++ b/src/components/Navigator/modes/default/views.ts @@ -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() } ]; diff --git a/src/config.ts b/src/config.ts index 83d7fcd5a..90b81e7c8 100644 --- a/src/config.ts +++ b/src/config.ts @@ -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 } diff --git a/src/configuration/index.tsx b/src/configuration/index.tsx index 6b1c17a84..e597ad17c 100644 --- a/src/configuration/index.tsx +++ b/src/configuration/index.tsx @@ -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: , - 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: , + permission: PermissionEnum.MANAGE_SHIPPING, + title: intl.formatMessage(sectionNames.shipping), + url: shippingZonesListUrl() + }, + { + description: intl.formatMessage({ + defaultMessage: "Manage and update your warehouse information", + id: "configurationMenuWarehouses" + }), + icon: , + permission: PermissionEnum.MANAGE_PRODUCTS, + title: intl.formatMessage(sectionNames.warehouses), + url: warehouseSection + } + ] + }, { label: intl.formatMessage({ defaultMessage: "Miscellaneous" diff --git a/src/hooks/useListSettings.ts b/src/hooks/useListSettings.ts index 75c1afd86..cac29fca9 100644 --- a/src/hooks/useListSettings.ts +++ b/src/hooks/useListSettings.ts @@ -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( defaultListSettings ); + useEffect(() => { + if (settings[listName] === undefined) { + setListSettings(settings => ({ + ...settings, + [listName]: defaultListSettings[listName] + })); + } + }, []); + const updateListSettings = (key: keyof ListSettings, value: any) => setListSettings(settings => ({ ...settings, diff --git a/src/icons/Warehouses.tsx b/src/icons/Warehouses.tsx new file mode 100644 index 000000000..56170523f --- /dev/null +++ b/src/icons/Warehouses.tsx @@ -0,0 +1,16 @@ +import createSvgIcon from "@material-ui/icons/utils/createSvgIcon"; +import React from "react"; + +const Warehouses = createSvgIcon( + <> + + , + "Warehouses" +); + +export default Warehouses; diff --git a/src/index.tsx b/src/index.tsx index db050df08..12279e63b 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -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} /> + {createConfigurationMenu(intl).filter(menu => menu.menuItems.map(item => hasPermission(item.permission, user) diff --git a/src/intl.ts b/src/intl.ts index 8b7328cff..2f8841205 100644 --- a/src/intl.ts +++ b/src/intl.ts @@ -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" diff --git a/src/products/types/StockFragment.ts b/src/products/types/StockFragment.ts new file mode 100644 index 000000000..7aba2f87d --- /dev/null +++ b/src/products/types/StockFragment.ts @@ -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; +} diff --git a/src/types.ts b/src/types.ts index 3db6bcc62..e3f2014a8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -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" } diff --git a/src/types/globalTypes.ts b/src/types/globalTypes.ts index 72cec5cbe..48507817e 100644 --- a/src/types/globalTypes.ts +++ b/src/types/globalTypes.ts @@ -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; diff --git a/src/warehouses/components/WarehouseList/WarehouseList.tsx b/src/warehouses/components/WarehouseList/WarehouseList.tsx new file mode 100644 index 000000000..6220a8951 --- /dev/null +++ b/src/warehouses/components/WarehouseList/WarehouseList.tsx @@ -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 { + warehouses: WarehouseFragment[]; + onAdd: () => void; + onRemove: (id: string) => void; +} + +const numberOfColumns = 3; + +const WarehouseList: React.FC = props => { + const { + warehouses, + disabled, + settings, + sort, + pageInfo, + onNextPage, + onPreviousPage, + onUpdateListSettings, + onRemove, + onRowClick, + onSort + } = props; + + const classes = useStyles(props); + + return ( + + + + onSort(WarehouseListUrlSortField.name)} + > + + + + + + + + + + + + + + + + + {renderCollection( + warehouses, + warehouse => ( + warehouse.id)} + > + + {maybe(() => warehouse.name, )} + + + {maybe( + () => + warehouse.shippingZones.edges + .map(edge => edge.node.name) + .join(", "), + + )} + + +
+ + + + onRemove(warehouse.id)} + > + + +
+
+
+ ), + () => ( + + + + + + ) + )} +
+
+ ); +}; + +WarehouseList.displayName = "WarehouseList"; +export default WarehouseList; diff --git a/src/warehouses/components/WarehouseList/index.ts b/src/warehouses/components/WarehouseList/index.ts new file mode 100644 index 000000000..4c64e0c2c --- /dev/null +++ b/src/warehouses/components/WarehouseList/index.ts @@ -0,0 +1,2 @@ +export { default } from "./WarehouseList"; +export * from "./WarehouseList"; diff --git a/src/warehouses/components/WarehouseListPage/WarehouseListPage.stories.tsx b/src/warehouses/components/WarehouseListPage/WarehouseListPage.stories.tsx new file mode 100644 index 000000000..7b163fd66 --- /dev/null +++ b/src/warehouses/components/WarehouseListPage/WarehouseListPage.stories.tsx @@ -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", () => ) + .add("loading", () => ( + + )) + .add("no data", () => ); diff --git a/src/warehouses/components/WarehouseListPage/WarehouseListPage.tsx b/src/warehouses/components/WarehouseListPage/WarehouseListPage.tsx new file mode 100644 index 000000000..05c66b060 --- /dev/null +++ b/src/warehouses/components/WarehouseListPage/WarehouseListPage.tsx @@ -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, + TabPageProps { + warehouses: WarehouseFragment[]; + onBack: () => void; + onRemove: (id: string) => void; +} + +export const WarehouseListPage: React.FC = ({ + warehouses, + currentTab, + disabled, + initialSearch, + pageInfo, + settings, + tabs, + onAdd, + onAll, + onBack, + onNextPage, + onPreviousPage, + onRemove, + onRowClick, + onSearchChange, + onTabChange, + onTabDelete, + onTabSave, + onUpdateListSettings, + ...listProps +}) => { + const intl = useIntl(); + + return ( + + + + + + + + + + + + + ); +}; +WarehouseListPage.displayName = "WarehouseListPage"; +export default WarehouseListPage; diff --git a/src/warehouses/components/WarehouseListPage/index.ts b/src/warehouses/components/WarehouseListPage/index.ts new file mode 100644 index 000000000..1bce1dd38 --- /dev/null +++ b/src/warehouses/components/WarehouseListPage/index.ts @@ -0,0 +1,2 @@ +export { default } from "./WarehouseListPage"; +export * from "./WarehouseListPage"; diff --git a/src/warehouses/fixtures.ts b/src/warehouses/fixtures.ts new file mode 100644 index 000000000..e393c9c11 --- /dev/null +++ b/src/warehouses/fixtures.ts @@ -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 + })) + } + } +]; diff --git a/src/warehouses/index.tsx b/src/warehouses/index.tsx new file mode 100644 index 000000000..8d06f0040 --- /dev/null +++ b/src/warehouses/index.tsx @@ -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 = ({ location }) => { + const qs = parseQs(location.search.substr(1)); + const params: WarehouseListUrlQueryParams = asSortParams( + qs, + WarehouseListUrlSortField + ); + + return ; +}; + +// const WarehouseCreate: React.FC> = ({ location }) => { +// const qs = parseQs(location.search.substr(1)); +// const params: WarehouseAddUrlQueryParams = qs; +// return ; +// }; + +// const WarehouseDetails: React.FC> = ({ +// location, +// match +// }) => { +// const qs = parseQs(location.search.substr(1)); +// const params: WarehouseUrlQueryParams = qs; +// return ( +// +// ); +// }; + +export const WarehouseSection: React.FC = () => { + const intl = useIntl(); + + return ( + <> + + + + {/* + */} + + + ); +}; +export default WarehouseSection; diff --git a/src/warehouses/queries.ts b/src/warehouses/queries.ts new file mode 100644 index 000000000..1cfd8b5ac --- /dev/null +++ b/src/warehouses/queries.ts @@ -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); diff --git a/src/warehouses/types/WarehouseFragment.ts b/src/warehouses/types/WarehouseFragment.ts new file mode 100644 index 000000000..1f6e19e37 --- /dev/null +++ b/src/warehouses/types/WarehouseFragment.ts @@ -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; +} diff --git a/src/warehouses/types/WarehouseList.ts b/src/warehouses/types/WarehouseList.ts new file mode 100644 index 000000000..00e3b29e4 --- /dev/null +++ b/src/warehouses/types/WarehouseList.ts @@ -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; +} diff --git a/src/warehouses/urls.ts b/src/warehouses/urls.ts new file mode 100644 index 000000000..9864e5077 --- /dev/null +++ b/src/warehouses/urls.ts @@ -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; +export type WarehouseListUrlDialog = "remove" | TabActionDialog; +export enum WarehouseListUrlSortField { + name = "name" +} +export type WarehouseListUrlSort = Sort; +export type WarehouseListUrlQueryParams = ActiveTab & + Dialog & + 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 & SingleAction; +export const warehouseUrl = (id: string, params?: WarehouseUrlQueryParams) => + warehousePath(encodeURIComponent(id)) + "?" + stringifyQs(params); + +export const warehouseAddPath = urlJoin(warehouseSection, "add"); +export const warehouseAddUrl = warehouseAddPath; diff --git a/src/warehouses/views/WarehouseList/WarehouseList.tsx b/src/warehouses/views/WarehouseList/WarehouseList.tsx new file mode 100644 index 000000000..bc119d669 --- /dev/null +++ b/src/warehouses/views/WarehouseList/WarehouseList.tsx @@ -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 = ({ 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 ( + <> + + 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)} + /> + + tabs[currentTab - 1].name, "...")} + /> + + ); +}; + +WarehouseList.displayName = "WarehouseList"; +export default WarehouseList; diff --git a/src/warehouses/views/WarehouseList/filters.ts b/src/warehouses/views/WarehouseList/filters.ts new file mode 100644 index 000000000..f0e6a19f3 --- /dev/null +++ b/src/warehouses/views/WarehouseList/filters.ts @@ -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(WAREHOUSE_FILTERS_KEY); + +export const { areFiltersApplied, getActiveFilters } = createFilterUtils< + WarehouseListUrlQueryParams, + WarehouseListUrlFilters +>(WarehouseListUrlFiltersEnum); diff --git a/src/warehouses/views/WarehouseList/index.ts b/src/warehouses/views/WarehouseList/index.ts new file mode 100644 index 000000000..4c64e0c2c --- /dev/null +++ b/src/warehouses/views/WarehouseList/index.ts @@ -0,0 +1,2 @@ +export { default } from "./WarehouseList"; +export * from "./WarehouseList"; diff --git a/src/warehouses/views/WarehouseList/sort.ts b/src/warehouses/views/WarehouseList/sort.ts new file mode 100644 index 000000000..ccf74b0fb --- /dev/null +++ b/src/warehouses/views/WarehouseList/sort.ts @@ -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 +);