Add channel picker product details (#1355)

* Add channel picker product details (#1353)

* Enable channel picker on product details page

* Add channel picker to product details

* Simplify category details page

* Update snapshots and translations

* Update types

* Update testId, bump macaw-ui

* Update snapshots, types and schema
This commit is contained in:
Jakub Majorek 2021-09-03 14:27:34 +02:00 committed by GitHub
parent 9b2f5e3db2
commit d5a36e47fc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 1008 additions and 1487 deletions

View file

@ -1370,21 +1370,9 @@
"context": "number of products",
"string": "No. of Products"
},
"src_dot_categories_dot_components_dot_CategoryProductList_dot_1134347598": {
"context": "product price",
"string": "Price"
},
"src_dot_categories_dot_components_dot_CategoryProductList_dot_1657559629": {
"string": "No products found"
},
"src_dot_categories_dot_components_dot_CategoryProductList_dot_1952810469": {
"context": "product type",
"string": "Type"
},
"src_dot_categories_dot_components_dot_CategoryProductList_dot_3326160357": {
"context": "availability status",
"string": "Availability"
},
"src_dot_categories_dot_components_dot_CategoryProductList_dot_636461959": {
"context": "product",
"string": "Name"
@ -1393,6 +1381,10 @@
"context": "button",
"string": "Add product"
},
"src_dot_categories_dot_components_dot_CategoryProducts_dot_4078580795": {
"context": "button",
"string": "View products"
},
"src_dot_categories_dot_components_dot_CategoryProducts_dot_4164156574": {
"context": "header",
"string": "Products in {categoryName}"

2
package-lock.json generated
View file

@ -1,6 +1,6 @@
{
"name": "saleor-dashboard",
"version": "3.0.0-b.5",
"version": "3.0.0-b.11",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,5 @@
import { TableBody, TableCell, TableFooter, TableRow } from "@material-ui/core";
import { ChannelsAvailabilityDropdown } from "@saleor/components/ChannelsAvailabilityDropdown";
import Checkbox from "@saleor/components/Checkbox";
import MoneyRange from "@saleor/components/MoneyRange";
import ResponsiveTable from "@saleor/components/ResponsiveTable";
import Skeleton from "@saleor/components/Skeleton";
import TableCellAvatar from "@saleor/components/TableCellAvatar";
@ -10,7 +8,7 @@ import TableHead from "@saleor/components/TableHead";
import TablePagination from "@saleor/components/TablePagination";
import { makeStyles } from "@saleor/macaw-ui";
import { maybe, renderCollection } from "@saleor/misc";
import { ChannelProps, ListActions, ListProps } from "@saleor/types";
import { ListActions, ListProps } from "@saleor/types";
import React from "react";
import { FormattedMessage } from "react-intl";
@ -21,15 +19,6 @@ const useStyles = makeStyles(
[theme.breakpoints.up("lg")]: {
colName: {
width: "auto"
},
colPrice: {
width: 300
},
colPublished: {
width: 200
},
colType: {
width: 200
}
},
colFill: {
@ -40,11 +29,6 @@ const useStyles = makeStyles(
colNameHeader: {
marginLeft: AVATAR_MARGIN
},
colPrice: {
textAlign: "right"
},
colPublished: {},
colType: {},
link: {
cursor: "pointer"
},
@ -66,17 +50,12 @@ const useStyles = makeStyles(
}
);
interface CategoryProductListProps
extends ListProps,
ListActions,
ChannelProps {
channelsCount: number;
interface CategoryProductListProps extends ListProps, ListActions {
products: CategoryDetails_category_products_edges_node[];
}
export const CategoryProductList: React.FC<CategoryProductListProps> = props => {
const {
channelsCount,
disabled,
isChecked,
pageInfo,
@ -87,13 +66,12 @@ export const CategoryProductList: React.FC<CategoryProductListProps> = props =>
toolbar,
onNextPage,
onPreviousPage,
onRowClick,
selectedChannelId
onRowClick
} = props;
const classes = useStyles(props);
const numberOfColumns = 5;
const numberOfColumns = 2;
return (
<div className={classes.tableContainer}>
@ -101,9 +79,6 @@ export const CategoryProductList: React.FC<CategoryProductListProps> = props =>
<colgroup>
<col />
<col className={classes.colName} />
<col className={classes.colType} />
<col className={classes.colPublished} />
<col className={classes.colPrice} />
</colgroup>
<TableHead
colSpan={numberOfColumns}
@ -118,24 +93,6 @@ export const CategoryProductList: React.FC<CategoryProductListProps> = props =>
<FormattedMessage defaultMessage="Name" description="product" />
</span>
</TableCell>
<TableCell className={classes.colType}>
<FormattedMessage
defaultMessage="Type"
description="product type"
/>
</TableCell>
<TableCell className={classes.colPublished}>
<FormattedMessage
defaultMessage="Availability"
description="availability status"
/>
</TableCell>
<TableCell className={classes.colPrice}>
<FormattedMessage
defaultMessage="Price"
description="product price"
/>
</TableCell>
</TableHead>
<TableFooter>
<TableRow>
@ -155,9 +112,6 @@ export const CategoryProductList: React.FC<CategoryProductListProps> = props =>
products,
product => {
const isSelected = product ? isChecked(product.id) : false;
const channel = product?.channelListings.find(
listing => listing.channel.id === selectedChannelId
);
return (
<TableRow
@ -182,35 +136,6 @@ export const CategoryProductList: React.FC<CategoryProductListProps> = props =>
>
{product ? product.name : <Skeleton />}
</TableCellAvatar>
<TableCell className={classes.colType}>
{product && product.productType ? (
product.productType.name
) : (
<Skeleton />
)}
</TableCell>
<TableCell className={classes.colPublished}>
{product && !product?.channelListings?.length ? (
"-"
) : product?.channelListings !== undefined ? (
<ChannelsAvailabilityDropdown
allChannelsCount={channelsCount}
channels={product?.channelListings}
/>
) : (
<Skeleton />
)}
</TableCell>
<TableCell className={classes.colPrice}>
{product?.channelListings ? (
<MoneyRange
from={channel?.pricing?.priceRange?.start?.net}
to={channel?.pricing?.priceRange?.stop?.net}
/>
) : (
<Skeleton />
)}
</TableCell>
</TableRow>
);
},

View file

@ -1,25 +1,23 @@
import { Button, Card } from "@material-ui/core";
import HorizontalSpacer from "@saleor/apps/components/HorizontalSpacer";
import CardTitle from "@saleor/components/CardTitle";
import { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField";
import { InternalLink } from "@saleor/components/InternalLink";
import { productListUrl } from "@saleor/products/urls";
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { ChannelProps, ListActions, PageListProps } from "../../../types";
import { ListActions, PageListProps } from "../../../types";
import { CategoryDetails_category_products_edges_node } from "../../types/CategoryDetails";
import CategoryProductList from "../CategoryProductList";
import { useStyles } from "./styles";
interface CategoryProductsProps
extends PageListProps,
ListActions,
ChannelProps {
interface CategoryProductsProps extends PageListProps, ListActions {
products: CategoryDetails_category_products_edges_node[];
channelChoices: SingleAutocompleteChoiceType[];
channelsCount: number;
categoryName: string;
categoryId: string;
}
export const CategoryProducts: React.FC<CategoryProductsProps> = ({
channelsCount,
products,
disabled,
pageInfo,
@ -27,15 +25,16 @@ export const CategoryProducts: React.FC<CategoryProductsProps> = ({
onNextPage,
onPreviousPage,
onRowClick,
categoryId,
categoryName,
isChecked,
selected,
selectedChannelId,
toggle,
toggleAll,
toolbar
}) => {
const intl = useIntl();
const classes = useStyles();
return (
<Card>
@ -48,22 +47,39 @@ export const CategoryProducts: React.FC<CategoryProductsProps> = ({
{ categoryName }
)}
toolbar={
<Button
color="primary"
variant="text"
onClick={onAdd}
data-test-id="addProducts"
>
<FormattedMessage
defaultMessage="Add product"
description="button"
/>
</Button>
<div className={classes.toolbar}>
<InternalLink
to={productListUrl({
categories: [categoryId]
})}
>
<Button
color="primary"
variant="text"
data-test-id="viewProducts"
>
<FormattedMessage
defaultMessage="View products"
description="button"
/>
</Button>
</InternalLink>
<HorizontalSpacer />
<Button
color="primary"
variant="text"
onClick={onAdd}
data-test-id="addProducts"
>
<FormattedMessage
defaultMessage="Add product"
description="button"
/>
</Button>
</div>
}
/>
<CategoryProductList
channelsCount={channelsCount}
selectedChannelId={selectedChannelId}
products={products}
disabled={disabled}
pageInfo={pageInfo}

View file

@ -0,0 +1,10 @@
import { makeStyles } from "@saleor/macaw-ui";
export const useStyles = makeStyles(
() => ({
toolbar: {
display: "flex"
}
}),
{ name: "CategoryProducts" }
);

View file

@ -7,7 +7,6 @@ import Metadata from "@saleor/components/Metadata/Metadata";
import PageHeader from "@saleor/components/PageHeader";
import Savebar from "@saleor/components/Savebar";
import SeoForm from "@saleor/components/SeoForm";
import { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField";
import { Tab, TabContainer } from "@saleor/components/Tab";
import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment";
import { SubmitPromise } from "@saleor/hooks/useForm";
@ -17,7 +16,7 @@ import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { maybe } from "../../../misc";
import { ChannelProps, TabListActions } from "../../../types";
import { TabListActions } from "../../../types";
import CategoryDetailsForm from "../../components/CategoryDetailsForm";
import CategoryList from "../../components/CategoryList";
import {
@ -35,8 +34,7 @@ export enum CategoryPageTab {
}
export interface CategoryUpdatePageProps
extends TabListActions<"productListToolbar" | "subcategoryListToolbar">,
ChannelProps {
extends TabListActions<"productListToolbar" | "subcategoryListToolbar"> {
changeTab: (index: CategoryPageTab) => void;
currentTab: CategoryPageTab;
errors: ProductErrorFragment[];
@ -49,8 +47,6 @@ export interface CategoryUpdatePageProps
hasPreviousPage: boolean;
};
saveButtonBarState: ConfirmButtonTransitionState;
channelChoices: SingleAutocompleteChoiceType[];
channelsCount: number;
onImageDelete: () => void;
onSubmit: (data: CategoryUpdateData) => SubmitPromise;
onImageUpload(file: File);
@ -69,8 +65,6 @@ const ProductsTab = Tab(CategoryPageTab.products);
export const CategoryUpdatePage: React.FC<CategoryUpdatePageProps> = ({
changeTab,
channelChoices,
channelsCount,
currentTab,
category,
disabled,
@ -93,7 +87,6 @@ export const CategoryUpdatePage: React.FC<CategoryUpdatePageProps> = ({
isChecked,
productListToolbar,
selected,
selectedChannelId,
subcategoryListToolbar,
toggle,
toggleAll
@ -206,8 +199,7 @@ export const CategoryUpdatePage: React.FC<CategoryUpdatePageProps> = ({
)}
{currentTab === CategoryPageTab.products && (
<CategoryProducts
channelsCount={channelsCount}
channelChoices={channelChoices}
categoryId={category?.id}
categoryName={category?.name}
products={products}
disabled={disabled}
@ -219,7 +211,6 @@ export const CategoryUpdatePage: React.FC<CategoryUpdatePageProps> = ({
toggle={toggle}
toggleAll={toggleAll}
selected={selected}
selectedChannelId={selectedChannelId}
isChecked={isChecked}
toolbar={productListToolbar}
/>

View file

@ -3,7 +3,6 @@ import {
categoryFragment
} from "@saleor/fragments/categories";
import { pageInfoFragment } from "@saleor/fragments/pageInfo";
import { channelListingProductFragment } from "@saleor/fragments/products";
import makeQuery from "@saleor/hooks/makeQuery";
import gql from "graphql-tag";
@ -49,7 +48,6 @@ export const useRootCategoriesQuery = makeQuery<RootCategories, {}>(
);
export const categoryDetails = gql`
${channelListingProductFragment}
${categoryFragment}
${categoryDetailsFragment}
${pageInfoFragment}
@ -84,13 +82,6 @@ export const categoryDetails = gql`
thumbnail {
url
}
productType {
id
name
}
channelListings {
...ChannelListingProductFragment
}
}
}
}

View file

@ -80,70 +80,11 @@ export interface CategoryDetails_category_products_edges_node_thumbnail {
url: string;
}
export interface CategoryDetails_category_products_edges_node_productType {
__typename: "ProductType";
id: string;
name: string;
}
export interface CategoryDetails_category_products_edges_node_channelListings_channel {
__typename: "Channel";
id: string;
name: string;
currencyCode: string;
}
export interface CategoryDetails_category_products_edges_node_channelListings_pricing_priceRange_start_net {
__typename: "Money";
amount: number;
currency: string;
}
export interface CategoryDetails_category_products_edges_node_channelListings_pricing_priceRange_start {
__typename: "TaxedMoney";
net: CategoryDetails_category_products_edges_node_channelListings_pricing_priceRange_start_net;
}
export interface CategoryDetails_category_products_edges_node_channelListings_pricing_priceRange_stop_net {
__typename: "Money";
amount: number;
currency: string;
}
export interface CategoryDetails_category_products_edges_node_channelListings_pricing_priceRange_stop {
__typename: "TaxedMoney";
net: CategoryDetails_category_products_edges_node_channelListings_pricing_priceRange_stop_net;
}
export interface CategoryDetails_category_products_edges_node_channelListings_pricing_priceRange {
__typename: "TaxedMoneyRange";
start: CategoryDetails_category_products_edges_node_channelListings_pricing_priceRange_start | null;
stop: CategoryDetails_category_products_edges_node_channelListings_pricing_priceRange_stop | null;
}
export interface CategoryDetails_category_products_edges_node_channelListings_pricing {
__typename: "ProductPricingInfo";
priceRange: CategoryDetails_category_products_edges_node_channelListings_pricing_priceRange | null;
}
export interface CategoryDetails_category_products_edges_node_channelListings {
__typename: "ProductChannelListing";
isPublished: boolean;
publicationDate: any | null;
isAvailableForPurchase: boolean | null;
availableForPurchase: any | null;
visibleInListings: boolean;
channel: CategoryDetails_category_products_edges_node_channelListings_channel;
pricing: CategoryDetails_category_products_edges_node_channelListings_pricing | null;
}
export interface CategoryDetails_category_products_edges_node {
__typename: "Product";
id: string;
name: string;
thumbnail: CategoryDetails_category_products_edges_node_thumbnail | null;
productType: CategoryDetails_category_products_edges_node_productType;
channelListings: CategoryDetails_category_products_edges_node_channelListings[] | null;
}
export interface CategoryDetails_category_products_edges {

View file

@ -1,9 +1,7 @@
import { DialogContentText, IconButton } from "@material-ui/core";
import DeleteIcon from "@material-ui/icons/Delete";
import ActionDialog from "@saleor/components/ActionDialog";
import useAppChannel from "@saleor/components/AppLayout/AppChannelContext";
import NotFoundPage from "@saleor/components/NotFoundPage";
import Skeleton from "@saleor/components/Skeleton";
import { WindowTitle } from "@saleor/components/WindowTitle";
import useBulkActions from "@saleor/hooks/useBulkActions";
import useNavigator from "@saleor/hooks/useNavigator";
@ -14,7 +12,7 @@ import usePaginator, {
import { commonMessages, errorMessages } from "@saleor/intl";
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
import createMetadataUpdateHandler from "@saleor/utils/handlers/metadataUpdateHandler";
import { mapEdgesToItems, mapNodeToChoice } from "@saleor/utils/maps";
import { mapEdgesToItems } from "@saleor/utils/maps";
import {
useMetadataUpdate,
usePrivateMetadataUpdate
@ -81,10 +79,6 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
variables: { ...paginationState, id }
});
const { availableChannels, channel } = useAppChannel(false);
const channelChoices = mapNodeToChoice(availableChannels);
const category = data?.category;
if (category === null) {
@ -209,16 +203,10 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
variables => updatePrivateMetadata({ variables })
);
if (typeof channel === "undefined") {
return <Skeleton />;
}
return (
<>
<WindowTitle title={maybe(() => data.category.name)} />
<CategoryUpdatePage
channelsCount={availableChannels.length}
channelChoices={channelChoices}
changeTab={changeTab}
currentTab={params.activeTab}
category={maybe(() => data.category)}
@ -260,7 +248,6 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
onSubmit={handleSubmit}
products={mapEdgesToItems(data?.category?.products)}
saveButtonBarState={updateResult.status}
selectedChannelId={channel?.id}
subcategories={mapEdgesToItems(data?.category?.children)}
subcategoryListToolbar={
<IconButton

View file

@ -0,0 +1,13 @@
import classNames from "classnames";
import React from "react";
import { Link, LinkProps } from "react-router-dom";
import { useStyles } from "./styles";
export const InternalLink: React.FC<LinkProps> = ({ className, ...props }) => {
const classes = useStyles();
return <Link className={classNames(classes.root, className)} {...props} />;
};
export default InternalLink;

View file

@ -0,0 +1 @@
export * from "./InternalLink";

View file

@ -0,0 +1,10 @@
import { makeStyles } from "@saleor/macaw-ui";
export const useStyles = makeStyles(
() => ({
root: {
textDecoration: "none"
}
}),
{ name: "InternalLink" }
);

View file

@ -11,6 +11,7 @@ import { WarehouseClickAndCollectOptionEnum } from "./../../types/globalTypes";
export interface OrderFulfillData_order_deliveryMethod_ShippingMethod {
__typename: "ShippingMethod";
id: string;
}
export interface OrderFulfillData_order_deliveryMethod_Warehouse {

View file

@ -103,6 +103,13 @@ export interface OrderFulfillmentApprove_orderFulfillmentApprove_order_events_us
lastName: string;
}
export interface OrderFulfillmentApprove_orderFulfillmentApprove_order_events_app {
__typename: "App";
id: string;
name: string | null;
appUrl: string | null;
}
export interface OrderFulfillmentApprove_orderFulfillmentApprove_order_events_lines_discount_amount {
__typename: "Money";
amount: number;
@ -157,6 +164,7 @@ export interface OrderFulfillmentApprove_orderFulfillmentApprove_order_events {
transactionReference: string | null;
type: OrderEventsEnum | null;
user: OrderFulfillmentApprove_orderFulfillmentApprove_order_events_user | null;
app: OrderFulfillmentApprove_orderFulfillmentApprove_order_events_app | null;
lines: (OrderFulfillmentApprove_orderFulfillmentApprove_order_events_lines | null)[] | null;
}

View file

@ -165,7 +165,7 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
productVariantCreateOpts
] = useVariantCreateMutation({});
const { availableChannels, channel } = useAppChannel(false);
const { availableChannels, channel } = useAppChannel();
const { data, loading, refetch } = useProductDetails({
displayLoader: true,
variables: {

View file

@ -73,7 +73,7 @@ const ShippingZoneDetails: React.FC<ShippingZoneDetailsProps> = ({
displayLoader: true,
variables: { id, ...paginationState }
});
const { availableChannels, channel } = useAppChannel(false);
const { availableChannels, channel } = useAppChannel();
const [openModal, closeModal] = createDialogActionHandlers<
ShippingZoneUrlDialog,

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
import placeholderImage from "@assets/images/placeholder255x255.png";
import { ProductErrorCode } from "@saleor/types/globalTypes";
import { mapEdgesToItems, mapNodeToChoice } from "@saleor/utils/maps";
import { mapEdgesToItems } from "@saleor/utils/maps";
import { storiesOf } from "@storybook/react";
import React from "react";
@ -14,13 +14,9 @@ import Decorator from "../../Decorator";
const category = categoryFixture(placeholderImage);
const channelChoices = mapNodeToChoice(mapEdgesToItems(category?.products));
const updateProps: Omit<CategoryUpdatePageProps, "classes"> = {
category,
changeTab: undefined,
channelChoices,
channelsCount: 2,
currentTab: CategoryPageTab.categories,
disabled: false,
errors: [],
@ -42,7 +38,6 @@ const updateProps: Omit<CategoryUpdatePageProps, "classes"> = {
productListToolbar: null,
products: mapEdgesToItems(category.products),
saveButtonBarState: "default",
selectedChannelId: "123",
subcategories: mapEdgesToItems(category.children),
subcategoryListToolbar: null,
...listActionsProps

View file

@ -1542,6 +1542,7 @@ export enum PaymentChargeStatusEnum {
export enum PermissionEnum {
HANDLE_PAYMENTS = "HANDLE_PAYMENTS",
IMPERSONATE_USER = "IMPERSONATE_USER",
MANAGE_APPS = "MANAGE_APPS",
MANAGE_CHANNELS = "MANAGE_CHANNELS",
MANAGE_CHECKOUTS = "MANAGE_CHECKOUTS",