diff --git a/src/fixtures.ts b/src/fixtures.ts index 3394f4a92..037506378 100644 --- a/src/fixtures.ts +++ b/src/fixtures.ts @@ -487,12 +487,12 @@ export const adminUserPermissions: User_userPermissions[] = [ ]; export const address = { - __typename: "Address", + __typename: "Address" as "Address", city: "Port Danielshire", cityArea: "", companyName: "", country: { - __typename: "CountryDisplay", + __typename: "CountryDisplay" as "CountryDisplay", code: "SE", country: "Szwecja" }, diff --git a/src/types/globalTypes.ts b/src/types/globalTypes.ts index 53faacc0a..e6066af22 100644 --- a/src/types/globalTypes.ts +++ b/src/types/globalTypes.ts @@ -1447,6 +1447,14 @@ export interface WarehouseSortingInput { field: WarehouseSortField; } +export interface WarehouseUpdateInput { + name: string; + companyName?: string | null; + shippingZones?: (string | null)[] | null; + email?: string | null; + address?: WarehouseAddressInput | null; +} + export interface WebhookCreateInput { name?: string | null; targetUrl?: string | null; diff --git a/src/warehouses/components/WarehouseCreatePage/WarehouseCreatePage.stories.tsx b/src/warehouses/components/WarehouseCreatePage/WarehouseCreatePage.stories.tsx index 9f25b855b..87ef6f4d4 100644 --- a/src/warehouses/components/WarehouseCreatePage/WarehouseCreatePage.stories.tsx +++ b/src/warehouses/components/WarehouseCreatePage/WarehouseCreatePage.stories.tsx @@ -1,10 +1,9 @@ import { storiesOf } from "@storybook/react"; import React from "react"; -import { address, permissions } from "@saleor/fixtures"; import Decorator from "@saleor/storybook/Decorator"; import { formError } from "@saleor/storybook/misc"; -import { warehouseList } from "../../fixtures"; +import { shop } from "@saleor/siteSettings/fixtures"; import WarehouseCreatePage, { WarehouseCreatePageProps, WarehouseCreatePageFormData @@ -13,20 +12,15 @@ import WarehouseCreatePage, { const props: WarehouseCreatePageProps = { disabled: false, errors: [], - saveButtonBarState: "default", onBack: () => undefined, onSubmit: () => undefined, - warehouse: { - ...warehouseList[0], - address - } + saveButtonBarState: "default", + shop }; storiesOf("Views / Warehouses / Create warehouse", module) .addDecorator(Decorator) .add("default", () => ) - .add("loading", () => ( - - )) + .add("loading", () => ) .add("form errors", () => ( undefined, + onSubmit: () => undefined, + saveButtonBarState: "default", + warehouse: { + ...warehouseList[0], + address + } +}; +storiesOf("Views / Warehouses / Warehouse details", module) + .addDecorator(Decorator) + .add("default", () => ) + .add("loading", () => ( + + )) + .add("form errors", () => ( + ).map(field => + formError(field) + )} + /> + )); diff --git a/src/warehouses/components/WarehouseDetailsPage/WarehouseDetailsPage.tsx b/src/warehouses/components/WarehouseDetailsPage/WarehouseDetailsPage.tsx new file mode 100644 index 000000000..1d6b02689 --- /dev/null +++ b/src/warehouses/components/WarehouseDetailsPage/WarehouseDetailsPage.tsx @@ -0,0 +1,141 @@ +import React from "react"; +import { useIntl, FormattedMessage } from "react-intl"; + +import Container from "@saleor/components/Container"; +import Form from "@saleor/components/Form"; +import SaveButtonBar from "@saleor/components/SaveButtonBar"; +import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton"; +import { UserError } from "@saleor/types"; +import Grid from "@saleor/components/Grid"; +import CardSpacer from "@saleor/components/CardSpacer"; +import CompanyAddressInput from "@saleor/components/CompanyAddressInput"; +import { AddressTypeInput } from "@saleor/customers/types"; +import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler"; +import { mapCountriesToChoices } from "@saleor/utils/maps"; +import useAddressValidation from "@saleor/hooks/useAddressValidation"; +import useStateFromProps from "@saleor/hooks/useStateFromProps"; +import { maybe, findValueInEnum } from "@saleor/misc"; +import { ShopInfo_shop } from "@saleor/components/Shop/types/ShopInfo"; +import AppHeader from "@saleor/components/AppHeader"; +import PageHeader from "@saleor/components/PageHeader"; +import { sectionNames } from "@saleor/intl"; +import { CountryCode } from "@saleor/types/globalTypes"; +import WarehouseInfo from "../WarehouseInfo"; +import WarehouseZones from "../WarehouseZones"; +import { WarehouseDetails_warehouse } from "../../types/WarehouseDetails"; + +export interface WarehouseDetailsPageFormData extends AddressTypeInput { + name: string; +} +export interface WarehouseDetailsPageProps { + disabled: boolean; + errors: UserError[]; + saveButtonBarState: ConfirmButtonTransitionState; + shop: ShopInfo_shop; + warehouse: WarehouseDetails_warehouse; + onBack: () => void; + onShippingZoneClick: (id: string) => void; + onSubmit: (data: WarehouseDetailsPageFormData) => void; +} + +const WarehouseDetailsPage: React.FC = ({ + disabled, + errors: apiErrors, + saveButtonBarState, + shop, + warehouse, + onBack, + onShippingZoneClick, + onSubmit +}) => { + const intl = useIntl(); + const [displayCountry, setDisplayCountry] = useStateFromProps(""); + + const { + errors: validationErrors, + submit: handleSubmit + } = useAddressValidation(onSubmit); + + const initialForm: WarehouseDetailsPageFormData = { + city: maybe(() => warehouse.address.city, ""), + companyName: maybe(() => warehouse.address.companyName, ""), + country: maybe(() => + findValueInEnum(warehouse.address.country.code, CountryCode) + ), + countryArea: maybe(() => warehouse.address.countryArea, ""), + name: maybe(() => warehouse.name, ""), + phone: maybe(() => warehouse.address.phone, ""), + postalCode: maybe(() => warehouse.address.postalCode, ""), + streetAddress1: maybe(() => warehouse.address.streetAddress1, ""), + streetAddress2: maybe(() => warehouse.address.streetAddress2, "") + }; + + return ( + + {({ change, data, errors, submit }) => { + const countryChoices = mapCountriesToChoices( + maybe(() => shop.countries, []) + ); + const handleCountryChange = createSingleAutocompleteSelectHandler( + change, + setDisplayCountry, + countryChoices + ); + + return ( + + + + + warehouse.name)} /> + + + + + + + + + warehouse.shippingZones.edges.map(edge => edge.node) + )} + onShippingZoneClick={onShippingZoneClick} + /> + + + + + ); + }} + + ); +}; + +WarehouseDetailsPage.displayName = "WarehouseDetailsPage"; +export default WarehouseDetailsPage; diff --git a/src/warehouses/components/WarehouseDetailsPage/index.ts b/src/warehouses/components/WarehouseDetailsPage/index.ts new file mode 100644 index 000000000..c5fea07a8 --- /dev/null +++ b/src/warehouses/components/WarehouseDetailsPage/index.ts @@ -0,0 +1,2 @@ +export { default } from "./WarehouseDetailsPage"; +export * from "./WarehouseDetailsPage"; diff --git a/src/warehouses/components/WarehouseInfo/WarehouseInfo.tsx b/src/warehouses/components/WarehouseInfo/WarehouseInfo.tsx index 56b402e08..7b31cc2ea 100644 --- a/src/warehouses/components/WarehouseInfo/WarehouseInfo.tsx +++ b/src/warehouses/components/WarehouseInfo/WarehouseInfo.tsx @@ -3,6 +3,7 @@ import Card from "@material-ui/core/Card"; import CardContent from "@material-ui/core/CardContent"; import TextField from "@material-ui/core/TextField"; import { useIntl } from "react-intl"; + import CardTitle from "@saleor/components/CardTitle"; import { commonMessages } from "@saleor/intl"; import { FormChange } from "@saleor/hooks/useForm"; diff --git a/src/warehouses/components/WarehouseZones/WarehouseZones.tsx b/src/warehouses/components/WarehouseZones/WarehouseZones.tsx new file mode 100644 index 000000000..b4fe1cb2d --- /dev/null +++ b/src/warehouses/components/WarehouseZones/WarehouseZones.tsx @@ -0,0 +1,73 @@ +import React from "react"; +import Card from "@material-ui/core/Card"; +import CardContent from "@material-ui/core/CardContent"; +import Typography from "@material-ui/core/Typography"; +import { useIntl, FormattedMessage } from "react-intl"; +import makeStyles from "@material-ui/core/styles/makeStyles"; + +import CardTitle from "@saleor/components/CardTitle"; +import { WarehouseDetails_warehouse_shippingZones_edges_node } from "@saleor/warehouses/types/WarehouseDetails"; +import { renderCollection, maybe } from "@saleor/misc"; +import Link from "@saleor/components/Link"; +import Skeleton from "@saleor/components/Skeleton"; + +export interface WarehouseInfoProps { + zones: WarehouseDetails_warehouse_shippingZones_edges_node[]; + onShippingZoneClick: (id: string) => void; +} + +const useStyles = makeStyles( + theme => ({ + link: { + "&:not(:last-of-type)": { + marginBottom: theme.spacing() + } + } + }), + { + name: "WarehouseInfoProps" + } +); + +const WarehouseInfo: React.FC = ({ + zones, + onShippingZoneClick +}) => { + const classes = useStyles({}); + const intl = useIntl(); + + return ( + + + + {renderCollection( + zones, + zone => + maybe( + () => ( + + onShippingZoneClick(zone.id)}> + {zone.name} + + + ), + + ), + () => ( + + + + ) + )} + + + ); +}; + +WarehouseInfo.displayName = "WarehouseInfo"; +export default WarehouseInfo; diff --git a/src/warehouses/components/WarehouseZones/index.ts b/src/warehouses/components/WarehouseZones/index.ts new file mode 100644 index 000000000..c0a280252 --- /dev/null +++ b/src/warehouses/components/WarehouseZones/index.ts @@ -0,0 +1,2 @@ +export { default } from "./WarehouseZones"; +export * from "./WarehouseZones"; diff --git a/src/warehouses/index.tsx b/src/warehouses/index.tsx index 0b37a9da5..b94af8c1a 100644 --- a/src/warehouses/index.tsx +++ b/src/warehouses/index.tsx @@ -7,17 +7,14 @@ import { useIntl } from "react-intl"; import { asSortParams } from "@saleor/utils/sort"; import { WindowTitle } from "../components/WindowTitle"; import { - // warehouseAddPath, - // WarehouseAddUrlQueryParams, warehouseListPath, WarehouseListUrlQueryParams, - // warehousePath, - // WarehouseUrlQueryParams, + warehousePath, + WarehouseUrlQueryParams, WarehouseListUrlSortField, warehouseAddPath } from "./urls"; -// import WarehouseCreateComponent from "./views/WarehouseCreate"; -// import WarehouseDetailsComponent from "./views/WarehouseDetails"; +import WarehouseDetailsComponent from "./views/WarehouseDetails"; import WarehouseListComponent from "./views/WarehouseList"; import WarehouseCreate from "./views/WarehouseCreate"; @@ -31,19 +28,19 @@ const WarehouseList: React.FC = ({ location }) => { return ; }; -// const WarehouseDetails: React.FC> = ({ -// location, -// match -// }) => { -// const qs = parseQs(location.search.substr(1)); -// const params: WarehouseUrlQueryParams = 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(); @@ -54,7 +51,7 @@ export const WarehouseSection: React.FC = () => { - {/* */} + > ); diff --git a/src/warehouses/mutations.ts b/src/warehouses/mutations.ts index 9611cac88..26a9b2805 100644 --- a/src/warehouses/mutations.ts +++ b/src/warehouses/mutations.ts @@ -5,6 +5,10 @@ import { WarehouseCreate, WarehouseCreateVariables } from "./types/WarehouseCreate"; +import { + WarehouseUpdate, + WarehouseUpdateVariables +} from "./types/WarehouseUpdate"; import { WarehouseDelete, WarehouseDeleteVariables @@ -44,3 +48,22 @@ export const useWarehouseCreate = makeMutation< WarehouseCreate, WarehouseCreateVariables >(createWarehouse); + +const updateWarehouse = gql` + ${warehouseDetailsFragment} + mutation WarehouseUpdate($id: ID!, $input: WarehouseUpdateInput!) { + updateWarehouse(id: $id, input: $input) { + errors { + field + message + } + warehouse { + ...WarehouseDetailsFragment + } + } + } +`; +export const useWarehouseUpdate = makeMutation< + WarehouseUpdate, + WarehouseUpdateVariables +>(updateWarehouse); diff --git a/src/warehouses/queries.ts b/src/warehouses/queries.ts index d75984c68..6eeefdeb4 100644 --- a/src/warehouses/queries.ts +++ b/src/warehouses/queries.ts @@ -4,6 +4,10 @@ import makeQuery from "@saleor/hooks/makeQuery"; import { pageInfoFragment } from "@saleor/queries"; import { fragmentAddress } from "@saleor/orders/queries"; import { WarehouseList, WarehouseListVariables } from "./types/WarehouseList"; +import { + WarehouseDetails, + WarehouseDetailsVariables +} from "./types/WarehouseDetails"; export const warehouseFragment = gql` fragment WarehouseFragment on Warehouse { @@ -65,3 +69,16 @@ export const useWarehouseList = makeQuery< WarehouseList, WarehouseListVariables >(warehouseList); + +const warehouseDetails = gql` + ${warehouseDetailsFragment} + query WarehouseDetails($id: ID!) { + warehouse(id: $id) { + ...WarehouseDetailsFragment + } + } +`; +export const useWarehouseDetails = makeQuery< + WarehouseDetails, + WarehouseDetailsVariables +>(warehouseDetails); diff --git a/src/warehouses/types/WarehouseDetails.ts b/src/warehouses/types/WarehouseDetails.ts new file mode 100644 index 000000000..b48eb255a --- /dev/null +++ b/src/warehouses/types/WarehouseDetails.ts @@ -0,0 +1,61 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +// ==================================================== +// GraphQL query operation: WarehouseDetails +// ==================================================== + +export interface WarehouseDetails_warehouse_shippingZones_edges_node { + __typename: "ShippingZone"; + id: string; + name: string; +} + +export interface WarehouseDetails_warehouse_shippingZones_edges { + __typename: "ShippingZoneCountableEdge"; + node: WarehouseDetails_warehouse_shippingZones_edges_node; +} + +export interface WarehouseDetails_warehouse_shippingZones { + __typename: "ShippingZoneCountableConnection"; + edges: WarehouseDetails_warehouse_shippingZones_edges[]; +} + +export interface WarehouseDetails_warehouse_address_country { + __typename: "CountryDisplay"; + code: string; + country: string; +} + +export interface WarehouseDetails_warehouse_address { + __typename: "Address"; + city: string; + cityArea: string; + companyName: string; + country: WarehouseDetails_warehouse_address_country; + countryArea: string; + firstName: string; + id: string; + lastName: string; + phone: string | null; + postalCode: string; + streetAddress1: string; + streetAddress2: string; +} + +export interface WarehouseDetails_warehouse { + __typename: "Warehouse"; + id: string; + name: string; + shippingZones: WarehouseDetails_warehouse_shippingZones; + address: WarehouseDetails_warehouse_address; +} + +export interface WarehouseDetails { + warehouse: WarehouseDetails_warehouse | null; +} + +export interface WarehouseDetailsVariables { + id: string; +} diff --git a/src/warehouses/types/WarehouseUpdate.ts b/src/warehouses/types/WarehouseUpdate.ts new file mode 100644 index 000000000..345f6f9fb --- /dev/null +++ b/src/warehouses/types/WarehouseUpdate.ts @@ -0,0 +1,76 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +import { WarehouseUpdateInput } from "./../../types/globalTypes"; + +// ==================================================== +// GraphQL mutation operation: WarehouseUpdate +// ==================================================== + +export interface WarehouseUpdate_updateWarehouse_errors { + __typename: "Error"; + field: string | null; + message: string | null; +} + +export interface WarehouseUpdate_updateWarehouse_warehouse_shippingZones_edges_node { + __typename: "ShippingZone"; + id: string; + name: string; +} + +export interface WarehouseUpdate_updateWarehouse_warehouse_shippingZones_edges { + __typename: "ShippingZoneCountableEdge"; + node: WarehouseUpdate_updateWarehouse_warehouse_shippingZones_edges_node; +} + +export interface WarehouseUpdate_updateWarehouse_warehouse_shippingZones { + __typename: "ShippingZoneCountableConnection"; + edges: WarehouseUpdate_updateWarehouse_warehouse_shippingZones_edges[]; +} + +export interface WarehouseUpdate_updateWarehouse_warehouse_address_country { + __typename: "CountryDisplay"; + code: string; + country: string; +} + +export interface WarehouseUpdate_updateWarehouse_warehouse_address { + __typename: "Address"; + city: string; + cityArea: string; + companyName: string; + country: WarehouseUpdate_updateWarehouse_warehouse_address_country; + countryArea: string; + firstName: string; + id: string; + lastName: string; + phone: string | null; + postalCode: string; + streetAddress1: string; + streetAddress2: string; +} + +export interface WarehouseUpdate_updateWarehouse_warehouse { + __typename: "Warehouse"; + id: string; + name: string; + shippingZones: WarehouseUpdate_updateWarehouse_warehouse_shippingZones; + address: WarehouseUpdate_updateWarehouse_warehouse_address; +} + +export interface WarehouseUpdate_updateWarehouse { + __typename: "WarehouseUpdate"; + errors: WarehouseUpdate_updateWarehouse_errors[] | null; + warehouse: WarehouseUpdate_updateWarehouse_warehouse | null; +} + +export interface WarehouseUpdate { + updateWarehouse: WarehouseUpdate_updateWarehouse | null; +} + +export interface WarehouseUpdateVariables { + id: string; + input: WarehouseUpdateInput; +} diff --git a/src/warehouses/views/WarehouseDetails/WarehouseDetails.tsx b/src/warehouses/views/WarehouseDetails/WarehouseDetails.tsx new file mode 100644 index 000000000..792ee6715 --- /dev/null +++ b/src/warehouses/views/WarehouseDetails/WarehouseDetails.tsx @@ -0,0 +1,84 @@ +import React from "react"; +import { useIntl } from "react-intl"; + +import WarehouseDetailsPage from "@saleor/warehouses/components/WarehouseDetailsPage"; +import useNavigator from "@saleor/hooks/useNavigator"; +import { + warehouseListUrl, + WarehouseUrlQueryParams +} from "@saleor/warehouses/urls"; +import { useWarehouseDetails } from "@saleor/warehouses/queries"; +import { commonMessages } from "@saleor/intl"; +import useNotifier from "@saleor/hooks/useNotifier"; +import { maybe, findValueInEnum, getMutationStatus } from "@saleor/misc"; +import { CountryCode } from "@saleor/types/globalTypes"; +import useShop from "@saleor/hooks/useShop"; +import { WindowTitle } from "@saleor/components/WindowTitle"; +import { useWarehouseUpdate } from "@saleor/warehouses/mutations"; +import { shippingZoneUrl } from "@saleor/shipping/urls"; + +export interface WarehouseDetailsProps { + id: string; + params: WarehouseUrlQueryParams; +} + +const WarehouseDetails: React.FC = ({ id, params }) => { + const intl = useIntl(); + const navigate = useNavigator(); + const notify = useNotifier(); + const shop = useShop(); + const { data, loading } = useWarehouseDetails({ + displayLoader: true, + require: ["warehouse"], + variables: { id } + }); + const [updateWarehouse, updateWarehouseOpts] = useWarehouseUpdate({ + onCompleted: data => { + if (data.updateWarehouse.errors.length === 0) { + notify({ text: intl.formatMessage(commonMessages.savedChanges) }); + } + } + }); + const updateWarehouseTransitionState = getMutationStatus(updateWarehouseOpts); + + return ( + <> + data.warehouse.name)} /> + navigate(warehouseListUrl())} + disabled={loading || updateWarehouseOpts.loading} + errors={maybe( + () => updateWarehouseOpts.data.updateWarehouse.errors, + [] + )} + saveButtonBarState={updateWarehouseTransitionState} + shop={shop} + warehouse={maybe(() => data.warehouse)} + onShippingZoneClick={id => navigate(shippingZoneUrl(id))} + onSubmit={data => + updateWarehouse({ + variables: { + id, + input: { + address: { + city: data.city, + cityArea: data.cityArea, + country: findValueInEnum(data.country, CountryCode), + countryArea: data.countryArea, + phone: data.phone, + postalCode: data.postalCode, + streetAddress1: data.streetAddress1, + streetAddress2: data.streetAddress2 + }, + name: data.name + } + } + }) + } + /> + > + ); +}; + +WarehouseDetails.displayName = "WarehouseDetails"; +export default WarehouseDetails; diff --git a/src/warehouses/views/WarehouseDetails/index.ts b/src/warehouses/views/WarehouseDetails/index.ts new file mode 100644 index 000000000..a8e1f03bc --- /dev/null +++ b/src/warehouses/views/WarehouseDetails/index.ts @@ -0,0 +1,2 @@ +export { default } from "./WarehouseDetails"; +export * from "./WarehouseDetails";