Add warehouse details

This commit is contained in:
dominik-zeglen 2020-01-30 16:37:22 +01:00
parent c9afddd050
commit 8445d2d815
16 changed files with 561 additions and 32 deletions

View file

@ -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"
},

View file

@ -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;

View file

@ -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", () => <WarehouseCreatePage {...props} />)
.add("loading", () => (
<WarehouseCreatePage {...props} service={undefined} disabled={true} />
))
.add("loading", () => <WarehouseCreatePage {...props} disabled={true} />)
.add("form errors", () => (
<WarehouseCreatePage
{...props}

View file

@ -0,0 +1,48 @@
import { storiesOf } from "@storybook/react";
import React from "react";
import { address } from "@saleor/fixtures";
import Decorator from "@saleor/storybook/Decorator";
import { formError } from "@saleor/storybook/misc";
import { warehouseList } from "../../fixtures";
import WarehouseDetailsPage, {
WarehouseDetailsPageProps,
WarehouseDetailsPageFormData
} from "./WarehouseDetailsPage";
const props: WarehouseDetailsPageProps = {
disabled: false,
errors: [],
onBack: () => undefined,
onSubmit: () => undefined,
saveButtonBarState: "default",
warehouse: {
...warehouseList[0],
address
}
};
storiesOf("Views / Warehouses / Warehouse details", module)
.addDecorator(Decorator)
.add("default", () => <WarehouseDetailsPage {...props} />)
.add("loading", () => (
<WarehouseDetailsPage {...props} warehouse={undefined} disabled={true} />
))
.add("form errors", () => (
<WarehouseDetailsPage
{...props}
errors={([
"name",
"city",
"cityArea",
"companyName",
"country",
"countryArea",
"phone",
"postalCode",
"streetAddress1",
"streetAddress2"
] as Array<keyof WarehouseDetailsPageFormData>).map(field =>
formError(field)
)}
/>
));

View file

@ -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<WarehouseDetailsPageProps> = ({
disabled,
errors: apiErrors,
saveButtonBarState,
shop,
warehouse,
onBack,
onShippingZoneClick,
onSubmit
}) => {
const intl = useIntl();
const [displayCountry, setDisplayCountry] = useStateFromProps("");
const {
errors: validationErrors,
submit: handleSubmit
} = useAddressValidation<WarehouseDetailsPageFormData>(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 (
<Form
initial={initialForm}
errors={[...apiErrors, ...validationErrors]}
onSubmit={handleSubmit}
>
{({ change, data, errors, submit }) => {
const countryChoices = mapCountriesToChoices(
maybe(() => shop.countries, [])
);
const handleCountryChange = createSingleAutocompleteSelectHandler(
change,
setDisplayCountry,
countryChoices
);
return (
<Container>
<AppHeader onBack={onBack}>
<FormattedMessage {...sectionNames.warehouses} />
</AppHeader>
<PageHeader title={maybe(() => warehouse.name)} />
<Grid>
<div>
<WarehouseInfo
data={data}
disabled={disabled}
errors={errors}
onChange={change}
/>
<CardSpacer />
<CompanyAddressInput
countries={countryChoices}
data={data}
disabled={disabled}
displayCountry={displayCountry}
errors={errors}
header={intl.formatMessage({
defaultMessage: "Address Information",
description: "warehouse"
})}
onChange={change}
onCountryChange={handleCountryChange}
/>
</div>
<div>
<WarehouseZones
zones={maybe(() =>
warehouse.shippingZones.edges.map(edge => edge.node)
)}
onShippingZoneClick={onShippingZoneClick}
/>
</div>
</Grid>
<SaveButtonBar
disabled={disabled}
onCancel={onBack}
onSave={submit}
state={saveButtonBarState}
/>
</Container>
);
}}
</Form>
);
};
WarehouseDetailsPage.displayName = "WarehouseDetailsPage";
export default WarehouseDetailsPage;

View file

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

View file

@ -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";

View file

@ -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<WarehouseInfoProps> = ({
zones,
onShippingZoneClick
}) => {
const classes = useStyles({});
const intl = useIntl();
return (
<Card>
<CardTitle
title={intl.formatMessage({
defaultMessage: "Shipping Zones",
description: "zones that warehouse sends to"
})}
/>
<CardContent>
{renderCollection(
zones,
zone =>
maybe(
() => (
<div className={classes.link} key={zone.id}>
<Link underline onClick={() => onShippingZoneClick(zone.id)}>
{zone.name}
</Link>
</div>
),
<Skeleton />
),
() => (
<Typography color="textSecondary">
<FormattedMessage defaultMessage="This warehouse has no shipping zones assigned." />
</Typography>
)
)}
</CardContent>
</Card>
);
};
WarehouseInfo.displayName = "WarehouseInfo";
export default WarehouseInfo;

View file

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

View file

@ -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<RouteComponentProps> = ({ location }) => {
return <WarehouseListComponent 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}
// />
// );
// };
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();
@ -54,7 +51,7 @@ export const WarehouseSection: React.FC = () => {
<Switch>
<Route exact path={warehouseListPath} component={WarehouseList} />
<Route exact path={warehouseAddPath} component={WarehouseCreate} />
{/* <Route path={warehousePath(":id")} component={WarehouseDetails} /> */}
<Route path={warehousePath(":id")} component={WarehouseDetails} />
</Switch>
</>
);

View file

@ -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);

View file

@ -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);

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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<WarehouseDetailsProps> = ({ 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 (
<>
<WindowTitle title={maybe(() => data.warehouse.name)} />
<WarehouseDetailsPage
onBack={() => 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;

View file

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