Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Admin Generator (Future): Forward selectionProps to support implementing AssignGrid #2449

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
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
78 changes: 78 additions & 0 deletions demo/admin/src/products/future/AssignProductsGrid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { gql, useApolloClient, useQuery } from "@apollo/client";
import { Loading, Savable } from "@comet/admin";
import {
GQLGetProductIdsForProductCategoryQuery,
GQLGetProductIdsForProductCategoryQueryVariables,
GQLSetProductCategoryMutation,
GQLSetProductCategoryMutationVariables,
} from "@src/products/future/AssignProductsGrid.generated";
import { ProductsGrid as SelectProductsGrid } from "@src/products/future/generated/SelectProductsGrid";
import isEqual from "lodash.isequal";
import React, { useState } from "react";
import { FormattedMessage } from "react-intl";

const setProductCategoryMutation = gql`
mutation SetProductCategory($id: ID!, $input: [ID!]!) {
updateProductCategory(id: $id, input: { products: $input }) {
id
}
}
`;

const getProductIdsForProductCategory = gql`
query GetProductIdsForProductCategory($id: ID!) {
products(filter: { category: { equal: $id } }) {
nodes {
id
}
}
}
`;

interface FormProps {
productCategoryId: string;
}

export function AssignProductsGrid({ productCategoryId }: FormProps): React.ReactElement {
const client = useApolloClient();

const { data, error, loading } = useQuery<GQLGetProductIdsForProductCategoryQuery, GQLGetProductIdsForProductCategoryQueryVariables>(
getProductIdsForProductCategory,
{
variables: { id: productCategoryId },
onCompleted: (data) => {
setValues(data.products.nodes.map((product) => product.id));
},
},
);

const [values, setValues] = useState<string[]>([]);

if (error) return <FormattedMessage id="common.error" defaultMessage="An error has occured. Please try again at later" />;
if (loading) return <Loading />;

return (
<>
<Savable
doSave={async () => {
await client.mutate<GQLSetProductCategoryMutation, GQLSetProductCategoryMutationVariables>({
mutation: setProductCategoryMutation,
variables: { id: productCategoryId, input: values },
update: (cache, result) => cache.evict({ fieldName: "products" }),
});
return true;
}}
hasChanges={!isEqual((data?.products.nodes.map((product) => product.id) ?? []).sort(), values.sort())}
doReset={() => {
setValues(data?.products.nodes.map((product) => product.id) ?? []);
}}
/>
<SelectProductsGrid
selectionModel={values}
onSelectionModelChange={(newSelectionModel) => {
setValues(newSelectionModel.map((rowId) => String(rowId)));
}}
/>
</>
);
}
18 changes: 18 additions & 0 deletions demo/admin/src/products/future/SelectProductsGrid.cometGen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { future_GridConfig as GridConfig } from "@comet/cms-admin";
import { GQLProduct } from "@src/graphql.generated";

export const SelectProductsGrid: GridConfig<GQLProduct> = {
type: "grid",
gqlType: "Product",
fragmentName: "SelectProductsGridFuture",
readOnly: true,
selectionProps: "multiSelect",
columns: [
{ type: "text", name: "title", headerName: "Titel", minWidth: 200, maxWidth: 250 },
{ type: "text", name: "description", headerName: "Description" },
{ type: "number", name: "price", headerName: "Price", maxWidth: 150 },
{ type: "staticSelect", name: "type", maxWidth: 150, values: [{ value: "Cap", label: "great Cap" }, "Shirt", "Tie"] },
{ type: "date", name: "availableSince", width: 140 },
{ type: "dateTime", name: "createdAt", width: 170 },
],
};
164 changes: 164 additions & 0 deletions demo/admin/src/products/future/generated/SelectProductsGrid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
// This file has been generated by comet admin-generator.
// You may choose to use this file as scaffold by moving this file out of generated folder and removing this comment.
import { gql, useQuery } from "@apollo/client";
import {
DataGridToolbar,
GridColDef,
GridFilterButton,
muiGridFilterToGql,
muiGridSortToGql,
renderStaticSelectCell,
ToolbarFillSpace,
ToolbarItem,
useBufferedRowCount,
useDataGridRemote,
usePersistentColumnState,
} from "@comet/admin";
import { DataGridPro, DataGridProProps, GridToolbarQuickFilter } from "@mui/x-data-grid-pro";
import * as React from "react";
import { useIntl } from "react-intl";

