Skip to content

Commit

Permalink
Demo Admin Products Form: add nested object as fields
Browse files Browse the repository at this point in the history
  • Loading branch information
nsams committed Nov 13, 2023
1 parent 986efd1 commit 187b275
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 83 deletions.
1 change: 1 addition & 0 deletions demo/admin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@
"prettier": "^2.0.0",
"style-loader": "^3.0.0",
"ts-node": "^10.0.0",
"type-fest": "^4.6.0",
"typescript": "^4.0.0",
"webpack": "^5.0.0",
"webpack-cli": "^4.0.0",
Expand Down
5 changes: 5 additions & 0 deletions demo/admin/src/products/ProductForm.gql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ export const productFormFragment = gql`
id
title
}
packageDimensions {
width
height
depth
}
}
`;

Expand Down
60 changes: 49 additions & 11 deletions demo/admin/src/products/ProductForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
FinalFormSaveSplitButton,
FinalFormSelect,
FinalFormSubmitEvent,
FormSection,
Loading,
MainContent,
Toolbar,
Expand All @@ -29,6 +30,7 @@ import { filter } from "graphql-anywhere";
import isEqual from "lodash.isequal";
import React from "react";
import { FormattedMessage } from "react-intl";
import { MergeDeep } from "type-fest";

import {
createProductMutation,
Expand Down Expand Up @@ -62,10 +64,18 @@ const rootBlocks = {
image: DamImageBlock,
};

type FormValues = Omit<GQLProductFormManualFragment, "price" | "image"> & {
price: string;
image: BlockState<typeof rootBlocks.image>;
};
type FormValues = MergeDeep<
GQLProductFormManualFragment,
{
price: string;
image: BlockState<typeof rootBlocks.image>;
packageDimensions: {
width: string;
height: string;
depth: string;
};
}
>;

function ProductForm({ id }: FormProps): React.ReactElement {
const stackApi = useStackApi();
Expand All @@ -84,6 +94,12 @@ function ProductForm({ id }: FormProps): React.ReactElement {
...filter<GQLProductFormManualFragment>(productFormFragment, data.product),
price: String(data.product.price),
image: rootBlocks.image.input2State(data.product.image),
packageDimensions: {
...data.product.packageDimensions,
width: String(data.product.packageDimensions.width),
height: String(data.product.packageDimensions.height),
depth: String(data.product.packageDimensions.depth),
},
}
: {
image: rootBlocks.image.defaultValues(),
Expand Down Expand Up @@ -113,7 +129,12 @@ function ProductForm({ id }: FormProps): React.ReactElement {
variants: [],
articleNumbers: [],
discounts: [],
packageDimensions: { width: 0, height: 0, depth: 0 },
packageDimensions: {
...formValues.packageDimensions,
width: parseFloat(formValues.packageDimensions.width),
height: parseFloat(formValues.packageDimensions.height),
depth: parseFloat(formValues.packageDimensions.depth),
},
statistics: { views: 0 },
};
if (mode === "edit") {
Expand Down Expand Up @@ -228,9 +249,6 @@ function ProductForm({ id }: FormProps): React.ReactElement {
component={FinalFormSelect}
{...categorySelectAsyncProps}
getOptionLabel={(option: GQLProductCategorySelectFragment) => option.title}
getOptionSelected={(option: GQLProductCategorySelectFragment, value: GQLProductCategorySelectFragment) => {
return option.id === value.id;
}}
/>
<Field
fullWidth
Expand All @@ -240,9 +258,6 @@ function ProductForm({ id }: FormProps): React.ReactElement {
multiple
{...tagsSelectAsyncProps}
getOptionLabel={(option: GQLProductTagsSelectFragment) => option.title}
getOptionSelected={(option: GQLProductTagsSelectFragment, value: GQLProductTagsSelectFragment) => {
return option.id === value.id;
}}
/>
<Field
fullWidth
Expand All @@ -262,6 +277,29 @@ function ProductForm({ id }: FormProps): React.ReactElement {
<Field name="image" isEqual={isEqual}>
{createFinalFormBlock(rootBlocks.image)}
</Field>
<FormSection title={<FormattedMessage id="product.packageDimensions" defaultMessage="Package Dimensions" />}>
<Field
fullWidth
name="packageDimensions.width"
component={FinalFormInput}
inputProps={{ type: "number" }}
label={<FormattedMessage id="product.width" defaultMessage="Width" />}
/>
<Field
fullWidth
name="packageDimensions.height"
component={FinalFormInput}
inputProps={{ type: "number" }}
label={<FormattedMessage id="product.height" defaultMessage="Height" />}
/>
<Field
fullWidth
name="packageDimensions.depth"
component={FinalFormInput}
inputProps={{ type: "number" }}
label={<FormattedMessage id="product.depth" defaultMessage="Depth" />}
/>
</FormSection>
</MainContent>
</EditPageLayout>
)}
Expand Down
4 changes: 2 additions & 2 deletions demo/api/schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ type Product implements DocumentInterface {
discounts: [ProductDiscounts!]!
articleNumbers: [String!]!
dimensions: ProductDimensions
packageDimensions: ProductPackageDimensions
packageDimensions: ProductPackageDimensions!
statistics: ProductStatistics
createdAt: DateTime!
category: ProductCategory
Expand Down Expand Up @@ -1116,7 +1116,7 @@ input ProductInput {
discounts: [ProductDiscountsInput!]! = []
articleNumbers: [String!]! = []
dimensions: ProductDimensionsInput
packageDimensions: ProductPackageDimensionsInput
packageDimensions: ProductPackageDimensionsInput!
statistics: ProductStatisticsInput
variants: [ProductVariantInput!]! = []
category: ID = null
Expand Down
6 changes: 3 additions & 3 deletions demo/api/src/products/entities/product.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,9 @@ export class Product extends BaseEntity<Product, "id"> implements DocumentInterf
@Field(() => ProductDimensions, { nullable: true })
dimensions?: ProductDimensions = undefined;

@Embedded(() => ProductPackageDimensions, { nullable: true })
@Field(() => ProductPackageDimensions, { nullable: true })
packageDimensions?: ProductPackageDimensions = undefined;
@Embedded(() => ProductPackageDimensions)
@Field(() => ProductPackageDimensions)
packageDimensions: ProductPackageDimensions;

@OneToOne(() => ProductStatistics, { inversedBy: "product", owner: true, ref: true, nullable: true })
@Field(() => ProductStatistics, { nullable: true })
Expand Down
6 changes: 3 additions & 3 deletions demo/api/src/products/generated/dto/product.input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,11 @@ export class ProductInput {
@Field(() => ProductDimensions, { nullable: true })
dimensions?: ProductDimensions;

@IsNullable()
@IsNotEmpty()
@ValidateNested()
@Type(() => ProductPackageDimensions)
@Field(() => ProductPackageDimensions, { nullable: true })
packageDimensions?: ProductPackageDimensions;
@Field(() => ProductPackageDimensions)
packageDimensions: ProductPackageDimensions;

@IsNullable()
@Field(() => ProductStatisticsInput, { nullable: true })
Expand Down
Loading

0 comments on commit 187b275

Please sign in to comment.