From 56ea03b1896eba1cd5ac5a456e2d1b3024182cfb Mon Sep 17 00:00:00 2001
From: Hugo FOYART <11079152+foyarash@users.noreply.github.com>
Date: Mon, 7 Oct 2024 12:02:36 +0200
Subject: [PATCH 1/4] feat: add depth selection for actions (#470)
---
.changeset/dry-tips-thank.md | 5 +++
.../pages/docs/api/model-configuration.mdx | 6 +++
apps/example/options.tsx | 1 +
packages/next-admin/src/appHandler.ts | 16 ++++++-
.../src/components/ClientActionDialog.tsx | 13 ++++--
packages/next-admin/src/pageHandler.ts | 13 +++++-
packages/next-admin/src/types.ts | 6 +++
packages/next-admin/src/utils/prisma.ts | 44 +++++++++++++++----
packages/next-admin/src/utils/server.ts | 4 ++
9 files changed, 94 insertions(+), 14 deletions(-)
create mode 100644 .changeset/dry-tips-thank.md
diff --git a/.changeset/dry-tips-thank.md b/.changeset/dry-tips-thank.md
new file mode 100644
index 00000000..a5d34e75
--- /dev/null
+++ b/.changeset/dry-tips-thank.md
@@ -0,0 +1,5 @@
+---
+"@premieroctet/next-admin": patch
+---
+
+feat: add depth selection for actions (#443)
diff --git a/apps/docs/pages/docs/api/model-configuration.mdx b/apps/docs/pages/docs/api/model-configuration.mdx
index 257a1296..38707bea 100644
--- a/apps/docs/pages/docs/api/model-configuration.mdx
+++ b/apps/docs/pages/docs/api/model-configuration.mdx
@@ -652,6 +652,12 @@ The `actions` property is an array of objects that allows you to define a set of
description:
"a message that will be displayed when the action fails if action doesn't return a message object or throw an error with a message",
},
+ {
+ name: "depth",
+ type: "Number",
+ description:
+ "a number that defines the depth of the relations to select in the resource. Use this with caution, a number too high can potentially cause slower queries. Defaults to 2.",
+ },
]}
/>
diff --git a/apps/example/options.tsx b/apps/example/options.tsx
index db180c47..0b9025a9 100644
--- a/apps/example/options.tsx
+++ b/apps/example/options.tsx
@@ -189,6 +189,7 @@ export const options: NextAdminOptions = {
id: "user-details",
title: "actions.user.details.title",
component: ,
+ depth: 3,
},
],
},
diff --git a/packages/next-admin/src/appHandler.ts b/packages/next-admin/src/appHandler.ts
index 9596a721..6271afb6 100644
--- a/packages/next-admin/src/appHandler.ts
+++ b/packages/next-admin/src/appHandler.ts
@@ -58,11 +58,25 @@ export const createHandler =
({
?.split(",")
.map((id) => formatId(resource, id));
+ const depth = req.nextUrl.searchParams.get("depth");
+
if (!ids) {
return NextResponse.json({ error: "No ids provided" }, { status: 400 });
}
- const data = await getRawData({ prisma, resource, resourceIds: ids });
+ if (depth && isNaN(Number(depth))) {
+ return NextResponse.json(
+ { error: "Depth should be a number" },
+ { status: 400 }
+ );
+ }
+
+ const data = await getRawData({
+ prisma,
+ resource,
+ resourceIds: ids,
+ maxDepth: depth ? Number(depth) : undefined,
+ });
return NextResponse.json(data);
})
diff --git a/packages/next-admin/src/components/ClientActionDialog.tsx b/packages/next-admin/src/components/ClientActionDialog.tsx
index 49e0fae0..3dcd5f3d 100644
--- a/packages/next-admin/src/components/ClientActionDialog.tsx
+++ b/packages/next-admin/src/components/ClientActionDialog.tsx
@@ -42,9 +42,16 @@ const ClientActionDialog = ({
useEffect(() => {
setIsLoading(true);
- fetch(
- `${apiBasePath}/${slugify(resource)}/raw?ids=${resourceIds.join(",")}`
- )
+ const params = new URLSearchParams();
+
+ params.set("ids", resourceIds.join(","));
+
+ if (action.depth) {
+ // Avoid negative depth
+ params.set("depth", Math.max(1, action.depth).toString());
+ }
+
+ fetch(`${apiBasePath}/${slugify(resource)}/raw?${params.toString()}`)
.then((res) => res.json())
.then(setData)
.finally(() => {
diff --git a/packages/next-admin/src/pageHandler.ts b/packages/next-admin/src/pageHandler.ts
index bcaef78a..319e305a 100644
--- a/packages/next-admin/src/pageHandler.ts
+++ b/packages/next-admin/src/pageHandler.ts
@@ -90,7 +90,18 @@ export const createHandler = ({
ids = ids?.split(",").map((id: string) => formatId(resource, id));
}
- const data = await getRawData({ prisma, resource, resourceIds: ids });
+ const depth = req.query.depth;
+
+ if (depth && isNaN(Number(depth))) {
+ return res.status(400).json({ error: "Depth should be a number" });
+ }
+
+ const data = await getRawData({
+ prisma,
+ resource,
+ resourceIds: ids,
+ maxDepth: depth ? Number(depth) : undefined,
+ });
return res.json(data);
})
diff --git a/packages/next-admin/src/types.ts b/packages/next-admin/src/types.ts
index 774c17b7..4477c1c5 100644
--- a/packages/next-admin/src/types.ts
+++ b/packages/next-admin/src/types.ts
@@ -549,6 +549,12 @@ export type BareModelAction = {
canExecute?: (item: Model) => boolean;
icon?: keyof typeof OutlineIcons;
style?: ActionStyle;
+ /**
+ * Max depth of the related records to select
+ *
+ * @default 2
+ */
+ depth?: number;
};
export type ServerAction = {
diff --git a/packages/next-admin/src/utils/prisma.ts b/packages/next-admin/src/utils/prisma.ts
index f9ecb028..45b58d15 100644
--- a/packages/next-admin/src/utils/prisma.ts
+++ b/packages/next-admin/src/utils/prisma.ts
@@ -661,6 +661,38 @@ export const getDataItem = async ({
return data;
};
+type DeepIncludeRecord = Record;
+
+const includeDataByDepth = (
+ model: Prisma.DMMF.Model,
+ currentDepth: number,
+ maxDepth: number
+) => {
+ const include = model?.fields.reduce((acc, field) => {
+ if (field.kind === "object") {
+ /**
+ * We substract because, if the condition matches,
+ * we will have all the fields in the related model, which are
+ * counted in currentDepth + 1
+ */
+ if (currentDepth < maxDepth - 1) {
+ acc[field.name] = {
+ include: includeDataByDepth(
+ getPrismaModelForResource(field.type as M)!,
+ currentDepth + 1,
+ maxDepth
+ ),
+ };
+ } else {
+ acc[field.name] = true;
+ }
+ }
+ return acc;
+ }, {} as DeepIncludeRecord);
+
+ return include;
+};
+
/**
* Get raw data from Prisma (2-deep nested relations)
* @param prisma
@@ -672,22 +704,16 @@ export const getRawData = async ({
prisma,
resource,
resourceIds,
+ maxDepth = 2,
}: {
prisma: PrismaClient;
resource: M;
resourceIds: Array;
+ maxDepth?: number;
}): Promise[]> => {
const modelDMMF = getPrismaModelForResource(resource);
- const include = modelDMMF?.fields.reduce(
- (acc, field) => {
- if (field.kind === "object") {
- acc[field.name] = true;
- }
- return acc;
- },
- {} as Record
- );
+ const include = includeDataByDepth(modelDMMF!, 1, maxDepth);
// @ts-expect-error
const data = await prisma[resource].findMany({
diff --git a/packages/next-admin/src/utils/server.ts b/packages/next-admin/src/utils/server.ts
index a8e3299a..e39729ec 100644
--- a/packages/next-admin/src/utils/server.ts
+++ b/packages/next-admin/src/utils/server.ts
@@ -50,10 +50,14 @@ export const getEnableToExecuteActions = async (
actions?: Omit, "action">[]
): Promise => {
if (actions?.some((action) => action.canExecute)) {
+ const maxDepth = Math.max(0, ...actions.map((action) => action.depth ?? 0));
+
const data: Model[] = await getRawData({
prisma,
resource,
resourceIds: ids,
+ // Apply the default value if its 0
+ maxDepth: maxDepth || undefined,
});
return actions?.reduce(
From 3225788bd475ff27a537f1eefc8e4da4bbaa14ff Mon Sep 17 00:00:00 2001
From: Regourd Colin
Date: Mon, 7 Oct 2024 12:03:28 +0200
Subject: [PATCH 2/4] Fix image (#472)
---
.changeset/witty-snakes-shout.md | 5 +++++
apps/example/options.tsx | 2 +-
.../src/components/inputs/FileWidget.tsx | 18 ++++++++++++++----
packages/next-admin/src/utils/server.ts | 7 ++++---
4 files changed, 24 insertions(+), 8 deletions(-)
create mode 100644 .changeset/witty-snakes-shout.md
diff --git a/.changeset/witty-snakes-shout.md b/.changeset/witty-snakes-shout.md
new file mode 100644
index 00000000..218d6418
--- /dev/null
+++ b/.changeset/witty-snakes-shout.md
@@ -0,0 +1,5 @@
+---
+"@premieroctet/next-admin": patch
+---
+
+Fix image (get async)
diff --git a/apps/example/options.tsx b/apps/example/options.tsx
index 0b9025a9..3c281835 100644
--- a/apps/example/options.tsx
+++ b/apps/example/options.tsx
@@ -1,5 +1,5 @@
-import UserDetailsDialog from "@/components/UserDetailsDialogContent";
import AddTagDialog from "@/components/PostAddTagDialogContent";
+import UserDetailsDialog from "@/components/UserDetailsDialogContent";
import { NextAdminOptions } from "@premieroctet/next-admin";
import DatePicker from "./components/DatePicker";
import PasswordInput from "./components/PasswordInput";
diff --git a/packages/next-admin/src/components/inputs/FileWidget.tsx b/packages/next-admin/src/components/inputs/FileWidget.tsx
index 35276853..e0fb30e0 100644
--- a/packages/next-admin/src/components/inputs/FileWidget.tsx
+++ b/packages/next-admin/src/components/inputs/FileWidget.tsx
@@ -9,12 +9,12 @@ import { ChangeEvent, useEffect, useRef, useState } from "react";
import Loader from "../../assets/icons/Loader";
import { useFormState } from "../../context/FormStateContext";
import { useI18n } from "../../context/I18nContext";
-import { getFilenameFromUrl, isImageType } from "../../utils/file";
+import { getFilenameFromUrl } from "../../utils/file";
const FileWidget = (props: WidgetProps) => {
const [file, setFile] = useState();
const [errors, setErrors] = useState(props.rawErrors);
- const [fileIsImage, setFileIsImage] = useState(false);
+ const [fileIsImage, setFileIsImage] = useState(true);
const [filename, setFilename] = useState(null);
const [fileUrl, setFileUrl] = useState(props.value);
const [isPending, setIsPending] = useState(false);
@@ -35,12 +35,22 @@ const FileWidget = (props: WidgetProps) => {
useEffect(() => {
if (props.value) {
setIsPending(true);
- setFileUrl(props.value);
+
+ const image = document.createElement("img");
+ image.src = props.value as string;
+ image.onload = () => {
+ setFileIsImage(true);
+ setIsPending(false);
+ };
+ image.onerror = (e) => {
+ console.error(e);
+ setFileIsImage(false);
+ setIsPending(false);
+ };
const filename = getFilenameFromUrl(props.value);
if (filename) {
setFilename(filename);
}
- setFileIsImage(isImageType(props.value));
setIsPending(false);
} else {
setIsPending(false);
diff --git a/packages/next-admin/src/utils/server.ts b/packages/next-admin/src/utils/server.ts
index e39729ec..182fb51c 100644
--- a/packages/next-admin/src/utils/server.ts
+++ b/packages/next-admin/src/utils/server.ts
@@ -317,7 +317,8 @@ export const transformData = (
const model = models.find((model) => model.name === modelName);
if (!model) return data;
- return Object.keys(data).reduce((acc, key) => {
+ return Object.keys(data).reduce(async (accP, key) => {
+ const acc = await accP;
const field = model.fields?.find((field) => field.name === key);
const fieldKind = field?.kind;
const get = editOptions?.fields?.[key as Field]?.handler?.get;
@@ -326,7 +327,7 @@ export const transformData = (
editOptions?.fields?.[key as Field]?.relationshipSearchField;
if (get) {
- acc[key] = get(data[key]);
+ acc[key] = await get(data[key]);
} else if (fieldKind === "enum") {
const value = data[key];
if (Array.isArray(value)) {
@@ -414,7 +415,7 @@ export const transformData = (
}
}
return acc;
- }, {} as any);
+ }, Promise.resolve({}) as any);
};
/**
From 81b2e54b40b212dd9fdd7211e1d57bd6ced3c176 Mon Sep 17 00:00:00 2001
From: Regourd Colin
Date: Mon, 7 Oct 2024 12:04:11 +0200
Subject: [PATCH 3/4] Nullable realtionship (#473)
---
.changeset/wise-snakes-boil.md | 5 +++++
packages/next-admin/src/components/inputs/SelectWidget.tsx | 4 ++--
2 files changed, 7 insertions(+), 2 deletions(-)
create mode 100644 .changeset/wise-snakes-boil.md
diff --git a/.changeset/wise-snakes-boil.md b/.changeset/wise-snakes-boil.md
new file mode 100644
index 00000000..88b42a31
--- /dev/null
+++ b/.changeset/wise-snakes-boil.md
@@ -0,0 +1,5 @@
+---
+"@premieroctet/next-admin": patch
+---
+
+Fix relation one-to-many - nullable relation
diff --git a/packages/next-admin/src/components/inputs/SelectWidget.tsx b/packages/next-admin/src/components/inputs/SelectWidget.tsx
index 5c57b740..e86747a7 100644
--- a/packages/next-admin/src/components/inputs/SelectWidget.tsx
+++ b/packages/next-admin/src/components/inputs/SelectWidget.tsx
@@ -36,7 +36,7 @@ const SelectWidget = ({
const { basePath } = useConfig();
- const handleChange = (option: Enumeration) => {
+ const handleChange = (option: Enumeration | null) => {
setFieldDirty(props.name);
onChange(option);
onClose();
@@ -95,7 +95,7 @@ const SelectWidget = ({
className="flex items-center"
onClick={(e) => {
e.preventDefault();
- onChange({});
+ handleChange(null);
}}
>
From 0d2b092f83cc4820a3c6651727b64bb40519a34e Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Mon, 7 Oct 2024 12:37:33 +0200
Subject: [PATCH 4/4] Version Packages (#474)
Co-authored-by: github-actions[bot]
---
.changeset/dry-tips-thank.md | 5 -----
.changeset/wise-snakes-boil.md | 5 -----
.changeset/witty-snakes-shout.md | 5 -----
apps/docs/package.json | 2 +-
apps/docs/pages/changelog/index.md | 8 ++++++++
apps/example/package.json | 2 +-
packages/next-admin/CHANGELOG.md | 8 ++++++++
packages/next-admin/package.json | 2 +-
yarn.lock | 6 +++---
9 files changed, 22 insertions(+), 21 deletions(-)
delete mode 100644 .changeset/dry-tips-thank.md
delete mode 100644 .changeset/wise-snakes-boil.md
delete mode 100644 .changeset/witty-snakes-shout.md
diff --git a/.changeset/dry-tips-thank.md b/.changeset/dry-tips-thank.md
deleted file mode 100644
index a5d34e75..00000000
--- a/.changeset/dry-tips-thank.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-"@premieroctet/next-admin": patch
----
-
-feat: add depth selection for actions (#443)
diff --git a/.changeset/wise-snakes-boil.md b/.changeset/wise-snakes-boil.md
deleted file mode 100644
index 88b42a31..00000000
--- a/.changeset/wise-snakes-boil.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-"@premieroctet/next-admin": patch
----
-
-Fix relation one-to-many - nullable relation
diff --git a/.changeset/witty-snakes-shout.md b/.changeset/witty-snakes-shout.md
deleted file mode 100644
index 218d6418..00000000
--- a/.changeset/witty-snakes-shout.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-"@premieroctet/next-admin": patch
----
-
-Fix image (get async)
diff --git a/apps/docs/package.json b/apps/docs/package.json
index afe390d2..ab170e57 100644
--- a/apps/docs/package.json
+++ b/apps/docs/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@heroicons/react": "^2.1.1",
- "@premieroctet/next-admin": "6.1.5",
+ "@premieroctet/next-admin": "6.1.6",
"clsx": "^2.1.0",
"framer-motion": "^11.0.8",
"mini-svg-data-uri": "^1.4.4",
diff --git a/apps/docs/pages/changelog/index.md b/apps/docs/pages/changelog/index.md
index b1fdf5a4..c813b54f 100644
--- a/apps/docs/pages/changelog/index.md
+++ b/apps/docs/pages/changelog/index.md
@@ -1,5 +1,13 @@
# @premieroctet/next-admin
+## 6.1.6
+
+### Patch Changes
+
+- [56ea03b](https://github.com/premieroctet/next-admin/commit/56ea03b): feat: add depth selection for actions ([#443](https://github.com/premieroctet/next-admin/issues/443))
+- [81b2e54](https://github.com/premieroctet/next-admin/commit/81b2e54): Fix relation one-to-many - nullable relation
+- [3225788](https://github.com/premieroctet/next-admin/commit/3225788): Fix image (get async)
+
## 6.1.5
### Patch Changes
diff --git a/apps/example/package.json b/apps/example/package.json
index 8b24c189..393dbd1b 100644
--- a/apps/example/package.json
+++ b/apps/example/package.json
@@ -20,7 +20,7 @@
"dependencies": {
"@heroicons/react": "^2.0.18",
"@picocss/pico": "^1.5.7",
- "@premieroctet/next-admin": "6.1.5",
+ "@premieroctet/next-admin": "6.1.6",
"@prisma/client": "5.14.0",
"@tremor/react": "^3.2.2",
"babel-plugin-superjson-next": "^0.4.5",
diff --git a/packages/next-admin/CHANGELOG.md b/packages/next-admin/CHANGELOG.md
index b1fdf5a4..c813b54f 100644
--- a/packages/next-admin/CHANGELOG.md
+++ b/packages/next-admin/CHANGELOG.md
@@ -1,5 +1,13 @@
# @premieroctet/next-admin
+## 6.1.6
+
+### Patch Changes
+
+- [56ea03b](https://github.com/premieroctet/next-admin/commit/56ea03b): feat: add depth selection for actions ([#443](https://github.com/premieroctet/next-admin/issues/443))
+- [81b2e54](https://github.com/premieroctet/next-admin/commit/81b2e54): Fix relation one-to-many - nullable relation
+- [3225788](https://github.com/premieroctet/next-admin/commit/3225788): Fix image (get async)
+
## 6.1.5
### Patch Changes
diff --git a/packages/next-admin/package.json b/packages/next-admin/package.json
index 4d1b800a..b729ab51 100644
--- a/packages/next-admin/package.json
+++ b/packages/next-admin/package.json
@@ -1,6 +1,6 @@
{
"name": "@premieroctet/next-admin",
- "version": "6.1.5",
+ "version": "6.1.6",
"description": "Next-Admin provides a customizable and turnkey admin dashboard for applications built with Next.js and powered by the Prisma ORM. It aims to simplify the development process by providing a turnkey admin system that can be easily integrated into your project.",
"keywords": [
"next.js",
diff --git a/yarn.lock b/yarn.lock
index 5a6b693d..edf4ed7c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2729,7 +2729,7 @@ __metadata:
languageName: unknown
linkType: soft
-"@premieroctet/next-admin@npm:6.1.5, @premieroctet/next-admin@workspace:packages/next-admin":
+"@premieroctet/next-admin@npm:6.1.6, @premieroctet/next-admin@workspace:packages/next-admin":
version: 0.0.0-use.local
resolution: "@premieroctet/next-admin@workspace:packages/next-admin"
dependencies:
@@ -6949,7 +6949,7 @@ __metadata:
dependencies:
"@babel/core": "npm:^7.0.0"
"@heroicons/react": "npm:^2.1.1"
- "@premieroctet/next-admin": "npm:6.1.5"
+ "@premieroctet/next-admin": "npm:6.1.6"
"@types/node": "npm:^17.0.12"
"@types/react": "npm:^18.2.0"
"@types/react-dom": "npm:^18.2.0"
@@ -8012,7 +8012,7 @@ __metadata:
"@heroicons/react": "npm:^2.0.18"
"@picocss/pico": "npm:^1.5.7"
"@playwright/test": "npm:^1.37.0"
- "@premieroctet/next-admin": "npm:6.1.5"
+ "@premieroctet/next-admin": "npm:6.1.6"
"@prisma/client": "npm:5.14.0"
"@tremor/react": "npm:^3.2.2"
"@types/node": "npm:^17.0.12"