saleor-dashboard/src/channels/utils.ts

437 lines
12 KiB
TypeScript

// @ts-strict-ignore
import {
ChannelSaleFormData,
SaleDetailsPageFormData,
} from "@dashboard/discounts/components/SaleDetailsPage";
import { VoucherDetailsPageFormData } from "@dashboard/discounts/components/VoucherDetailsPage";
import { RequirementsPicker } from "@dashboard/discounts/types";
import {
ChannelDetailsFragment,
ChannelFragment,
CollectionDetailsFragment,
ProductDetailsVariantFragment,
ProductFragment,
ProductVariantDetailsQuery,
SaleDetailsFragment,
SaleType,
ShippingZoneQuery,
VoucherDetailsFragment,
} from "@dashboard/graphql";
import { RequireOnlyOne } from "@dashboard/misc";
import { validatePrice } from "@dashboard/products/utils/validation";
import { mapNodeToChoice } from "@dashboard/utils/maps";
import uniqBy from "lodash/uniqBy";
export interface Channel {
id: string;
name: string;
}
export interface ChannelData {
id: string;
name: string;
isPublished?: boolean;
publicationDate?: string | null;
currency?: string;
variantsIds?: string[];
price?: string;
costPrice?: string;
availableForPurchase?: string;
isAvailableForPurchase?: boolean;
visibleInListings?: boolean;
preorderThreshold?: number;
unitsSold?: number;
}
export interface ProductChannelListingData extends Channel {
isPublished: boolean;
publicationDate: string | null;
availableForPurchase: string;
isAvailableForPurchase: boolean;
visibleInListings: boolean;
currency?: string;
}
export interface ChannelPriceData {
id: string;
name: string;
currency: string;
price: string;
costPrice?: string;
}
export interface IChannelPriceArgs {
price: string;
costPrice: string;
}
export type ChannelPriceArgs = RequireOnlyOne<
IChannelPriceArgs,
"price" | "costPrice"
>;
export interface ChannelPreorderArgs {
preorderThreshold: number;
unitsSold: number;
hasPreorderEndDate: boolean;
preorderEndDateTime?: string;
}
export interface ChannelPriceAndPreorderData {
id: string;
name: string;
currency: string;
price: string;
costPrice?: string;
preorderThreshold?: number | null;
unitsSold?: number;
}
export interface IChannelPriceAndPreorderArgs {
price: string;
costPrice: string;
preorderThreshold?: number | null;
unitsSold?: number;
}
export type ChannelPriceAndPreorderArgs = IChannelPriceArgs & {
preorderThreshold: number | null;
unitsSold?: number;
};
export interface ChannelVoucherData {
id: string;
name: string;
discountValue: string;
currency: string;
minSpent: string;
}
export interface ChannelSaleData {
id: string;
name: string;
discountValue: string;
currency: string;
}
export interface ChannelCollectionData {
id: string;
isPublished: boolean;
name: string;
publicationDate: string | null;
}
export const createCollectionChannels = (data?: ChannelFragment[]) =>
data?.map(channel => ({
id: channel.id,
isPublished: false,
name: channel.name,
publicationDate: null,
}));
export const createVoucherChannels = (data?: ChannelFragment[]) =>
data?.map(channel => ({
currency: channel.currencyCode,
discountValue: "",
id: channel.id,
minSpent: "",
name: channel.name,
}));
export const createSaleChannels = (data?: ChannelFragment[]) =>
data?.map(channel => ({
currency: channel.currencyCode,
discountValue: "",
id: channel.id,
name: channel.name,
percentageValue: "",
fixedValue: "",
}));
export const createVariantChannels = (
data?: ProductVariantDetailsQuery["productVariant"],
): ChannelPriceData[] => {
if (data) {
return data?.channelListings.map(listing => ({
costPrice: listing.costPrice?.amount.toString() || "",
currency: listing.channel.currencyCode,
id: listing.channel.id,
name: listing.channel.name,
price: listing.price?.amount?.toString(),
}));
}
return [];
};
export const createChannelsDataWithSaleDiscountPrice = (
saleData?: SaleDetailsFragment,
data?: ChannelFragment[],
): ChannelSaleData[] => {
if (data && saleData?.channelListings) {
const dataArr = createSaleChannels(data);
const saleDataArr = createChannelsDataFromSale(saleData);
return uniqBy([...saleDataArr, ...dataArr], obj => obj.id);
}
return [];
};
export const createChannelsDataWithDiscountPrice = (
voucherData?: VoucherDetailsFragment,
data?: ChannelFragment[],
): ChannelVoucherData[] => {
if (data && voucherData?.channelListings) {
const dataArr = createVoucherChannels(data);
const voucherDataArr = createChannelsDataFromVoucher(voucherData);
return uniqBy([...voucherDataArr, ...dataArr], obj => obj.id);
}
return [];
};
export const createChannelsData = (data?: ChannelFragment[]): ChannelData[] =>
data?.map(channel => ({
availableForPurchase: null,
costPrice: "",
currency: channel.currencyCode,
id: channel.id,
isAvailableForPurchase: true,
variantsIds: [],
isPublished: true,
name: channel.name,
price: "",
publicationDate: null,
visibleInListings: true,
})) || [];
export const createChannelsDataWithPrice = (
productData?: ProductFragment,
data?: ChannelFragment[],
): ChannelData[] => {
if (data && productData?.channelListings) {
const dataArr = createChannelsData(data);
const productDataArr = createChannelsDataFromProduct(productData);
return uniqBy([...productDataArr, ...dataArr], obj => obj.id);
}
return [];
};
export const createShippingChannels = (
data?: ShippingZoneQuery["shippingZone"]["channels"],
): ChannelShippingData[] =>
data?.map(channel => ({
currency: channel.currencyCode,
id: channel.id,
maxValue: "",
minValue: "",
name: channel.name,
price: "",
})) || [];
export const createShippingChannelsFromRate = (
data?: ShippingZoneQuery["shippingZone"]["shippingMethods"][0]["channelListings"],
): ChannelShippingData[] =>
data?.map(channelData => ({
currency: channelData.channel.currencyCode,
id: channelData.channel.id,
maxValue: channelData.maximumOrderPrice
? channelData.maximumOrderPrice.amount.toString()
: "",
minValue: channelData.minimumOrderPrice
? channelData.minimumOrderPrice.amount.toString()
: "",
name: channelData.channel.name,
price: channelData.price ? channelData.price.amount.toString() : "",
})) || [];
export const createCollectionChannelsData = (
collectionData?: CollectionDetailsFragment,
) => {
if (collectionData?.channelListings) {
const collectionDataArr = collectionData?.channelListings.map(listing => ({
id: listing.channel.id,
isPublished: listing.isPublished,
name: listing.channel.name,
publicationDate: listing.publicationDate,
}));
return collectionDataArr;
}
};
export interface ChannelShippingData {
currency: string;
id: string;
minValue: string;
name: string;
maxValue: string;
price: string;
}
export const createChannelsDataFromVoucher = (
voucherData?: VoucherDetailsFragment,
) =>
voucherData?.channelListings?.map(option => ({
currency: option.channel.currencyCode || option?.minSpent?.currency || "",
discountValue: option.discountValue.toString() || "",
id: option.channel.id,
minSpent: option?.minSpent?.amount.toString() || "",
name: option.channel.name,
})) || [];
export const createChannelsDataFromSale = (
saleData?: SaleDetailsFragment,
): ChannelSaleFormData[] =>
saleData?.channelListings?.map(option => ({
currency: option.channel.currencyCode || "",
discountValue: option.discountValue.toString() || "",
id: option.channel.id,
name: option.channel.name,
percentageValue:
saleData.type === SaleType.PERCENTAGE
? option.discountValue.toString()
: "",
fixedValue:
saleData.type === SaleType.FIXED ? option.discountValue.toString() : "",
})) || [];
export const createChannelsDataFromProduct = (productData?: ProductFragment) =>
productData?.channelListings?.map(
({
channel,
availableForPurchase,
isAvailableForPurchase,
visibleInListings,
publicationDate,
isPublished,
}) => {
const variantChannel = productData.variants[0]?.channelListings.find(
listing => listing.channel.id === channel.id,
);
// Comparing explicitly to false because `hasVariants` can be undefined
const isSimpleProduct = !productData.productType?.hasVariants;
const haveVariantsChannelListings = productData.variants.some(variant =>
variant.channelListings.some(
listing => listing.channel.id === channel.id,
),
);
const price = variantChannel?.price;
const costPrice = variantChannel?.costPrice;
const variantsIds = extractVariantsIdsForChannel(
productData.variants,
channel.id,
);
const soldUnits = variantChannel?.preorderThreshold?.soldUnits;
const preorderThreshold = variantChannel?.preorderThreshold?.quantity;
// Published defaults to true if none of variants have set channel listing yet
const isProductPublished =
!isSimpleProduct && !haveVariantsChannelListings ? true : isPublished;
return {
availableForPurchase,
isPublished: isProductPublished,
publicationDate,
variantsIds,
costPrice: costPrice?.amount.toString() ?? "",
currency: price ? price.currency : "",
id: channel.id,
isAvailableForPurchase: !!isAvailableForPurchase,
name: channel.name,
price: price ? price.amount.toString() : "",
visibleInListings: !!visibleInListings,
soldUnits,
preorderThreshold,
};
},
) || [];
export const extractVariantsIdsForChannel = (
productVariants: ProductDetailsVariantFragment[],
channelId: string,
) =>
productVariants
?.filter(({ channelListings }) =>
channelListings.some(({ channel }) => channel.id === channelId),
)
.map(({ id }) => id) || [];
export const createSortedChannelsDataFromProduct = (
productData?: ProductFragment,
): ChannelData[] =>
createChannelsDataFromProduct(productData).sort((channel, nextChannel) =>
channel.name.localeCompare(nextChannel.name),
);
export const createSortedChannelsData = (data?: ChannelFragment[]) =>
createChannelsData(data)?.sort((channel, nextChannel) =>
channel.name.localeCompare(nextChannel.name),
);
export const createSortedShippingChannels = (
data?: ShippingZoneQuery["shippingZone"]["channels"],
) =>
createShippingChannels(data)?.sort((channel, nextChannel) =>
channel.name.localeCompare(nextChannel.name),
);
export const createSortedShippingChannelsFromRate = (
data?: ShippingZoneQuery["shippingZone"]["shippingMethods"][0]["channelListings"],
) =>
createShippingChannelsFromRate(data)?.sort((channel, nextChannel) =>
channel.name.localeCompare(nextChannel.name),
);
export const createSortedVoucherData = (data?: ChannelFragment[]) =>
createVoucherChannels(data)?.sort((channel, nextChannel) =>
channel.name.localeCompare(nextChannel.name),
);
export const createSortedSaleData = (data?: ChannelFragment[]) =>
createSaleChannels(data)?.sort((channel, nextChannel) =>
channel.name.localeCompare(nextChannel.name),
);
export const createSortedChannelsDataFromVoucher = (
data?: VoucherDetailsFragment,
) =>
createChannelsDataFromVoucher(data)?.sort((channel, nextChannel) =>
channel.name.localeCompare(nextChannel.name),
);
export const createSortedChannelsDataFromSale = (
data?: SaleDetailsFragment,
): ChannelSaleFormData[] =>
createChannelsDataFromSale(data)?.sort((channel, nextChannel) =>
channel.name.localeCompare(nextChannel.name),
);
export const getChannelsCurrencyChoices = (
id: string,
selectedChannel: ChannelDetailsFragment,
channelsList: ChannelDetailsFragment[],
) =>
id
? mapNodeToChoice(
channelsList?.filter(
channel =>
channel.id !== id &&
channel.currencyCode === selectedChannel?.currencyCode,
),
)
: [];
export const validateSalePrice = (
data: SaleDetailsPageFormData,
channel: ChannelSaleFormData,
) =>
validatePrice(
data.type === SaleType.PERCENTAGE
? channel.percentageValue
: channel.fixedValue,
);
export const validateVoucherPrice = (
data: VoucherDetailsPageFormData,
channel: ChannelVoucherData,
) =>
validatePrice(channel.discountValue) ||
(data.requirementsPicker === RequirementsPicker.ORDER &&
validatePrice(channel.minSpent));