import { GQLProductsGridQuery, GQLProductsGridQueryVariables, GQLSelectProductsGridFutureFragment } from "./SelectProductsGrid.generated";

const productsFragment = gql`
fragment SelectProductsGridFuture on Product {
id
title
description
price
type
availableSince
createdAt
}
`;

const productsQuery = gql`
query ProductsGrid($offset: Int!, $limit: Int!, $sort: [ProductSort!], $search: String, $filter: ProductFilter) {
products(offset: $offset, limit: $limit, sort: $sort, search: $search, filter: $filter) {
nodes {
...SelectProductsGridFuture
}
totalCount
}
}
${productsFragment}
`;

function ProductsGridToolbar() {
return (
<DataGridToolbar>
<ToolbarItem>
<GridToolbarQuickFilter />
</ToolbarItem>
<ToolbarItem>
<GridFilterButton />
</ToolbarItem>
<ToolbarFillSpace />
</DataGridToolbar>
);
}

type Props = {
selectionModel?: DataGridProProps["selectionModel"];
onSelectionModelChange?: DataGridProProps["onSelectionModelChange"];
};

export function ProductsGrid({ selectionModel, onSelectionModelChange }: Props): React.ReactElement {
const intl = useIntl();
const dataGridProps = {
...useDataGridRemote(),
...usePersistentColumnState("ProductsGrid"),
selectionModel,
onSelectionModelChange,
checkboxSelection: true,
keepNonExistentRowsSelected: true,
};

const columns: GridColDef<GQLSelectProductsGridFutureFragment>[] = [
{ field: "title", headerName: intl.formatMessage({ id: "product.title", defaultMessage: "Titel" }), flex: 1, minWidth: 200, maxWidth: 250 },
{
field: "description",
headerName: intl.formatMessage({ id: "product.description", defaultMessage: "Description" }),
flex: 1,
minWidth: 150,
},
{
field: "price",
headerName: intl.formatMessage({ id: "product.price", defaultMessage: "Price" }),
type: "number",
flex: 1,
minWidth: 150,
maxWidth: 150,
},
{
field: "type",
headerName: intl.formatMessage({ id: "product.type", defaultMessage: "Type" }),
type: "singleSelect",
valueFormatter: ({ value }) => value?.toString(),
valueOptions: [
{
value: "Cap",
label: intl.formatMessage({ id: "product.type.cap", defaultMessage: "great Cap" }),
},
{
value: "Shirt",
label: intl.formatMessage({ id: "product.type.shirt", defaultMessage: "Shirt" }),
},
{
value: "Tie",
label: intl.formatMessage({ id: "product.type.tie", defaultMessage: "Tie" }),
},
],
renderCell: renderStaticSelectCell,
flex: 1,
minWidth: 150,
maxWidth: 150,
},
{
field: "availableSince",
headerName: intl.formatMessage({ id: "product.availableSince", defaultMessage: "Available Since" }),
type: "date",
valueGetter: ({ row }) => row.availableSince && new Date(row.availableSince),
valueFormatter: ({ value }) => (value ? intl.formatDate(value) : ""),
width: 140,
},
{
field: "createdAt",
headerName: intl.formatMessage({ id: "product.createdAt", defaultMessage: "Created At" }),
type: "dateTime",
valueGetter: ({ row }) => row.createdAt && new Date(row.createdAt),
valueFormatter: ({ value }) =>
value ? intl.formatDate(value, { day: "numeric", month: "numeric", year: "numeric", hour: "numeric", minute: "numeric" }) : "",
width: 170,
},
];

const { filter: gqlFilter, search: gqlSearch } = muiGridFilterToGql(columns, dataGridProps.filterModel);

const { data, loading, error } = useQuery<GQLProductsGridQuery, GQLProductsGridQueryVariables>(productsQuery, {
variables: {
filter: gqlFilter,
search: gqlSearch,
offset: dataGridProps.page * dataGridProps.pageSize,
limit: dataGridProps.pageSize,
sort: muiGridSortToGql(dataGridProps.sortModel),
},
});
const rowCount = useBufferedRowCount(data?.products.totalCount);
if (error) throw error;
const rows = data?.products.nodes ?? [];

return (
<DataGridPro
{...dataGridProps}
disableSelectionOnClick
rows={rows}
rowCount={rowCount}
columns={columns}
loading={loading}
components={{
Toolbar: ProductsGridToolbar,
}}
/>
);
}
2 changes: 2 additions & 0 deletions demo/api/schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -1483,12 +1483,14 @@ input ProductCategoryInput {
title: String!
slug: String!
position: Int
products: [ID!]! = []
}

