wip
This commit is contained in:
parent
27a740ed6b
commit
9812277e9a
9 changed files with 580 additions and 2 deletions
|
@ -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 || []
|
||||
);
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
));
|
270
src/orders/components/OrderFulfillPage/OrderFulfillPage.tsx
Normal file
270
src/orders/components/OrderFulfillPage/OrderFulfillPage.tsx
Normal 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;
|
163
src/orders/components/OrderFulfillPage/fixtures.ts
Normal file
163
src/orders/components/OrderFulfillPage/fixtures.ts
Normal 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"
|
||||
};
|
2
src/orders/components/OrderFulfillPage/index.ts
Normal file
2
src/orders/components/OrderFulfillPage/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from "./OrderFulfillPage";
|
||||
export { default } from "./OrderFulfillPage";
|
|
@ -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);
|
||||
|
|
70
src/orders/types/OrderFulfillData.ts
Normal file
70
src/orders/types/OrderFulfillData.ts
Normal 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;
|
||||
}
|
0
src/orders/views/OrderFulfill/OrderFulfill.tsx
Normal file
0
src/orders/views/OrderFulfill/OrderFulfill.tsx
Normal file
0
src/orders/views/OrderFulfill/index.ts
Normal file
0
src/orders/views/OrderFulfill/index.ts
Normal file
Loading…
Reference in a new issue