From c1c51e1aaf74b68d6f89ea4a85ffb4ec2c418b59 Mon Sep 17 00:00:00 2001 From: Colin Regourd Date: Tue, 10 Oct 2023 17:58:23 +0200 Subject: [PATCH 1/3] Add formatting system to relationship column and fix relationshsip one-to-many issue --- apps/example/pages/admin/[[...nextadmin]].tsx | 17 +++++++-- packages/next-admin/src/components/Cell.tsx | 19 +++++----- packages/next-admin/src/components/List.tsx | 12 +++++-- packages/next-admin/src/types.ts | 10 +++--- packages/next-admin/src/utils/prisma.ts | 36 +++++++++---------- packages/next-admin/src/utils/server.ts | 17 +++++---- .../next-admin/src/utils/validator.test.ts | 2 +- 7 files changed, 66 insertions(+), 47 deletions(-) diff --git a/apps/example/pages/admin/[[...nextadmin]].tsx b/apps/example/pages/admin/[[...nextadmin]].tsx index 1c487b08..d773d2a5 100644 --- a/apps/example/pages/admin/[[...nextadmin]].tsx +++ b/apps/example/pages/admin/[[...nextadmin]].tsx @@ -21,13 +21,24 @@ export default function Admin(props: AdminComponentProps) { list: { fields: { role: { - formatter: (user) => { - return {user.role as string}; + formatter: (role) => { + return {role.toString()}; }, }, }, }, }, + Post: { + list: { + fields: { + author: { + formatter: (author) => { + return {`${author.name} - ${author.email}`}; + }, + } + } + } + } }, }} /> @@ -111,7 +122,7 @@ export const getServerSideProps: GetServerSideProps = async ({ req, res }) => { published: { display: true, }, - authorId: { + author: { display: true, }, categories: { diff --git a/packages/next-admin/src/components/Cell.tsx b/packages/next-admin/src/components/Cell.tsx index 7d3ee5f6..97fef2f0 100644 --- a/packages/next-admin/src/components/Cell.tsx +++ b/packages/next-admin/src/components/Cell.tsx @@ -1,15 +1,16 @@ import React, { ReactNode } from "react"; -import { ListDataFieldValue } from "../types"; +import { ListDataFieldValue, ListDataItem, ModelName } from "../types"; import Link from "next/link"; import clsx from "clsx"; import { useConfig } from "../context/ConfigContext"; type Props = { - cell: ListDataFieldValue | ReactNode; + cell: ListDataFieldValue | ReactNode + formatter: (cell: any) => ReactNode }; -export default function Cell({ cell }: Props) { +export default function Cell({ cell, formatter }: Props) { const { basePath } = useConfig() const isReactNode = (cell: ListDataFieldValue | ReactNode): cell is ReactNode => { @@ -26,32 +27,32 @@ export default function Cell({ cell }: Props) { href={`${basePath}/${cell.value.url}`} className="hover:underline cursor-pointer text-indigo-700 hover:text-indigo-900 font-semibold" > - {cell.value.label} + {formatter(cell.value.label)} ); } else if (cell.type === "count") { return (
-

{cell.value}

+

{formatter(cell.value)}

); } else if (cell.type === "date") { return (
-

{cell.value.toString()}

+

{formatter(cell.value.toString())}

); } } else if (typeof cell === "string") { return (
-

{cell.toString()}

+

{formatter(cell.toString())}

); } else if (typeof cell === "number") { return (
-

{cell.toString()}

+

{formatter(cell.toString())}

); } else if (typeof cell === "boolean") { @@ -63,7 +64,7 @@ export default function Cell({ cell }: Props) { "bg-neutral-50 text-neutral-600" )} > -

{cell.toString()}

+

{formatter(cell.toString())}

); } diff --git a/packages/next-admin/src/components/List.tsx b/packages/next-admin/src/components/List.tsx index ff624121..95c86d3b 100644 --- a/packages/next-admin/src/components/List.tsx +++ b/packages/next-admin/src/components/List.tsx @@ -74,10 +74,18 @@ function List({ resource, data, total, options }: ListProps) { }, cell: ({ row }) => { const modelData = row.original; - const cellData = options?.list?.fields[property as keyof ListFieldsOptions]?.formatter?.(modelData) ?? modelData[property]; + const cellData = modelData[property as keyof ListFieldsOptions]; + const dataFormatter = options?.list?.fields[property as keyof ListFieldsOptions]?.formatter || ((cell: any) => { + if (typeof cell === "object") { + return
{JSON.stringify(cell.id)}
+ } else { + return
{cell}
+ } + }) + return ( - + ); }, }; diff --git a/packages/next-admin/src/types.ts b/packages/next-admin/src/types.ts index 5b6938b9..8e401875 100644 --- a/packages/next-admin/src/types.ts +++ b/packages/next-admin/src/types.ts @@ -10,11 +10,11 @@ export type ModelName = Prisma.ModelName; export type ScalarField = Prisma.TypeMap["model"][T]["payload"]["scalars"]; export type ObjectField = Prisma.TypeMap["model"][T]["payload"]["objects"]; -export type Model = ScalarField & { +export type Model = ScalarField & { [P in keyof ObjectField]: - ObjectField[P] extends { scalars: infer S } ? T extends never ? S : T - : ObjectField[P] extends { scalars: infer S } | null ? T extends never ? S | null : T | null - : ObjectField[P] extends { scalars: infer S }[] ? T extends never ? S[] : T[] + ObjectField[P] extends { scalars: infer S } ? (T extends object ? S : T) + : ObjectField[P] extends { scalars: infer S } | null ? T extends object ? S | null : T | null + : ObjectField[P] extends { scalars: infer S }[] ? T extends object ? S[] : T[] : never; } @@ -142,7 +142,7 @@ export type ListDataFieldValue = export type ListComponentFieldsOptions = { [P in Field]?: { - formatter?: (item: ListDataItem) => ReactNode; + formatter?: (item: Model[P]) => ReactNode; }; }; diff --git a/packages/next-admin/src/utils/prisma.ts b/packages/next-admin/src/utils/prisma.ts index 7033cd2d..4ba1ecda 100644 --- a/packages/next-admin/src/utils/prisma.ts +++ b/packages/next-admin/src/utils/prisma.ts @@ -7,7 +7,7 @@ import { Order, PrismaListRequest, Select, - UField, + Field, } from "../types"; import { ITEMS_PER_PAGE } from "../config"; import { capitalize } from "./tools"; @@ -18,21 +18,21 @@ export const createWherePredicate = ( ) => { return search ? { - OR: fieldsFiltered - ?.filter((field) => field.kind === "scalar") - .map((field) => { - if (field.type === "String") { - return { - [field.name]: { contains: search, mode: "insensitive" }, - }; - } - if (field.type === "Int" && !isNaN(Number(search))) { - return { [field.name]: Number(search) }; - } - return null; - }) - .filter(Boolean), - } + OR: fieldsFiltered + ?.filter((field) => field.kind === "scalar") + .map((field) => { + if (field.type === "String") { + return { + [field.name]: { contains: search, mode: "insensitive" }, + }; + } + if (field.type === "Int" && !isNaN(Number(search))) { + return { [field.name]: Number(search) }; + } + return null; + }) + .filter(Boolean), + } : {}; }; @@ -48,7 +48,7 @@ export const preparePrismaListRequest = ( Number(searchParams.get("itemsPerPage")) || ITEMS_PER_PAGE; let orderBy: Order = {}; - const sortParam = searchParams.get("sortColumn") as UField; + const sortParam = searchParams.get("sortColumn") as Field; const orderValue = searchParams.get("sortDirection") as Prisma.SortOrder; if ( orderValue in Prisma.SortOrder && @@ -66,7 +66,7 @@ export const preparePrismaListRequest = ( const listKeys = Object.keys(list) as Array>; select = listKeys.reduce((acc, column) => { const field = model?.fields.find(({ name }) => name === column); - if (field?.kind === "object") { + if (field?.kind === "object" && field?.isList === true) { if (!acc._count) acc._count = { select: {} }; acc._count.select = { ...acc._count.select, [column]: true }; } else { diff --git a/packages/next-admin/src/utils/server.ts b/packages/next-admin/src/utils/server.ts index 2b82bca1..43c3c6a9 100644 --- a/packages/next-admin/src/utils/server.ts +++ b/packages/next-admin/src/utils/server.ts @@ -205,25 +205,24 @@ export const findRelationInData = async ( dmmfSchema?: Prisma.DMMF.Field[] ) => { dmmfSchema?.forEach((dmmfProperty) => { + const dmmfPropertyName = dmmfProperty.name const dmmfPropertyType = dmmfProperty.type; const dmmfPropertyKind = dmmfProperty.kind; const dmmfPropertyRelationFromFields = dmmfProperty.relationFromFields; const dmmfPropertyRelationToFields = dmmfProperty.relationToFields; - if (dmmfPropertyKind === "object") { + if (dmmfPropertyKind === "object" ) { if ( dmmfPropertyRelationFromFields!.length > 0 && dmmfPropertyRelationToFields!.length > 0 ) { - const relationProperty = dmmfPropertyRelationFromFields![0]; data.map((item) => { - if (item[relationProperty]) { - item[relationProperty] = { + if (item[dmmfPropertyName]) { + item[dmmfPropertyName] = { type: "link", value: { - label: item[relationProperty], - url: `${dmmfProperty.type as ModelName}/${item[relationProperty] - }`, + label: item[dmmfPropertyName], + url: `${dmmfProperty.type as ModelName}/${item[dmmfPropertyName]["id"]}`, }, }; } else { @@ -270,8 +269,8 @@ export const parseFormData = ( const dmmfPropertyType = dmmfProperty.type; const dmmfPropertyKind = dmmfProperty.kind; if (dmmfPropertyKind === "object") { - if(Boolean(formData[dmmfPropertyName])) { - parsedData[dmmfPropertyName] = JSON.parse(formData[dmmfPropertyName] as string) as ModelWithoutRelationships[typeof dmmfPropertyName]; + if (Boolean(formData[dmmfPropertyName])) { + parsedData[dmmfPropertyName] = JSON.parse(formData[dmmfPropertyName] as string) as ModelWithoutRelationships[typeof dmmfPropertyName]; } else { parsedData[dmmfPropertyName] = null as ModelWithoutRelationships[typeof dmmfPropertyName]; } diff --git a/packages/next-admin/src/utils/validator.test.ts b/packages/next-admin/src/utils/validator.test.ts index 196f7e31..383f90af 100644 --- a/packages/next-admin/src/utils/validator.test.ts +++ b/packages/next-admin/src/utils/validator.test.ts @@ -14,7 +14,7 @@ describe("validator", () => { expect(() => validate<"User">( { - id: "1", + id: 1, email: "bob.com", }, { From 0e202999e839a2010fea095998110d2ea8221f74 Mon Sep 17 00:00:00 2001 From: Regourd Colin Date: Tue, 10 Oct 2023 18:10:02 +0200 Subject: [PATCH 2/3] Create early-suns-tan.md --- .changeset/early-suns-tan.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/early-suns-tan.md diff --git a/.changeset/early-suns-tan.md b/.changeset/early-suns-tan.md new file mode 100644 index 00000000..caae90d3 --- /dev/null +++ b/.changeset/early-suns-tan.md @@ -0,0 +1,5 @@ +--- +"@premieroctet/next-admin": patch +--- + +Add formatting system to relationship column From d50198033d6a1973c45d2dc84dada36b146ee648 Mon Sep 17 00:00:00 2001 From: Colin Regourd Date: Tue, 10 Oct 2023 18:11:21 +0200 Subject: [PATCH 3/3] changes changeset --- .changeset/chilly-avocados-wink.md | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 .changeset/chilly-avocados-wink.md diff --git a/.changeset/chilly-avocados-wink.md b/.changeset/chilly-avocados-wink.md deleted file mode 100644 index a845151c..00000000 --- a/.changeset/chilly-avocados-wink.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ----