This commit is contained in:
dominik-zeglen 2020-04-20 11:37:32 +02:00
parent 27a740ed6b
commit 9812277e9a
9 changed files with 580 additions and 2 deletions

View file

@ -18,8 +18,8 @@ export interface UseFormsetOutput<TData = object, TValue = any> {
set: (data: FormsetData<TData, TValue>) => void;
}
function useFormset<TData = object, TValue = any>(
initial: FormsetData<TData>
): UseFormsetOutput<TData> {
initial: FormsetData<TData, TValue>
): UseFormsetOutput<TData, TValue> {
const [data, setData] = useStateFromProps<FormsetData<TData, TValue>>(
initial || []
);

View file

@ -0,0 +1,28 @@
import { storiesOf } from "@storybook/react";
import React from "react";
import Decorator from "@saleor/storybook/Decorator";
import { warehouseList } from "@saleor/warehouses/fixtures";
import OrderFulfillPage, { OrderFulfillPageProps } from "./OrderFulfillPage";
import { orderToFulfill } from "./fixtures";
const props: OrderFulfillPageProps = {
disabled: false,
onBack: () => undefined,
onSubmit: () => undefined,
order: orderToFulfill,
saveButtonBar: "default",
warehouses: warehouseList
};
storiesOf("Views / Orders / Fulfill order", module)
.addDecorator(Decorator)
.add("default", () => <OrderFulfillPage {...props} />)
.add("loading", () => (
<OrderFulfillPage
{...props}
disabled={true}
order={undefined}
warehouses={undefined}
/>
));

View file

@ -0,0 +1,270 @@
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import Card from "@material-ui/core/Card";
import Typography from "@material-ui/core/Typography";
import TableBody from "@material-ui/core/TableBody";
import TableCell from "@material-ui/core/TableCell";
import TableHead from "@material-ui/core/TableHead";
import TableRow from "@material-ui/core/TableRow";
import TextField from "@material-ui/core/TextField";
import classNames from "classnames";
import useFormset, { FormsetData } from "@saleor/hooks/useFormset";
import { StockInput } from "@saleor/types/globalTypes";
import { WarehouseFragment } from "@saleor/warehouses/types/WarehouseFragment";
import TableCellAvatar, {
AVATAR_MARGIN
} from "@saleor/components/TableCellAvatar";
import Container from "@saleor/components/Container";
import PageHeader from "@saleor/components/PageHeader";
import SaveButtonBar from "@saleor/components/SaveButtonBar";
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
import Form from "@saleor/components/Form";
import { OrderFulfillData_order } from "@saleor/orders/types/OrderFulfillData";
import CardTitle from "@saleor/components/CardTitle";
import ResponsiveTable from "@saleor/components/ResponsiveTable";
import makeStyles from "@material-ui/core/styles/makeStyles";
const useStyles = makeStyles(
theme => ({
colName: {
width: 300
},
colNameLabel: {
marginLeft: AVATAR_MARGIN
},
colQuantity: {
textAlign: "right",
width: 200
},
colQuantityContent: {
alignItems: "center",
display: "inline-flex"
},
colQuantityTotal: {
textAlign: "right",
width: 180
},
colSku: {
textAlign: "right",
width: 120
},
error: {
color: theme.palette.error.main
},
quantityInput: {
width: "4rem"
},
remainingQuantity: {
marginLeft: theme.spacing(),
paddingTop: 14
},
table: {
"&&": {
tableLayout: "fixed"
}
}
}),
{ name: "OrderFulfillPage" }
);
interface OrderFulfillFormData {
sendInfo: boolean;
}
interface OrderFulfillSubmitData extends OrderFulfillFormData {
items: FormsetData<null, StockInput[]>;
}
export interface OrderFulfillPageProps {
disabled: boolean;
order: OrderFulfillData_order;
saveButtonBar: ConfirmButtonTransitionState;
warehouses: WarehouseFragment[];
onBack: () => undefined;
onSubmit: (data: OrderFulfillSubmitData) => void;
}
const initialFormData: OrderFulfillFormData = {
sendInfo: true
};
const OrderFulfillPage: React.FC<OrderFulfillPageProps> = ({
disabled,
order,
saveButtonBar,
warehouses,
onBack,
onSubmit
}) => {
const intl = useIntl();
const classes = useStyles({});
const { change, data: formsetData } = useFormset<null, StockInput[]>(
order?.lines.map(line => ({
data: null,
id: line.id,
label: line.variant.attributes
.map(attribute =>
attribute.values
.map(attributeValue => attributeValue.name)
.join(" , ")
)
.join(" / "),
value: line.variant.stocks.map(stock => ({
quantity: 0,
warehouse: stock.warehouse.id
}))
}))
);
const handleSubmit = (formData: OrderFulfillFormData) =>
onSubmit({
...formData,
items: formsetData
});
return (
<Container>
<PageHeader
title={intl.formatMessage(
{
defaultMessage: "Order no. {orderNumber} - Add Fulfillment",
description: "page header"
},
{
orderNumber: order?.number
}
)}
/>
<Form initial={initialFormData} onSubmit={handleSubmit}>
{({ change, data, submit }) => (
<>
<Card>
<CardTitle
title={intl.formatMessage({
defaultMessage: "Items ready to ship",
description: "header"
})}
/>
<ResponsiveTable className={classes.table}>
<TableHead>
<TableRow>
<TableCell className={classes.colName}>
<span className={classes.colNameLabel}>
<FormattedMessage defaultMessage="Product name" />
</span>
</TableCell>
<TableCell className={classes.colSku}>
<FormattedMessage
defaultMessage="SKU"
description="product's sku"
/>
</TableCell>
{warehouses.map(warehouse => (
<TableCell
key={warehouse.id}
className={classes.colQuantity}
>
{warehouse.name}
</TableCell>
))}
<TableCell className={classes.colQuantityTotal}>
<FormattedMessage
defaultMessage="Quantity to fulfill"
description="quantity of fulfilled products"
/>
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{order?.lines.map((line, lineIndex) => {
const remainingQuantity =
line.quantity - line.quantityFulfilled;
return (
<TableRow key={line.id}>
<TableCellAvatar
className={classes.colName}
thumbnail={line?.thumbnail?.url}
>
{line.productName}
</TableCellAvatar>
<TableCell className={classes.colSku}>
{line.variant.sku}
</TableCell>
{warehouses.map(warehouse => {
const warehouseStock = line.variant.stocks.find(
stock => stock.warehouse.id === warehouse.id
);
if (!warehouseStock) {
return (
<TableCell
className={classNames(
classes.colQuantity,
classes.error
)}
>
<FormattedMessage
defaultMessage="No Stock"
description="no variant stock in warehouse"
/>
</TableCell>
);
}
return (
<TableCell className={classes.colQuantity}>
<div className={classes.colQuantityContent}>
<TextField
type="number"
inputProps={{
max: warehouseStock.quantity,
style: { textAlign: "right" }
}}
className={classes.quantityInput}
value={
formsetData[lineIndex].value[0].quantity
}
onChange={event => undefined}
error={
remainingQuantity <
formsetData[lineIndex].value[0]
.quantity ||
formsetData[lineIndex].value[0].quantity >
warehouseStock.quantity
}
/>
<div className={classes.remainingQuantity}>
/ {warehouseStock.quantity}
</div>
</div>
</TableCell>
);
})}
<TableCell className={classes.colQuantityTotal}>
{formsetData[lineIndex].value.reduce(
(acc, stock) => acc + stock.quantity,
0
)}{" "}
/ {remainingQuantity}
</TableCell>
</TableRow>
);
})}
</TableBody>
</ResponsiveTable>
</Card>
<SaveButtonBar
disabled={disabled}
state={saveButtonBar}
onSave={submit}
onCancel={onBack}
/>
</>
)}
</Form>
</Container>
);
};
OrderFulfillPage.displayName = "OrderFulfillPage";
export default OrderFulfillPage;

View file

@ -0,0 +1,163 @@
/* eslint-disable sort-keys */
import { OrderFulfillData_order } from "@saleor/orders/types/OrderFulfillData";
import { warehouseList } from "@saleor/warehouses/fixtures";
import * as placeholderImage from "@assets/images/sample-product.jpg";
export const orderToFulfill: OrderFulfillData_order = {
__typename: "Order",
id: "T3JkZXI6Mg==",
lines: [
{
__typename: "OrderLine",
id: "T3JkZXJMaW5lOjQ=",
isShippingRequired: true,
productName: "T-Shirt",
quantity: 3,
quantityFulfilled: 1,
variant: {
__typename: "ProductVariant",
id: "UHJvZHVjdFZhcmlhbnQ6Mjk2",
name: "S",
sku: "62783187",
attributes: [
{
__typename: "SelectedAttribute",
values: [
{
__typename: "AttributeValue",
id: "QXR0cmlidXRlVmFsdWU6MzY=",
name: "S"
}
]
}
],
stocks: [
{
__typename: "Stock",
id: "U3RvY2s6NTIy",
warehouse: warehouseList[0],
quantity: 1217
},
{
__typename: "Stock",
id: "U3RvY2s6NTIx",
warehouse: warehouseList[1],
quantity: 1217
},
{
__typename: "Stock",
id: "U3RvY2s6NTIz",
warehouse: warehouseList[2],
quantity: 1217
},
{
__typename: "Stock",
id: "U3RvY2s6NTI0",
warehouse: warehouseList[3],
quantity: 1220
}
]
},
thumbnail: {
__typename: "Image",
url: placeholderImage
}
},
{
__typename: "OrderLine",
id: "T3JkZXJMaW5lOjU=",
isShippingRequired: true,
productName: "Lemon Juice",
quantity: 4,
quantityFulfilled: 0,
variant: {
__typename: "ProductVariant",
id: "UHJvZHVjdFZhcmlhbnQ6MTgx",
name: "2.5l",
sku: "998323583",
attributes: [
{
__typename: "SelectedAttribute",
values: [
{
__typename: "AttributeValue",
id: "QXR0cmlidXRlVmFsdWU6NjE=",
name: "2.5l"
}
]
}
],
stocks: [
{
__typename: "Stock",
id: "U3RvY2s6NTI=",
warehouse: warehouseList[1],
quantity: 760
},
{
__typename: "Stock",
id: "U3RvY2s6NTE=",
warehouse: warehouseList[2],
quantity: 760
},
{
__typename: "Stock",
id: "U3RvY2s6NTM=",
warehouse: warehouseList[3],
quantity: 760
}
]
},
thumbnail: {
__typename: "Image",
url: placeholderImage
}
},
{
__typename: "OrderLine",
id: "T3JkZXJMaW5lOjY=",
isShippingRequired: true,
productName: "Orange Juice",
quantity: 3,
quantityFulfilled: 2,
variant: {
__typename: "ProductVariant",
id: "UHJvZHVjdFZhcmlhbnQ6MTgy",
name: "5l",
sku: "998323584",
attributes: [
{
__typename: "SelectedAttribute",
values: [
{
__typename: "AttributeValue",
id: "QXR0cmlidXRlVmFsdWU6NjI=",
name: "5l"
}
]
}
],
stocks: [
{
__typename: "Stock",
id: "U3RvY2s6NTc=",
warehouse: warehouseList[0],
quantity: 587
},
{
__typename: "Stock",
id: "U3RvY2s6NTY=",
warehouse: warehouseList[2],
quantity: 587
}
]
},
thumbnail: {
__typename: "Image",
url: placeholderImage
}
}
],
number: "9123"
};

View file

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

View file

@ -13,6 +13,10 @@ import {
SearchOrderVariant as SearchOrderVariantType,
SearchOrderVariantVariables
} from "./types/SearchOrderVariant";
import {
OrderFulfillData,
OrderFulfillDataVariables
} from "./types/OrderFulfillData";
export const fragmentOrderEvent = gql`
fragment OrderEventFragment on OrderEvent {
@ -334,3 +338,44 @@ export const useOrderVariantSearch = makeTopLevelSearch<
SearchOrderVariantType,
SearchOrderVariantVariables
>(searchOrderVariant);
const orderFulfillData = gql`
query OrderFulfillData($orderId: ID!) {
order(id: $orderId) {
id
lines {
id
isShippingRequired
productName
quantity
quantityFulfilled
variant {
id
name
sku
attributes {
values {
id
name
}
}
stocks {
id
warehouse {
id
}
quantity
}
}
thumbnail(size: 64) {
url
}
}
number
}
}
`;
export const useOrderFulfillData = makeQuery<
OrderFulfillData,
OrderFulfillDataVariables
>(orderFulfillData);

View file

@ -0,0 +1,70 @@
/* tslint:disable */
/* eslint-disable */
// This file was automatically generated and should not be edited.
// ====================================================
// GraphQL query operation: OrderFulfillData
// ====================================================
export interface OrderFulfillData_order_lines_variant_attributes_values {
__typename: "AttributeValue";
id: string;
name: string | null;
}
export interface OrderFulfillData_order_lines_variant_attributes {
__typename: "SelectedAttribute";
values: (OrderFulfillData_order_lines_variant_attributes_values | null)[];
}
export interface OrderFulfillData_order_lines_variant_stocks_warehouse {
__typename: "Warehouse";
id: string;
}
export interface OrderFulfillData_order_lines_variant_stocks {
__typename: "Stock";
id: string;
warehouse: OrderFulfillData_order_lines_variant_stocks_warehouse;
quantity: number;
}
export interface OrderFulfillData_order_lines_variant {
__typename: "ProductVariant";
id: string;
name: string;
sku: string;
attributes: OrderFulfillData_order_lines_variant_attributes[];
stocks: (OrderFulfillData_order_lines_variant_stocks | null)[] | null;
}
export interface OrderFulfillData_order_lines_thumbnail {
__typename: "Image";
url: string;
}
export interface OrderFulfillData_order_lines {
__typename: "OrderLine";
id: string;
isShippingRequired: boolean;
productName: string;
quantity: number;
quantityFulfilled: number;
variant: OrderFulfillData_order_lines_variant | null;
thumbnail: OrderFulfillData_order_lines_thumbnail | null;
}
export interface OrderFulfillData_order {
__typename: "Order";
id: string;
lines: (OrderFulfillData_order_lines | null)[];
number: string | null;
}
export interface OrderFulfillData {
order: OrderFulfillData_order | null;
}
export interface OrderFulfillDataVariables {
orderId: string;
}

View file