input ProductCategoryUpdateInput {
title: String
slug: String
position: Int
products: [ID!]
}

input ProductTagInput {
Expand Down
2 changes: 1 addition & 1 deletion demo/api/src/products/entities/product-category.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export class ProductCategory extends BaseEntity<ProductCategory, "id"> {
//search: true, //not implemented
//filter: true, //not implemented
//sort: true, //not implemented
input: false, //default is true
input: true, //default is true
johnnyomair marked this conversation as resolved.
Show resolved Hide resolved
})
@OneToMany(() => Product, (products) => products.category)
products = new Collection<Product>(this);
Expand Down
9 changes: 7 additions & 2 deletions demo/api/src/products/generated/dto/product-category.input.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// This file has been generated by comet api-generator.
// You may choose to use this file as scaffold by moving this file out of generated folder and removing this comment.
import { IsSlug, PartialType } from "@comet/cms-api";
import { Field, InputType, Int } from "@nestjs/graphql";
import { IsInt, IsNotEmpty, IsOptional, IsString, Min } from "class-validator";
import { Field, ID, InputType, Int } from "@nestjs/graphql";
import { IsArray, IsInt, IsNotEmpty, IsOptional, IsString, IsUUID, Min } from "class-validator";

@InputType()
export class ProductCategoryInput {
Expand All @@ -22,6 +22,11 @@ export class ProductCategoryInput {
@IsInt()
@Field(() => Int, { nullable: true })
position?: number;

@Field(() => [ID], { defaultValue: [] })
@IsArray()
@IsUUID(undefined, { each: true })
products: string[];
}

@InputType()
Expand Down
23 changes: 20 additions & 3 deletions demo/api/src/products/generated/product-category.resolver.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// This file has been generated by comet api-generator.
// You may choose to use this file as scaffold by moving this file out of generated folder and removing this comment.
import { AffectedEntity, extractGraphqlFields, gqlArgsToMikroOrmQuery, RequiredPermission } from "@comet/cms-api";
import { FindOptions } from "@mikro-orm/core";
import { FindOptions, Reference } from "@mikro-orm/core";
import { InjectRepository } from "@mikro-orm/nestjs";
import { EntityManager, EntityRepository } from "@mikro-orm/postgresql";
import { Args, ID, Info, Mutation, Parent, Query, ResolveField, Resolver } from "@nestjs/graphql";
Expand All @@ -21,6 +21,7 @@ export class ProductCategoryResolver {
private readonly entityManager: EntityManager,
private readonly productCategoriesService: ProductCategoriesService,
@InjectRepository(ProductCategory) private readonly repository: EntityRepository<ProductCategory>,
@InjectRepository(Product) private readonly productRepository: EntityRepository<Product>,
) {}

@Query(() => ProductCategory)
Expand Down Expand Up @@ -75,11 +76,19 @@ export class ProductCategoryResolver {
position = lastPosition + 1;
}

const { products: productsInput, ...assignInput } = input;
const productCategory = this.repository.create({
...input,
...assignInput,
position,
});

if (productsInput) {
const products = await this.productRepository.find({ id: productsInput });
if (products.length != productsInput.length) throw new Error("Couldn't find all products that were passed as input");
await productCategory.products.loadItems();
productCategory.products.set(products.map((product) => Reference.create(product)));
}

await this.entityManager.flush();

return productCategory;
Expand All @@ -105,10 +114,18 @@ export class ProductCategoryResolver {
}
}

const { products: productsInput, ...assignInput } = input;
productCategory.assign({
...input,
...assignInput,
});

if (productsInput) {
const products = await this.productRepository.find({ id: productsInput });
if (products.length != productsInput.length) throw new Error("Couldn't find all products that were passed as input");
await productCategory.products.loadItems();
productCategory.products.set(products.map((product) => Reference.create(product)));
}

await this.entityManager.flush();

return productCategory;
Expand Down
Loading