Saleor 1712 dashboard unable to handle empty channel list (#924)
* Use query hook on home page * Handle no channel * Handle no channel on pages * Add navigate channels * Move messages * Refactor * Update storybook and locale * Remove comment * Refactor * Update storybook * Fix skip * Fix undefined channel * Update storybook
This commit is contained in:
parent
3f5cacb8a0
commit
d0be941ade
23 changed files with 272 additions and 406 deletions
|
@ -3023,6 +3023,9 @@
|
|||
"src_dot_home_dot_components_dot_HomeActivityCard_dot_placed": {
|
||||
"string": "Order #{orderId} was placed"
|
||||
},
|
||||
"src_dot_home_dot_components_dot_HomeNotificationTable_dot_createNewChannel": {
|
||||
"string": "Create new channel"
|
||||
},
|
||||
"src_dot_hooks_dot_3382262667": {
|
||||
"string": "Variant {name} has been set as default."
|
||||
},
|
||||
|
|
|
@ -4,6 +4,7 @@ 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";
|
||||
|
@ -207,6 +208,10 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
|
|||
variables => updatePrivateMetadata({ variables })
|
||||
);
|
||||
|
||||
if (typeof channel === "undefined") {
|
||||
return <Skeleton />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<WindowTitle title={maybe(() => data.category.name)} />
|
||||
|
@ -256,7 +261,7 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
|
|||
data.category.products.edges.map(edge => edge.node)
|
||||
)}
|
||||
saveButtonBarState={updateResult.status}
|
||||
selectedChannelId={channel.id}
|
||||
selectedChannelId={channel?.id}
|
||||
subcategories={maybe(() =>
|
||||
data.category.children.edges.map(edge => edge.node)
|
||||
)}
|
||||
|
|
|
@ -146,8 +146,8 @@ const CollectionList: React.FC<CollectionListProps> = props => {
|
|||
collections,
|
||||
collection => {
|
||||
const isSelected = collection ? isChecked(collection.id) : false;
|
||||
const channel = collection?.channelListings.find(
|
||||
listing => listing.channel.id === selectedChannelId
|
||||
const channel = collection?.channelListings?.find(
|
||||
listing => listing?.channel?.id === selectedChannelId
|
||||
);
|
||||
return (
|
||||
<TableRow
|
||||
|
@ -184,11 +184,13 @@ const CollectionList: React.FC<CollectionListProps> = props => {
|
|||
{collection && !collection?.channelListings?.length ? (
|
||||
"-"
|
||||
) : collection?.channelListings !== undefined ? (
|
||||
<ChannelsAvailabilityDropdown
|
||||
allChannelsCount={channelsCount}
|
||||
currentChannel={channel}
|
||||
channels={collection?.channelListings}
|
||||
/>
|
||||
channel ? (
|
||||
<ChannelsAvailabilityDropdown
|
||||
allChannelsCount={channelsCount}
|
||||
currentChannel={channel}
|
||||
channels={collection?.channelListings}
|
||||
/>
|
||||
) : null
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
|
|
|
@ -184,7 +184,7 @@ export const CollectionList: React.FC<CollectionListProps> = ({ params }) => {
|
|||
toggle={toggle}
|
||||
toggleAll={toggleAll}
|
||||
channelsCount={availableChannels?.length}
|
||||
selectedChannelId={channel.id}
|
||||
selectedChannelId={channel?.id}
|
||||
/>
|
||||
<ActionDialog
|
||||
open={params.action === "remove" && maybe(() => params.ids.length > 0)}
|
||||
|
|
|
@ -30,6 +30,7 @@ export const AppChannelProvider: React.FC = ({ children }) => {
|
|||
const { data: channelData, refetch } = useChannelsList({
|
||||
skip: !isAuthenticated
|
||||
});
|
||||
|
||||
const [isPickerActive, setPickerActive] = React.useState(false);
|
||||
React.useEffect(() => {
|
||||
if (!selectedChannel) {
|
||||
|
@ -38,9 +39,10 @@ export const AppChannelProvider: React.FC = ({ children }) => {
|
|||
}, [channelData]);
|
||||
|
||||
const availableChannels = channelData?.channels || [];
|
||||
const channel = availableChannels.find(
|
||||
channel => channel.id === selectedChannel
|
||||
);
|
||||
|
||||
const channel =
|
||||
channelData &&
|
||||
(availableChannels.find(channel => channel.id === selectedChannel) || null);
|
||||
|
||||
return (
|
||||
<AppChannelContext.Provider
|
||||
|
@ -57,6 +59,7 @@ export const AppChannelProvider: React.FC = ({ children }) => {
|
|||
</AppChannelContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
AppChannelProvider.displayName = "AppChannelProvider";
|
||||
|
||||
function useAppChannel(enablePicker = true): UseAppChannel {
|
||||
|
|
|
@ -215,12 +215,14 @@ const AppLayout: React.FC<AppLayoutProps> = ({ children }) => {
|
|||
.includes("mac")}
|
||||
onClick={() => setNavigatorVisibility(true)}
|
||||
/>
|
||||
<AppChannelSelect
|
||||
channels={availableChannels}
|
||||
disabled={!isPickerActive}
|
||||
selectedChannelId={channel.id}
|
||||
onChannelSelect={setChannel}
|
||||
/>
|
||||
{channel && (
|
||||
<AppChannelSelect
|
||||
channels={availableChannels}
|
||||
disabled={!isPickerActive}
|
||||
selectedChannelId={channel.id}
|
||||
onChannelSelect={setChannel}
|
||||
/>
|
||||
)}
|
||||
<UserChip
|
||||
isDarkThemeEnabled={isDark}
|
||||
user={user}
|
||||
|
|
|
@ -78,7 +78,7 @@ const SaleCreatePage: React.FC<SaleCreatePageProps> = ({
|
|||
triggerChange
|
||||
);
|
||||
const formDisabled = data.channelListings?.some(channel =>
|
||||
validatePrice(channel.discountValue)
|
||||
validatePrice(channel?.discountValue)
|
||||
);
|
||||
return (
|
||||
<Container>
|
||||
|
|
|
@ -51,12 +51,12 @@ const SaleSummary: React.FC<SaleSummaryProps> = ({
|
|||
sale.type === SaleType.FIXED && channel?.discountValue ? (
|
||||
<Money
|
||||
money={{
|
||||
amount: channel.discountValue,
|
||||
currency: channel.currency
|
||||
amount: channel?.discountValue,
|
||||
currency: channel?.currency
|
||||
}}
|
||||
/>
|
||||
) : channel?.discountValue ? (
|
||||
<Percent amount={channel.discountValue} />
|
||||
<Percent amount={channel?.discountValue} />
|
||||
) : (
|
||||
"-"
|
||||
)
|
||||
|
|
|
@ -69,12 +69,12 @@ const VoucherSummary: React.FC<VoucherSummaryProps> = ({
|
|||
channel?.discountValue ? (
|
||||
<Money
|
||||
money={{
|
||||
amount: channel.discountValue,
|
||||
currency: channel.channel.currencyCode
|
||||
amount: channel?.discountValue,
|
||||
currency: channel?.channel.currencyCode
|
||||
}}
|
||||
/>
|
||||
) : channel?.discountValue ? (
|
||||
<Percent amount={channel.discountValue} />
|
||||
<Percent amount={channel?.discountValue} />
|
||||
) : (
|
||||
"-"
|
||||
)
|
||||
|
|
|
@ -199,7 +199,7 @@ export const SaleList: React.FC<SaleListProps> = ({ params }) => {
|
|||
<DeleteIcon />
|
||||
</IconButton>
|
||||
}
|
||||
selectedChannelId={channel.id}
|
||||
selectedChannelId={channel?.id}
|
||||
/>
|
||||
<ActionDialog
|
||||
confirmButtonState={saleBulkDeleteOpts.status}
|
||||
|
|
|
@ -298,7 +298,7 @@ export const VoucherDetails: React.FC<VoucherDetailsProps> = ({
|
|||
...(updateChannelsOpts.data
|
||||
?.voucherChannelListingUpdate.errors || [])
|
||||
]}
|
||||
selectedChannelId={channel.id}
|
||||
selectedChannelId={channel?.id}
|
||||
pageInfo={pageInfo}
|
||||
onNextPage={loadNextPage}
|
||||
onPreviousPage={loadPreviousPage}
|
||||
|
|
|
@ -200,7 +200,7 @@ export const VoucherList: React.FC<VoucherListProps> = ({ params }) => {
|
|||
<DeleteIcon />
|
||||
</IconButton>
|
||||
}
|
||||
selectedChannelId={channel.id}
|
||||
selectedChannelId={channel?.id}
|
||||
/>
|
||||
<ActionDialog
|
||||
confirmButtonState={voucherBulkDeleteOpts.status}
|
||||
|
|
|
@ -11,7 +11,40 @@ import Skeleton from "@saleor/components/Skeleton";
|
|||
import { UserPermissionProps } from "@saleor/types";
|
||||
import { PermissionEnum } from "@saleor/types/globalTypes";
|
||||
import React from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { defineMessages, useIntl } from "react-intl";
|
||||
|
||||
const messages = defineMessages({
|
||||
createNewChannel: {
|
||||
defaultMessage: "Create new channel"
|
||||
},
|
||||
noOrders: {
|
||||
defaultMessage: "No orders ready to fulfill",
|
||||
id: "homeNotificationTableNoOrders"
|
||||
},
|
||||
noPaymentWaiting: {
|
||||
defaultMessage: "No payments waiting for capture",
|
||||
id: "homeNotificationsNoPayments"
|
||||
},
|
||||
noProductsOut: {
|
||||
defaultMessage: "No products out of stock",
|
||||
id: "homeNotificationsTableNoProducts"
|
||||
},
|
||||
orderReady: {
|
||||
defaultMessage:
|
||||
"{amount, plural,one {One order is ready to fulfill} other {{amount} Orders are ready to fulfill}}",
|
||||
id: "homeNotificationTableOrders"
|
||||
},
|
||||
paymentCapture: {
|
||||
defaultMessage:
|
||||
"{amount, plural,one {One payment to capture}other {{amount} Payments to capture}}",
|
||||
id: "homeNotificationTablePayments"
|
||||
},
|
||||
productOut: {
|
||||
defaultMessage:
|
||||
"{amount, plural,one {One product out of stock}other {{amount} Products out of stock}}",
|
||||
id: "homeNotificationTableProducts"
|
||||
}
|
||||
});
|
||||
|
||||
const useStyles = makeStyles(
|
||||
() => ({
|
||||
|
@ -33,28 +66,46 @@ interface HomeNotificationTableProps extends UserPermissionProps {
|
|||
ordersToCapture: number;
|
||||
ordersToFulfill: number;
|
||||
productsOutOfStock: number;
|
||||
onCreateNewChannelClick: () => void;
|
||||
onOrdersToFulfillClick: () => void;
|
||||
onOrdersToCaptureClick: () => void;
|
||||
onProductsOutOfStockClick: () => void;
|
||||
noChannel: boolean;
|
||||
}
|
||||
|
||||
const HomeNotificationTable: React.FC<HomeNotificationTableProps> = props => {
|
||||
const {
|
||||
onCreateNewChannelClick,
|
||||
onOrdersToCaptureClick,
|
||||
onOrdersToFulfillClick,
|
||||
onProductsOutOfStockClick,
|
||||
ordersToCapture,
|
||||
ordersToFulfill,
|
||||
productsOutOfStock,
|
||||
userPermissions
|
||||
userPermissions,
|
||||
noChannel
|
||||
} = props;
|
||||
|
||||
const classes = useStyles(props);
|
||||
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<Card className={classes.tableCard}>
|
||||
<ResponsiveTable>
|
||||
<TableBody className={classes.tableRow}>
|
||||
{noChannel && (
|
||||
<TableRow hover={true} onClick={onCreateNewChannelClick}>
|
||||
<TableCell>
|
||||
<Typography>
|
||||
{intl.formatMessage(messages.createNewChannel)}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell className={classes.arrowIcon}>
|
||||
<KeyboardArrowRight />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
<RequirePermissions
|
||||
userPermissions={userPermissions}
|
||||
requiredPermissions={[PermissionEnum.MANAGE_ORDERS]}
|
||||
|
@ -65,20 +116,13 @@ const HomeNotificationTable: React.FC<HomeNotificationTableProps> = props => {
|
|||
<Skeleton />
|
||||
) : ordersToFulfill === 0 ? (
|
||||
<Typography>
|
||||
<FormattedMessage
|
||||
defaultMessage="No orders ready to fulfill"
|
||||
id="homeNotificationTableNoOrders"
|
||||
/>
|
||||
{intl.formatMessage(messages.noOrders)}
|
||||
</Typography>
|
||||
) : (
|
||||
<Typography>
|
||||
<FormattedMessage
|
||||
defaultMessage="{amount, plural,one {One order is ready to fulfill} other {{amount} Orders are ready to fulfill}}"
|
||||
id="homeNotificationTableOrders"
|
||||
values={{
|
||||
amount: <strong>{ordersToFulfill}</strong>
|
||||
}}
|
||||
/>
|
||||
{intl.formatMessage(messages.orderReady, {
|
||||
amount: <strong>{ordersToFulfill}</strong>
|
||||
})}
|
||||
</Typography>
|
||||
)}
|
||||
</TableCell>
|
||||
|
@ -92,20 +136,13 @@ const HomeNotificationTable: React.FC<HomeNotificationTableProps> = props => {
|
|||
<Skeleton />
|
||||
) : ordersToCapture === 0 ? (
|
||||
<Typography>
|
||||
<FormattedMessage
|
||||
defaultMessage="No payments waiting for capture"
|
||||
id="homeNotificationsNoPayments"
|
||||
/>
|
||||
{intl.formatMessage(messages.noPaymentWaiting)}
|
||||
</Typography>
|
||||
) : (
|
||||
<Typography>
|
||||
<FormattedMessage
|
||||
defaultMessage="{amount, plural,one {One payment to capture}other {{amount} Payments to capture}}"
|
||||
id="homeNotificationTablePayments"
|
||||
values={{
|
||||
amount: <strong>{ordersToCapture}</strong>
|
||||
}}
|
||||
/>
|
||||
{intl.formatMessage(messages.paymentCapture, {
|
||||
amount: <strong>{ordersToCapture}</strong>
|
||||
})}
|
||||
</Typography>
|
||||
)}
|
||||
</TableCell>
|
||||
|
@ -124,20 +161,13 @@ const HomeNotificationTable: React.FC<HomeNotificationTableProps> = props => {
|
|||
<Skeleton />
|
||||
) : productsOutOfStock === 0 ? (
|
||||
<Typography>
|
||||
<FormattedMessage
|
||||
defaultMessage="No products out of stock"
|
||||
id="homeNotificationsTableNoProducts"
|
||||
/>
|
||||
{intl.formatMessage(messages.noProductsOut)}
|
||||
</Typography>
|
||||
) : (
|
||||
<Typography>
|
||||
<FormattedMessage
|
||||
defaultMessage="{amount, plural,one {One product out of stock}other {{amount} Products out of stock}}"
|
||||
id="homeNotificationTableProducts"
|
||||
values={{
|
||||
amount: <strong>{productsOutOfStock}</strong>
|
||||
}}
|
||||
/>
|
||||
{intl.formatMessage(messages.productOut, {
|
||||
amount: <strong>{productsOutOfStock}</strong>
|
||||
})}
|
||||
</Typography>
|
||||
)}
|
||||
</TableCell>
|
||||
|
|
|
@ -46,17 +46,19 @@ const useStyles = makeStyles(
|
|||
|
||||
export interface HomePageProps extends UserPermissionProps {
|
||||
activities: Home_activities_edges_node[];
|
||||
orders: number;
|
||||
ordersToCapture: number;
|
||||
ordersToFulfill: number;
|
||||
orders: number | null;
|
||||
ordersToCapture: number | null;
|
||||
ordersToFulfill: number | null;
|
||||
productsOutOfStock: number;
|
||||
sales: Home_salesToday_gross;
|
||||
topProducts: Home_productTopToday_edges_node[];
|
||||
topProducts: Home_productTopToday_edges_node[] | null;
|
||||
userName: string;
|
||||
onCreateNewChannelClick: () => void;
|
||||
onOrdersToCaptureClick: () => void;
|
||||
onOrdersToFulfillClick: () => void;
|
||||
onProductClick: (productId: string, variantId: string) => void;
|
||||
onProductsOutOfStockClick: () => void;
|
||||
noChannel: boolean;
|
||||
}
|
||||
|
||||
const HomePage: React.FC<HomePageProps> = props => {
|
||||
|
@ -67,13 +69,15 @@ const HomePage: React.FC<HomePageProps> = props => {
|
|||
topProducts,
|
||||
onProductClick,
|
||||
activities,
|
||||
onCreateNewChannelClick,
|
||||
onOrdersToCaptureClick,
|
||||
onOrdersToFulfillClick,
|
||||
onProductsOutOfStockClick,
|
||||
ordersToCapture,
|
||||
ordersToFulfill,
|
||||
productsOutOfStock,
|
||||
userPermissions
|
||||
ordersToCapture = 0,
|
||||
ordersToFulfill = 0,
|
||||
productsOutOfStock = 0,
|
||||
userPermissions = [],
|
||||
noChannel
|
||||
} = props;
|
||||
|
||||
const classes = useStyles(props);
|
||||
|
@ -99,7 +103,9 @@ const HomePage: React.FC<HomePageProps> = props => {
|
|||
/>
|
||||
}
|
||||
>
|
||||
{sales ? (
|
||||
{noChannel ? (
|
||||
0
|
||||
) : sales ? (
|
||||
<Money money={sales} />
|
||||
) : (
|
||||
<Skeleton style={{ width: "5em" }} />
|
||||
|
@ -115,15 +121,18 @@ const HomePage: React.FC<HomePageProps> = props => {
|
|||
/>
|
||||
}
|
||||
>
|
||||
{orders === undefined ? (
|
||||
<Skeleton style={{ width: "5em" }} />
|
||||
) : (
|
||||
{noChannel ? (
|
||||
0
|
||||
) : orders !== undefined ? (
|
||||
orders
|
||||
) : (
|
||||
<Skeleton style={{ width: "5em" }} />
|
||||
)}
|
||||
</HomeAnalyticsCard>
|
||||
</div>
|
||||
</RequirePermissions>
|
||||
<HomeNotificationTable
|
||||
onCreateNewChannelClick={onCreateNewChannelClick}
|
||||
onOrdersToCaptureClick={onOrdersToCaptureClick}
|
||||
onOrdersToFulfillClick={onOrdersToFulfillClick}
|
||||
onProductsOutOfStockClick={onProductsOutOfStockClick}
|
||||
|
@ -131,30 +140,35 @@ const HomePage: React.FC<HomePageProps> = props => {
|
|||
ordersToFulfill={ordersToFulfill}
|
||||
productsOutOfStock={productsOutOfStock}
|
||||
userPermissions={userPermissions}
|
||||
noChannel={noChannel}
|
||||
/>
|
||||
<CardSpacer />
|
||||
<RequirePermissions
|
||||
userPermissions={userPermissions}
|
||||
requiredPermissions={[
|
||||
PermissionEnum.MANAGE_ORDERS,
|
||||
PermissionEnum.MANAGE_PRODUCTS
|
||||
]}
|
||||
>
|
||||
<HomeProductListCard
|
||||
onRowClick={onProductClick}
|
||||
topProducts={topProducts}
|
||||
/>
|
||||
<CardSpacer />
|
||||
</RequirePermissions>
|
||||
</div>
|
||||
<div>
|
||||
<RequirePermissions
|
||||
userPermissions={userPermissions}
|
||||
requiredPermissions={[PermissionEnum.MANAGE_ORDERS]}
|
||||
>
|
||||
<HomeActivityCard activities={activities} />
|
||||
</RequirePermissions>
|
||||
{topProducts && (
|
||||
<RequirePermissions
|
||||
userPermissions={userPermissions}
|
||||
requiredPermissions={[
|
||||
PermissionEnum.MANAGE_ORDERS,
|
||||
PermissionEnum.MANAGE_PRODUCTS
|
||||
]}
|
||||
>
|
||||
<HomeProductListCard
|
||||
onRowClick={onProductClick}
|
||||
topProducts={topProducts}
|
||||
/>
|
||||
<CardSpacer />
|
||||
</RequirePermissions>
|
||||
)}
|
||||
</div>
|
||||
{activities && (
|
||||
<div>
|
||||
<RequirePermissions
|
||||
userPermissions={userPermissions}
|
||||
requiredPermissions={[PermissionEnum.MANAGE_ORDERS]}
|
||||
>
|
||||
<HomeActivityCard activities={activities} />
|
||||
</RequirePermissions>
|
||||
</div>
|
||||
)}
|
||||
</Grid>
|
||||
</Container>
|
||||
);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import makeQuery from "@saleor/hooks/makeQuery";
|
||||
import gql from "graphql-tag";
|
||||
|
||||
import { TypedQuery } from "../queries";
|
||||
import { Home } from "./types/Home";
|
||||
import { Home, HomeVariables } from "./types/Home";
|
||||
|
||||
const home = gql`
|
||||
query Home($channel: String!) {
|
||||
|
@ -80,4 +80,5 @@ const home = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const HomePageQuery = TypedQuery<Home, {}>(home);
|
||||
|
||||
export const useHomePage = makeQuery<Home, HomeVariables>(home);
|
||||
|
|
|
@ -1,64 +1,69 @@
|
|||
import { channelsListUrl } from "@saleor/channels/urls";
|
||||
import useAppChannel from "@saleor/components/AppLayout/AppChannelContext";
|
||||
import useNavigator from "@saleor/hooks/useNavigator";
|
||||
import useUser from "@saleor/hooks/useUser";
|
||||
import React from "react";
|
||||
|
||||
import { getUserName, maybe } from "../../misc";
|
||||
import { getUserName } from "../../misc";
|
||||
import { orderListUrl } from "../../orders/urls";
|
||||
import { productListUrl, productVariantEditUrl } from "../../products/urls";
|
||||
import { OrderStatusFilter, StockAvailability } from "../../types/globalTypes";
|
||||
import HomePage from "../components/HomePage";
|
||||
import { HomePageQuery } from "../queries";
|
||||
import { useHomePage } from "../queries";
|
||||
|
||||
const HomeSection = () => {
|
||||
const navigate = useNavigator();
|
||||
const { user } = useUser();
|
||||
const { channel } = useAppChannel();
|
||||
|
||||
const noChannel = !channel && typeof channel !== "undefined";
|
||||
|
||||
const { data } = useHomePage({
|
||||
displayLoader: true,
|
||||
skip: noChannel,
|
||||
variables: { channel: channel?.slug }
|
||||
});
|
||||
|
||||
return (
|
||||
<HomePageQuery displayLoader variables={{ channel: channel.slug }}>
|
||||
{({ data }) => (
|
||||
<HomePage
|
||||
activities={maybe(() =>
|
||||
data.activities.edges.map(edge => edge.node).reverse()
|
||||
)}
|
||||
orders={maybe(() => data.ordersToday.totalCount)}
|
||||
sales={maybe(() => data.salesToday.gross)}
|
||||
topProducts={maybe(() =>
|
||||
data.productTopToday.edges.map(edge => edge.node)
|
||||
)}
|
||||
onProductClick={(productId, variantId) =>
|
||||
navigate(productVariantEditUrl(productId, variantId))
|
||||
}
|
||||
onOrdersToCaptureClick={() =>
|
||||
navigate(
|
||||
orderListUrl({
|
||||
status: [OrderStatusFilter.READY_TO_CAPTURE]
|
||||
})
|
||||
)
|
||||
}
|
||||
onOrdersToFulfillClick={() =>
|
||||
navigate(
|
||||
orderListUrl({
|
||||
status: [OrderStatusFilter.READY_TO_FULFILL]
|
||||
})
|
||||
)
|
||||
}
|
||||
onProductsOutOfStockClick={() =>
|
||||
navigate(
|
||||
productListUrl({
|
||||
stockStatus: StockAvailability.OUT_OF_STOCK
|
||||
})
|
||||
)
|
||||
}
|
||||
ordersToCapture={maybe(() => data.ordersToCapture.totalCount)}
|
||||
ordersToFulfill={maybe(() => data.ordersToFulfill.totalCount)}
|
||||
productsOutOfStock={maybe(() => data.productsOutOfStock.totalCount)}
|
||||
userName={getUserName(user, true)}
|
||||
userPermissions={user?.userPermissions || []}
|
||||
/>
|
||||
)}
|
||||
</HomePageQuery>
|
||||
<HomePage
|
||||
activities={data?.activities.edges.map(edge => edge.node).reverse()}
|
||||
orders={data?.ordersToday.totalCount}
|
||||
sales={data?.salesToday.gross}
|
||||
topProducts={data?.productTopToday.edges.map(edge => edge.node)}
|
||||
onProductClick={(productId, variantId) =>
|
||||
navigate(productVariantEditUrl(productId, variantId))
|
||||
}
|
||||
onCreateNewChannelClick={() => {
|
||||
navigate(channelsListUrl());
|
||||
}}
|
||||
onOrdersToCaptureClick={() =>
|
||||
navigate(
|
||||
orderListUrl({
|
||||
status: [OrderStatusFilter.READY_TO_CAPTURE]
|
||||
})
|
||||
)
|
||||
}
|
||||
onOrdersToFulfillClick={() =>
|
||||
navigate(
|
||||
orderListUrl({
|
||||
status: [OrderStatusFilter.READY_TO_FULFILL]
|
||||
})
|
||||
)
|
||||
}
|
||||
onProductsOutOfStockClick={() =>
|
||||
navigate(
|
||||
productListUrl({
|
||||
stockStatus: StockAvailability.OUT_OF_STOCK
|
||||
})
|
||||
)
|
||||
}
|
||||
ordersToCapture={data?.ordersToCapture.totalCount}
|
||||
ordersToFulfill={data?.ordersToFulfill.totalCount}
|
||||
productsOutOfStock={data?.productsOutOfStock.totalCount}
|
||||
userName={getUserName(user, true)}
|
||||
userPermissions={user?.userPermissions}
|
||||
noChannel={noChannel}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -142,13 +142,21 @@ const Routes: React.FC = () => {
|
|||
} = useAuth();
|
||||
const { channel } = useAppChannel(false);
|
||||
|
||||
const channelLoaded = typeof channel !== "undefined";
|
||||
|
||||
const homePageLoaded =
|
||||
channelLoaded &&
|
||||
isAuthenticated &&
|
||||
!tokenAuthLoading &&
|
||||
!tokenVerifyLoading;
|
||||
|
||||
const homePageLoading =
|
||||
(isAuthenticated && !channelLoaded) || (hasToken && tokenVerifyLoading);
|
||||
|
||||
return (
|
||||
<>
|
||||
<WindowTitle title={intl.formatMessage(commonMessages.dashboard)} />
|
||||
{channel &&
|
||||
isAuthenticated &&
|
||||
!tokenAuthLoading &&
|
||||
!tokenVerifyLoading ? (
|
||||
{homePageLoaded ? (
|
||||
<AppLayout>
|
||||
<ErrorBoundary
|
||||
onError={() =>
|
||||
|
@ -284,7 +292,7 @@ const Routes: React.FC = () => {
|
|||
</Switch>
|
||||
</ErrorBoundary>
|
||||
</AppLayout>
|
||||
) : (isAuthenticated && !channel) || (hasToken && tokenVerifyLoading) ? (
|
||||
) : homePageLoading ? (
|
||||
<LoginLoading />
|
||||
) : (
|
||||
<Auth />
|
||||
|
|
|
@ -20,6 +20,7 @@ import { ListViews } from "@saleor/types";
|
|||
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
||||
import createFilterHandlers from "@saleor/utils/handlers/filterHandlers";
|
||||
import createSortHandler from "@saleor/utils/handlers/sortHandler";
|
||||
import { mapNodeToChoice } from "@saleor/utils/maps";
|
||||
import { getSortParams } from "@saleor/utils/sort";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
@ -259,12 +260,9 @@ export const OrderDraftList: React.FC<OrderDraftListProps> = ({ params }) => {
|
|||
tabName={maybe(() => tabs[currentTab - 1].name, "...")}
|
||||
/>
|
||||
<ChannelPickerDialog
|
||||
channelsChoices={availableChannels.map(channel => ({
|
||||
label: channel.name,
|
||||
value: channel.id
|
||||
}))}
|
||||
channelsChoices={mapNodeToChoice(availableChannels)}
|
||||
confirmButtonState="success"
|
||||
defaultChoice={channel.id}
|
||||
defaultChoice={channel?.id}
|
||||
open={params.action === "create-order"}
|
||||
onClose={closeModal}
|
||||
onConfirm={channel =>
|
||||
|
|
|
@ -71,6 +71,8 @@ export const OrderList: React.FC<OrderListProps> = ({ params }) => {
|
|||
|
||||
const { channel, availableChannels } = useAppChannel();
|
||||
|
||||
const noChannel = !channel && typeof channel !== "undefined";
|
||||
|
||||
const tabs = getFilterTabs();
|
||||
|
||||
const currentTab =
|
||||
|
@ -176,23 +178,25 @@ export const OrderList: React.FC<OrderListProps> = ({ params }) => {
|
|||
onSubmit={handleFilterTabDelete}
|
||||
tabName={getStringOrPlaceholder(tabs[currentTab - 1]?.name)}
|
||||
/>
|
||||
<ChannelPickerDialog
|
||||
channelsChoices={availableChannels.map(channel => ({
|
||||
label: channel.name,
|
||||
value: channel.id
|
||||
}))}
|
||||
confirmButtonState="success"
|
||||
defaultChoice={channel.id}
|
||||
open={params.action === "create-order"}
|
||||
onClose={closeModal}
|
||||
onConfirm={channel =>
|
||||
createOrder({
|
||||
variables: {
|
||||
input: { channel }
|
||||
}
|
||||
})
|
||||
}
|
||||
/>
|
||||
{!noChannel && (
|
||||
<ChannelPickerDialog
|
||||
channelsChoices={availableChannels.map(channel => ({
|
||||
label: channel.name,
|
||||
value: channel.id
|
||||
}))}
|
||||
confirmButtonState="success"
|
||||
defaultChoice={channel.id}
|
||||
open={params.action === "create-order"}
|
||||
onClose={closeModal}
|
||||
onConfirm={channel =>
|
||||
createOrder({
|
||||
variables: {
|
||||
input: { channel }
|
||||
}
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -127,6 +127,8 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
|
|||
});
|
||||
const { availableChannels, channel } = useAppChannel();
|
||||
|
||||
const noChannel = !channel && typeof channel !== "undefined";
|
||||
|
||||
const [openModal, closeModal] = createDialogActionHandlers<
|
||||
ProductListUrlDialog,
|
||||
ProductListUrlQueryParams
|
||||
|
@ -222,8 +224,8 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
|
|||
);
|
||||
|
||||
const paginationState = createPaginationState(settings.rowNumber, params);
|
||||
const filter = getFilterVariables(params, channel.slug);
|
||||
const sort = getSortQueryVariables(params, channel.slug);
|
||||
const filter = !noChannel ? getFilterVariables(params, channel.slug) : null;
|
||||
const sort = !noChannel ? getSortQueryVariables(params, channel.slug) : null;
|
||||
const queryVariables = React.useMemo<ProductListVariables>(
|
||||
() => ({
|
||||
...paginationState,
|
||||
|
@ -302,7 +304,7 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
|
|||
() => attributes.data.availableInGrid.edges.map(edge => edge.node),
|
||||
[]
|
||||
)}
|
||||
currencySymbol={channel?.currencyCode}
|
||||
currencySymbol={channel?.currencyCode || ""}
|
||||
currentTab={currentTab}
|
||||
defaultSettings={defaultListSettings[ListViews.PRODUCT_LIST]}
|
||||
filterOpts={filterOpts}
|
||||
|
@ -380,7 +382,7 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
|
|||
tabs={getFilterTabs().map(tab => tab.name)}
|
||||
onExport={() => openModal("export")}
|
||||
channelsCount={availableChannels?.length}
|
||||
selectedChannelId={channel.id}
|
||||
selectedChannelId={channel?.id}
|
||||
/>
|
||||
<ActionDialog
|
||||
open={params.action === "delete"}
|
||||
|
|
|
@ -423,7 +423,7 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
|
|||
loading: searchCollectionsOpts.loading,
|
||||
onFetchMore: loadMoreCollections
|
||||
}}
|
||||
selectedChannelId={channel.id}
|
||||
selectedChannelId={channel?.id}
|
||||
openChannelsModal={handleChannelsModalOpen}
|
||||
onChannelsChange={setCurrentChannels}
|
||||
/>
|
||||
|
|
|
@ -52100,21 +52100,7 @@ exports[`Storyshots Views / Collections / Collection list default 1`] = `
|
|||
class="MuiTableCell-root-id MuiTableCell-body-id CollectionList-colAvailability-id"
|
||||
data-test="availability"
|
||||
data-test-availability="true"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
aria-controls="availability-menu"
|
||||
aria-haspopup="true"
|
||||
role="button"
|
||||
>
|
||||
<div
|
||||
class="StatusLabel-root-id undefined StatusLabel-errorDot-id"
|
||||
>
|
||||
Available in 1/2
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
/>
|
||||
</tr>
|
||||
<tr
|
||||
class="MuiTableRow-root-id CollectionList-tableRow-id MuiTableRow-hover-id"
|
||||
|
@ -52170,21 +52156,7 @@ exports[`Storyshots Views / Collections / Collection list default 1`] = `
|
|||
class="MuiTableCell-root-id MuiTableCell-body-id CollectionList-colAvailability-id"
|
||||
data-test="availability"
|
||||
data-test-availability="true"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
aria-controls="availability-menu"
|
||||
aria-haspopup="true"
|
||||
role="button"
|
||||
>
|
||||
<div
|
||||
class="StatusLabel-root-id undefined StatusLabel-errorDot-id"
|
||||
>
|
||||
Available in 1/2
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
/>
|
||||
</tr>
|
||||
<tr
|
||||
class="MuiTableRow-root-id CollectionList-tableRow-id MuiTableRow-hover-id"
|
||||
|
@ -52240,21 +52212,7 @@ exports[`Storyshots Views / Collections / Collection list default 1`] = `
|
|||
class="MuiTableCell-root-id MuiTableCell-body-id CollectionList-colAvailability-id"
|
||||
data-test="availability"
|
||||
data-test-availability="true"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
aria-controls="availability-menu"
|
||||
aria-haspopup="true"
|
||||
role="button"
|
||||
>
|
||||
<div
|
||||
class="StatusLabel-root-id undefined StatusLabel-errorDot-id"
|
||||
>
|
||||
Available in 1/2
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
/>
|
||||
</tr>
|
||||
<tr
|
||||
class="MuiTableRow-root-id CollectionList-tableRow-id MuiTableRow-hover-id"
|
||||
|
@ -52310,21 +52268,7 @@ exports[`Storyshots Views / Collections / Collection list default 1`] = `
|
|||
class="MuiTableCell-root-id MuiTableCell-body-id CollectionList-colAvailability-id"
|
||||
data-test="availability"
|
||||
data-test-availability="true"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
aria-controls="availability-menu"
|
||||
aria-haspopup="true"
|
||||
role="button"
|
||||
>
|
||||
<div
|
||||
class="StatusLabel-root-id undefined StatusLabel-errorDot-id"
|
||||
>
|
||||
Available in 1/2
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
/>
|
||||
</tr>
|
||||
<tr
|
||||
class="MuiTableRow-root-id CollectionList-tableRow-id MuiTableRow-hover-id"
|
||||
|
@ -52380,21 +52324,7 @@ exports[`Storyshots Views / Collections / Collection list default 1`] = `
|
|||
class="MuiTableCell-root-id MuiTableCell-body-id CollectionList-colAvailability-id"
|
||||
data-test="availability"
|
||||
data-test-availability="true"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
aria-controls="availability-menu"
|
||||
aria-haspopup="true"
|
||||
role="button"
|
||||
>
|
||||
<div
|
||||
class="StatusLabel-root-id undefined StatusLabel-errorDot-id"
|
||||
>
|
||||
Available in 1/2
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
/>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@ -91661,11 +91591,11 @@ exports[`Storyshots Views / HomePage loading 1`] = `
|
|||
<td
|
||||
class="MuiTableCell-root-id MuiTableCell-body-id"
|
||||
>
|
||||
<span
|
||||
class="Skeleton-skeleton-id"
|
||||
<div
|
||||
class="MuiTypography-root-id MuiTypography-body1-id"
|
||||
>
|
||||
|
||||
</span>
|
||||
No orders ready to fulfill
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="MuiTableCell-root-id MuiTableCell-body-id HomeNotificationTable-arrowIcon-id"
|
||||
|
@ -91689,11 +91619,11 @@ exports[`Storyshots Views / HomePage loading 1`] = `
|
|||
<td
|
||||
class="MuiTableCell-root-id MuiTableCell-body-id"
|
||||
>
|
||||
<span
|
||||
class="Skeleton-skeleton-id"
|
||||
<div
|
||||
class="MuiTypography-root-id MuiTypography-body1-id"
|
||||
>
|
||||
|
||||
</span>
|
||||
No payments waiting for capture
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="MuiTableCell-root-id MuiTableCell-body-id HomeNotificationTable-arrowIcon-id"
|
||||
|
@ -91717,11 +91647,11 @@ exports[`Storyshots Views / HomePage loading 1`] = `
|
|||
<td
|
||||
class="MuiTableCell-root-id MuiTableCell-body-id"
|
||||
>
|
||||
<span
|
||||
class="Skeleton-skeleton-id"
|
||||
<div
|
||||
class="MuiTypography-root-id MuiTypography-body1-id"
|
||||
>
|
||||
|
||||
</span>
|
||||
No products out of stock
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="MuiTableCell-root-id MuiTableCell-body-id HomeNotificationTable-arrowIcon-id"
|
||||
|
@ -91746,149 +91676,6 @@ exports[`Storyshots Views / HomePage loading 1`] = `
|
|||
<div
|
||||
class="CardSpacer-spacer-id"
|
||||
/>
|
||||
<div
|
||||
class="MuiPaper-root-id MuiPaper-elevation0-id MuiCard-root-id MuiPaper-rounded-id"
|
||||
>
|
||||
<div
|
||||
class="CardTitle-root-id"
|
||||
>
|
||||
<span
|
||||
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
|
||||
>
|
||||
Top Products
|
||||
</span>
|
||||
<div
|
||||
class="CardTitle-toolbar-id"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="CardTitle-children-id"
|
||||
/>
|
||||
<hr
|
||||
class="CardTitle-hr-id"
|
||||
/>
|
||||
<div
|
||||
class="ResponsiveTable-root-id"
|
||||
>
|
||||
<table
|
||||
class="MuiTable-root-id"
|
||||
>
|
||||
<colgroup>
|
||||
<col
|
||||
class="HomeProductListCard-colAvatar-id"
|
||||
/>
|
||||
<col
|
||||
class="HomeProductListCard-colName-id"
|
||||
/>
|
||||
<col />
|
||||
</colgroup>
|
||||
<tbody
|
||||
class="MuiTableBody-root-id"
|
||||
>
|
||||
<tr
|
||||
class="MuiTableRow-root-id"
|
||||
>
|
||||
<td
|
||||
class="MuiTableCell-root-id MuiTableCell-body-id TableCellAvatar-root-id HomeProductListCard-colAvatar-id"
|
||||
>
|
||||
<div
|
||||
class="TableCellAvatar-content-id"
|
||||
>
|
||||
<div
|
||||
class="MuiAvatar-root-id MuiAvatar-circle-id TableCellAvatar-avatar-id HomeProductListCard-avatarProps-id MuiAvatar-colorDefault-id"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root-id MuiSvgIcon-colorPrimary-id"
|
||||
focusable="false"
|
||||
role="presentation"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M19 8l-4 4h3c0 3.31-2.69 6-6 6-1.01 0-1.97-.25-2.8-.7l-1.46 1.46C8.97 19.54 10.43 20 12 20c4.42 0 8-3.58 8-8h3l-4-4zM6 12c0-3.31 2.69-6 6-6 1.01 0 1.97.25 2.8.7l1.46-1.46C15.03 4.46 13.57 4 12 4c-4.42 0-8 3.58-8 8H1l4 4 4-4H6z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="TableCellAvatar-children-id"
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="MuiTableCell-root-id MuiTableCell-body-id HomeProductListCard-label-id"
|
||||
>
|
||||
<span
|
||||
class="Skeleton-skeleton-id"
|
||||
>
|
||||
|
||||
</span>
|
||||
</td>
|
||||
<td
|
||||
class="MuiTableCell-root-id MuiTableCell-body-id"
|
||||
>
|
||||
<div
|
||||
class="MuiTypography-root-id MuiTypography-body1-id MuiTypography-alignRight-id"
|
||||
>
|
||||
<span
|
||||
class="Skeleton-skeleton-id"
|
||||
>
|
||||
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="CardSpacer-spacer-id"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="MuiPaper-root-id MuiPaper-elevation0-id MuiCard-root-id MuiPaper-rounded-id"
|
||||
>
|
||||
<div
|
||||
class="CardTitle-root-id"
|
||||
>
|
||||
<span
|
||||
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
|
||||
>
|
||||
Activity
|
||||
</span>
|
||||
<div
|
||||
class="CardTitle-toolbar-id"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="CardTitle-children-id"
|
||||
/>
|
||||
<hr
|
||||
class="CardTitle-hr-id"
|
||||
/>
|
||||
<ul
|
||||
class="MuiList-root-id MuiList-dense-id MuiList-padding-id"
|
||||
>
|
||||
<li
|
||||
class="MuiListItem-root-id MuiListItem-dense-id MuiListItem-gutters-id"
|
||||
>
|
||||
<div
|
||||
class="MuiListItemText-root-id HomeActivityCard-loadingProducts-id MuiListItemText-dense-id"
|
||||
>
|
||||
<div
|
||||
class="MuiTypography-root-id MuiTypography-body1-id"
|
||||
>
|
||||
<span
|
||||
class="Skeleton-skeleton-id"
|
||||
>
|
||||
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -13,6 +13,8 @@ const shop = shopFixture(placeholderImage);
|
|||
|
||||
const homePageProps: Omit<HomePageProps, "classes"> = {
|
||||
activities: shop.activities.edges.map(edge => edge.node),
|
||||
noChannel: false,
|
||||
onCreateNewChannelClick: () => undefined,
|
||||
onOrdersToCaptureClick: () => undefined,
|
||||
onOrdersToFulfillClick: () => undefined,
|
||||
onProductClick: () => undefined,
|
||||
|
|
Loading…
Reference in a new issue