Skip to content

Commit

Permalink
V2 (#59)
Browse files Browse the repository at this point in the history
* Add formatting system to relationship column and fix relationshsip one-to-many issue

* Create early-suns-tan.md

* changes changeset

* Fix ID field doesn't display

* centralize option for next admin

* change options stucture

* Create short-emus-move.md

* fix formatter type

* add test for getMappedDataList function

* change type and add formatter for example

* remove unused stringify for formatter

* restore formatter in example

* add format for date

* Create young-chicken-beam.md

* restore custom formatter in list for date format

* add handler props and fix date utc

* add changeset

* add test and change function

* change options

* fix: store correct date time (#70)

* chore: 🎨 apply prettier to all files

* Remove Date instanciation

* Add datetime widget, send date as iso string

* Remove comment, revert example config

* Fix tests

* Default date time widget value to empty string

* Handle date input

* feat: add file upload widget (#71)

* feat: add file upload widget

* Fix crash on home page

* Fix crash

* Try fixing e2e

* Add e2e artifacts

* Use file format

* Add file preview

* Improve deletion behavior

* Fix wording

* Update documentation

* Fix typo

---------

Co-authored-by: Hugo FOYART <[email protected]>
  • Loading branch information
cregourd and foyarash authored Nov 22, 2023
1 parent a049632 commit dbb5a3e
Show file tree
Hide file tree
Showing 40 changed files with 1,738 additions and 996 deletions.
2 changes: 0 additions & 2 deletions .changeset/chilly-avocados-wink.md

This file was deleted.

5 changes: 5 additions & 0 deletions .changeset/early-suns-tan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@premieroctet/next-admin": patch
---

Add formatting system to relationship column
13 changes: 13 additions & 0 deletions .changeset/short-emus-move.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"@premieroctet/next-admin": major
---

- Always fetch `id` property from items
- Hide `id`column in list if it doesn't have `display: true` options
- Mutualize `NextAdminOptions` to reduce useless duplication of code

⚠️ **Breaking Changes**

To reduce the complexity and duplication of next-admin options, this PR contains major changes to the structure of the options.

- Changed structure for `display` and `search` properties: fields are now entered as an array
7 changes: 7 additions & 0 deletions .changeset/young-chicken-beam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@premieroctet/next-admin": patch
---

Add ability to choose format type for Date property between date-time and date in edit options
Introduce handler object to handle custom logic for a property in edit mode
Add ability to use input variant in edit mode
6 changes: 6 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,9 @@ jobs:
run: yarn start:example &
- name: Run tests
run: yarn turbo test:e2e
- uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
169 changes: 91 additions & 78 deletions apps/docs/pages/docs/api-docs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,17 @@ export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {

It takes 3 parameters:

- Your Prisma client instance, _required
- Your Prisma schema, _required
- Your Prisma client instance, _required_
- Your Prisma schema, _required_

and an _optional_ object of type [`NextAdminOptions`](#nextadminoptions) to customize your admin with the following properties:

```ts
import { NextAdminOptions } from "@premieroctet/next-admin";

const options: NextAdminOptions = {
modelOptions: {
user: {
model: {
User: {
toString: (user) => `${user.email} / ${user.name}`,
},
},
Expand All @@ -51,19 +51,19 @@ The library does not provide an authentication system. If you want to add your o

export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
const session = await getServerSession(req, res, authOptions);
const isAdmin = session?.user?.role === 'SUPERADMIN'; // your role check
const isAdmin = session?.user?.role === "SUPERADMIN"; // your role check

if (!isAdmin) {
return {
redirect: {
destination: '/',
destination: "/",
permanent: false,
},
};
}

const { nextAdminRouter } = await import(
'@premieroctet/next-admin/dist/nextAdminRouter'
"@premieroctet/next-admin/dist/nextAdminRouter"
);
return nextAdminRouter(client).run(req, res);
};
Expand All @@ -72,6 +72,7 @@ export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
## `<NextAdmin />` component

`<NextAdmin />` is a React component that contains the entire UI of Next Admin. It can take several props:

- `AdminComponentProps`, which are passed by the [router function](#nextadminrouter-function) via getServerSideProps
- `options` used to customize the UI, like field formatters for example
- `dashboard` used to customize the rendered dashboard
Expand All @@ -86,21 +87,36 @@ import Dashboard from "../../components/CustomDashboard";

export default function Admin(props: AdminComponentProps) {
/* Props are passed from the nextAdminRouter function via getServerSideProps */
return <NextAdmin {...props} dashboard={Dashboard} options={{
model: {
user: {
list: {
fields: {
role: {
formatter: (user) => {
return <strong>{user.role as string}</strong>;
return (
<NextAdmin
{...props}
dashboard={Dashboard}
options={{
model: {
User: {
list: {
display: ["id", "name", "email", "posts", "role", "birthDate"],
search: ["name", "email"],
fields: {
role: {
formatter: (role) => {
return <strong>{role.toString()}</strong>;
},
},
birthDate: {
formatter: (date) => {
return new Date(date as unknown as string)
?.toLocaleString()
.split(" ")[0];
},
},
},
}
}
}
}
}
}} />;
},
},
},
}}
/>
);
}
```

Expand All @@ -110,49 +126,58 @@ Next Admin options is the third parameter of the [router function](#nextadminrou

### `model`

`model` is an object that represents the customization options for each model in your schema.
`model` is an object that represents the customization options for each model in your schema.

It takes as **_key_** a model name of your schema as **_value_** an object to customize your it.

By default if no models are defined, they will all be displayed in the admin. If you want more control, you have to define each model individually as empty objects or with the following properties:

| Name | Description | Default value |
| ----------------- | -------------------------------------------------------------- | ------------- |
| [fields](#fields) | an object that define options for the fields of your model | undefined |
| toString | a function that is used to display your record in related list | `id` field |
| Name | Description | Default value |
| ---------- | -------------------------------------------------------------- | ------------- |
| `toString` | a function that is used to display your record in related list | `id` field |

You can customize the following for each model:

- ##### `list` property

This property determines how your data is displayed in the [List View](/docs/glossary#list-view)

You can disable a feature for any field in the `fields` that follow this form:

| Name | Description | Default value |
| ------- | ------------------------------------------------------------------------------ | ------------- |
| search | a boolean that define wether this field is searchable | true |
| display | a boolean that define wether this field should be visible in the list view | true (*) |
| Name | Description | Default value |
| ------- | ----------------------------------------------------------------------- | ------------------------- |
| search | an array of searchable fields | all fields are searchable |
| display | an array of fields that are displayed in the list | all fields are displayed |
| fields | an object containing the model fields as keys, and customization values | undefined |

> Note that the `search` property is only available for `scalar` fields.
> (*) If `display` isn't set anywhere, all fields are displayed; once you set a display property to true, all fields are hidden by default except the ones you set to `display: true.
> 💡 By default, all fields are searchable and visible, use the `list` property to return a limited subset of fields instead of all fields in the list view.
- ##### `edit` property

This property determines how your data is displayed in the [edit view](/docs/glossary#edit-view)

You can disable a property for any field in the `fields` that follow this form:
| Name | Description | Default value |
| ------- | ----------------------------------------------------------------------- | ------------------------ |
| display | an array of fields that are displayed in the form | all fields are displayed |
| fields | an object containing the model fields as keys, and customization values | undefined |

##### `fields` property

The `fields` property is available in both `list` and `edit` properties.

| Name | Description | Default value |
| ------- | ---------------------------------------------------------- | ------------- |
| display | a boolean that define whether this field should be editable | true (*) |
| validate | a function to validate on the server the form value. It should returns true or the error message | undefined |
For the `list` property, it can take the following:

> (*) If `display` is not set anywhere, all fields are displayed; once you set a display property to true, all fields are hidden by default except the ones you've set to `display: true`.
| Name | Description |
| --------- | -------------------------------------------------------------------------------- |
| formatter | a function that takes the field value as a parameter, and that return a JSX node |

> 💡 By default all fields are searchable and visible, use the `edit` property to return a limited subset of fields instead of all fields in the edit view.
For the `edit` property, it can take the following:

| Name | Description |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| validate | a function that takes the field value as a parameter, and that returns a boolean |
| format | a string defining an OpenAPI field format, overriding the one set in the generator. An extra `file` format can be used to be able to have a file input |
| handler | an object that can take the following properties |
| handler.get | a function that takes the field value as a parameter and returns a transformed value displayed in the form |
| handler.upload | an async function that is used only for formats `file` and `data-url`. It takes a buffer as parameter and must return a string. Useful to upload a file to a remote provider |

Here is an example of using `NextAdminOptions` for the following schema :

Expand Down Expand Up @@ -180,53 +205,44 @@ model User {
```tsx
// pages/api/admin/[[...nextadmin]].ts
const options: NextAdminOptions = {
basePath: "/admin",
model: {
user: {
User: {
toString: (user) => `${user.name} (${user.email})`,
list: {
display: ["id", "name", "email", "posts", "role", "birthDate"],
search: ["name", "email"],
fields: {
id: {
search: true,
display: true,
},
name: {
search: true,
display: true,
},
email: {
search: true,
display: true,
},
role: {
search: true,
display: true,
formatter: (role) => {
return <strong>{role.toString()}</strong>;
},
},
posts: {
search: true,
display: true,
birthDate: {
formatter: (date) => {
return new Date(date as unknown as string)
?.toLocaleString()
.split(" ")[0];
},
},
},
},
edit: {
display: ["id", "name", "email", "posts", "role", "birthDate"],
fields: {
id: {
display: true,
},
name: {
display: true,
},
email: {
display: true,
validate: (email) => email.includes("@") || "Invalid email",
},
role: {
display: true,
},
posts: {
display: true,
birthDate: {
format: "date",
},
profile: {
display: true,
avatar: {
format: "file",
handler: {
upload: async (file: Buffer) => {
return "https://www.gravatar.com/avatar/00000000000000000000000000000000";
},
},
},
},
},
Expand All @@ -236,6 +252,3 @@ const options: NextAdminOptions = {

const adminRouter = await nextAdminRouter(prisma, schema, options);
```

> Why do we have `@ts-expect-error` comments in the code above?
> We don't know if a relational field belongs to a model because of the Prisma type constraint. So if you want to use a relational field in the `fields` property, you need to add the `@ts-expect-error` comment to avoid TypeScript errors.
3 changes: 1 addition & 2 deletions .env → apps/example/.env
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,4 @@
POSTGRES_PRISMA_URL="postgresql://next-admin:next-admin@localhost:5432/next-admin?schema=public"
POSTGRES_URL_NON_POOLING="postgresql://next-admin:next-admin@localhost:5432/next-admin?schema=public"
BASE_URL="http://localhost:3000/admin"
BASE_DOMAIN="http://localhost:3000"
```
BASE_DOMAIN="http://localhost:3000"
File renamed without changes.
8 changes: 4 additions & 4 deletions apps/example/e2e/table.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { test } from '@playwright/test';
import { pagination, search, sort } from './utils';
import { test } from "@playwright/test";
import { pagination, search, sort } from "./utils";

test.describe.serial('table test', () => {
test.describe.serial("table test", () => {
test(`search (on user)`, async ({ page }) => {
await search(page);
});
Expand All @@ -13,4 +13,4 @@ test.describe.serial('table test', () => {
test(`pagination (on user)`, async ({ page }) => {
await pagination(page);
});
});
});
Loading

2 comments on commit dbb5a3e

@vercel
Copy link

@vercel vercel bot commented on dbb5a3e Nov 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on dbb5a3e Nov 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

next-admin – ./apps/example

next-admin-git-main-premieroctet.vercel.app
next-admin-premieroctet.vercel.app
next-admin-po.vercel.app

Please sign in to comment.