Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion admin/src/common/MasterMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Assets, ChatCommunication, Dashboard, Domain, PageTree, Snips, Tag, Wrench } from "@comet/admin-icons";
import { Assets, ChatCommunication, Dashboard, Domain, List, PageTree, Snips, Tag, Wrench } from "@comet/admin-icons";
import {
ContentScopeIndicator,
createRedirectsPage,
Expand All @@ -15,6 +15,7 @@ import { Link } from "@src/documents/links/Link";
import { Page } from "@src/documents/pages/Page";
import { EditFooterPage } from "@src/footers/EditFooterPage";
import { ProductCategoriesPage } from "@src/products/ProductCategoriesPage";
import { ProductCollectionsPage } from "@src/products/ProductCollectionsPage";
import { ProductReviewsPage } from "@src/products/ProductReviewsPage";
import { ProductsPage } from "@src/products/ProductsPage";
import { FormattedMessage } from "react-intl";
Expand Down Expand Up @@ -73,6 +74,16 @@ export const masterMenuData: MasterMenuData = [
},
requiredPermission: "productCategories",
},
{
type: "route",
primary: <FormattedMessage id="menu.productCollections" defaultMessage="Product Collections" />,
icon: <List />,
route: {
path: "/product-collections",
component: ProductCollectionsPage,
},
requiredPermission: "productCollections",
},
{
type: "route",
primary: <FormattedMessage id="menu.productReviews" defaultMessage="Product Reviews" />,
Expand Down
84 changes: 84 additions & 0 deletions admin/src/products/ProductCollectionsPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import {
Button,
FillSpace,
FullHeightContent,
RouterTab,
RouterTabs,
SaveBoundary,
Stack,
StackLink,
StackMainContent,
StackPage,
StackSwitch,
StackToolbar,
ToolbarActions,
ToolbarAutomaticTitleItem,
ToolbarBackButton,
} from "@comet/admin";
import { Add } from "@comet/admin-icons";
import { ContentScopeIndicator } from "@comet/cms-admin";
import { type FunctionComponent } from "react";
import { FormattedMessage } from "react-intl";

import { ProductCollectionForm } from "./components/productCollectionForm/ProductCollectionForm";
import { ProductCollectionProductsGrid } from "./components/productCollectionProductsGrid/ProductCollectionProductsGrid";
import { ProductCollectionsGrid } from "./components/productCollectionsDataGrid/ProductCollectionsGrid";
import { ProductCollectionToolbar } from "./components/productCollectionToolbar/ProductCollectionToolbar";

export const ProductCollectionsPage: FunctionComponent = () => {
return (
<Stack topLevelTitle={<FormattedMessage id="productCollections.title" defaultMessage="Product Collections" />}>
<StackSwitch>
<StackPage name="grid">
<StackToolbar scopeIndicator={<ContentScopeIndicator />}>
<ToolbarBackButton />
<ToolbarAutomaticTitleItem />
<FillSpace />
<ToolbarActions>
<Button variant="primary" component={StackLink} pageName="add" payload="add" startIcon={<Add />}>
<FormattedMessage id="productCollections.addProductCollection" defaultMessage="Add Collection" />
</Button>
</ToolbarActions>
</StackToolbar>
<StackMainContent fullHeight>
<ProductCollectionsGrid />
</StackMainContent>
</StackPage>
<StackPage name="add">
<SaveBoundary>
<ProductCollectionToolbar />
<StackMainContent>
<ProductCollectionForm />
</StackMainContent>
</SaveBoundary>
</StackPage>
<StackPage name="edit">
{(id) => (
<SaveBoundary>
<ProductCollectionToolbar id={id} />
<StackMainContent>
<RouterTabs>
<RouterTab
forceRender={true}
path=""
label={<FormattedMessage id="productCollections.tab.general" defaultMessage="General" />}
>
<ProductCollectionForm id={id} />
</RouterTab>
<RouterTab
path="/products"
label={<FormattedMessage id="productCollections.tab.products" defaultMessage="Products" />}
>
<FullHeightContent>
<ProductCollectionProductsGrid productCollectionId={id} />
</FullHeightContent>
</RouterTab>
</RouterTabs>
</StackMainContent>
</SaveBoundary>
)}
</StackPage>
</StackSwitch>
</Stack>
);
};
12 changes: 12 additions & 0 deletions admin/src/products/components/collectionType/CollectionType.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { createTranslatableEnum } from "@src/common/components/enums/createTranslatableEnum/createTranslatableEnum";
import { type GQLCollectionType } from "@src/graphql.generated";
import { defineMessage } from "react-intl";

const { formattedMessageMap, Component: CollectionType } = createTranslatableEnum<GQLCollectionType>({
Manual: defineMessage({ defaultMessage: "Manual", id: "products.collectionType.manual" }),
Seasonal: defineMessage({ defaultMessage: "Seasonal", id: "products.collectionType.seasonal" }),
Featured: defineMessage({ defaultMessage: "Featured", id: "products.collectionType.featured" }),
Sale: defineMessage({ defaultMessage: "Sale", id: "products.collectionType.sale" }),
});

export { CollectionType, formattedMessageMap as collectionTypeFormattedMessageMap };
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { Chip } from "@mui/material";
import { ChipIcon } from "@src/common/components/enums/chipIcon/ChipIcon";
import { EnumChip, type EnumChipProps } from "@src/common/components/enums/enumChip/EnumChip";
import { type GQLCollectionType } from "@src/graphql.generated";
import { CollectionType, collectionTypeFormattedMessageMap } from "@src/products/components/collectionType/CollectionType";
import { type FunctionComponent } from "react";

type CollectionTypeChipProps = Pick<EnumChipProps<GQLCollectionType>, "loading" | "onSelectItem" | "value">;

const collectionTypeSortOrder: GQLCollectionType[] = ["Manual", "Seasonal", "Featured", "Sale"];

export const CollectionTypeChip: FunctionComponent<CollectionTypeChipProps> = ({ loading, onSelectItem, value }) => {
return (
<EnumChip<GQLCollectionType>
chipMap={{
Manual: (chipProps) => (
<Chip
clickable={!!chipProps.onClick}
color="default"
icon={<ChipIcon loading={chipProps.loading} onClick={chipProps.onClick} />}
label={<CollectionType value="Manual" />}
onClick={chipProps.onClick}
variant="filled"
/>
),
Seasonal: (chipProps) => (
<Chip
clickable={!!chipProps.onClick}
color="info"
icon={<ChipIcon loading={chipProps.loading} onClick={chipProps.onClick} />}
label={<CollectionType value="Seasonal" />}
onClick={chipProps.onClick}
variant="filled"
/>
),
Featured: (chipProps) => (
<Chip
clickable={!!chipProps.onClick}
color="primary"
icon={<ChipIcon loading={chipProps.loading} onClick={chipProps.onClick} />}
label={<CollectionType value="Featured" />}
onClick={chipProps.onClick}
variant="filled"
/>
),
Sale: (chipProps) => (
<Chip
clickable={!!chipProps.onClick}
color="error"
icon={<ChipIcon loading={chipProps.loading} onClick={chipProps.onClick} />}
label={<CollectionType value="Sale" />}
onClick={chipProps.onClick}
variant="filled"
/>
),
}}
formattedMessageMap={collectionTypeFormattedMessageMap}
loading={loading}
onSelectItem={onSelectItem}
sortOrder={collectionTypeSortOrder}
value={value}
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { SelectField, type SelectFieldProps } from "@comet/admin";
import { recordToOptions } from "@src/common/components/enums/recordToOptions/recordToOptions";
import { type GQLCollectionType } from "@src/graphql.generated";
import { collectionTypeFormattedMessageMap } from "@src/products/components/collectionType/CollectionType";
import { type FunctionComponent } from "react";

type CollectionTypeFormState = GQLCollectionType;

type CollectionTypeSelectFieldProps = Omit<SelectFieldProps<CollectionTypeFormState>, "options">;

export const CollectionTypeSelectField: FunctionComponent<CollectionTypeSelectFieldProps> = ({ name, ...restProps }) => {
return <SelectField name={name} {...restProps} options={recordToOptions(collectionTypeFormattedMessageMap)} />;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { gql } from "@apollo/client";

export const productCollectionFormFragment = gql`
fragment ProductCollectionFormDetails on ProductCollection {
name
slug
description
validFrom
validTo
isActive
collectionType
}
`;

export const productCollectionQuery = gql`
query ProductCollectionFormQuery($id: ID!) {
productCollection(id: $id) {
id
updatedAt
...ProductCollectionFormDetails
}
}
${productCollectionFormFragment}
`;

export const createProductCollectionMutation = gql`
mutation CreateProductCollection($scope: ProductCollectionScopeInput!, $input: ProductCollectionInput!) {
createProductCollection(scope: $scope, input: $input) {
productCollection {
id
updatedAt
...ProductCollectionFormDetails
}
errors {
code
field
}
}
}
${productCollectionFormFragment}
`;

export const updateProductCollectionMutation = gql`
mutation UpdateProductCollection($id: ID!, $input: ProductCollectionUpdateInput!) {
updateProductCollection(id: $id, input: $input) {
productCollection {
id
updatedAt
...ProductCollectionFormDetails
}
errors {
code
field
}
}
}
${productCollectionFormFragment}
`;
Loading