Giter Site home page Giter Site logo

premieroctet / next-admin Goto Github PK

View Code? Open in Web Editor NEW
283.0 9.0 12.0 3.4 MB

▲ Full-featured admin for Next.js and Prisma

Home Page: https://next-admin.js.org

License: MIT License

JavaScript 1.90% MDX 23.10% TypeScript 74.97% CSS 0.03%
admin nextjs prisma typescript

next-admin's Introduction

Next Admin

License Version

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.

https://next-admin.js.org

Features

  • 💅 Customizable admin dashboard
  • 💽 Database relationships management
  • 👩🏻‍💻 User management (CRUD operations)
  • 🎨 Dashboard widgets and customizable panels
  • ⚛️ Integration with Prisma ORM
  • 👔 Customizable list and form
  • ⚙️ Supports App Router and Page Router

Installation

To install the library, run the following command:

yarn add @premieroctet/next-admin prisma-json-schema-generator

Documentation

For detailed documentation, please refer to the documentation.

Usage

To use the library in your Next.js application, follow these steps:

  1. Add tailwind preset to your tailwind.config.js file - more details
  2. Add json schema generator to your Prisma schema file - more details
  3. Generate the schema with yarn run prisma generate
  4. Create a catch-all segment page page.tsx in the app/admin/[[...nextadmin]] folder - more details
  5. Create an catch-all API route route.ts in the app/api/[[...nextadmin]] folder - more details

Bonus: Customize the admin dashboard by passing the NextAdminOptions options to the router and customize the admin dashboard by passing dashboard props to NextAdmin component. (More details in the documentation)

What does it look like?

An example of next-admin options:

// app/admin/options.ts
import { NextAdminOptions } from "@premieroctet/next-admin";

export const options: NextAdminOptions = {
  title: "⚡️ My Admin Page",
  model: {
    User: {
      toString: (user) => `${user.name} (${user.email})`,
      title: "Users",
      icon: "UsersIcon",
      list: {
        search: ["name", "email"],
        filters: [
          {
            name: "is Admin",
            active: false,
            value: {
              role: {
                equals: "ADMIN",
              },
            },
          },
        ],
      },
    },
    Post: {
      toString: (post) => `${post.title}`,
    },
    Category: {
      title: "Categories",
      icon: "InboxStackIcon",
      toString: (category) => `${category.name}`,
      list: {
        display: ["name", "posts"],
      },
      edit: {
        display: ["name", "posts"],
      },
    },
  },
  pages: {
    "/custom": {
      title: "Custom page",
      icon: "AdjustmentsHorizontalIcon",
    },
  },
  externalLinks: [
    {
      label: "Website",
      url: "https://www.myblog.com",
    },
  ],
  sidebar: {
    groups: [
      {
        title: "Users",
        models: ["User"],
      },
      {
        title: "Categories",
        models: ["Category"],
      },
    ],
  },
};

📄 Documentation

For detailed documentation, please refer to the documentation.

🚀 Demonstration

You can find the library code in the next-admin repository.

Also you can find a deployed version of the library here.

Sponsors

This project is being developed by Premier Octet, a Web and mobile agency specializing in React and React Native developments.

License

This library is open source and released under the MIT License.

next-admin's People

Contributors

baptadn avatar cregourd avatar doob9p avatar facundospira avatar foyarash avatar github-actions[bot] avatar mahbd avatar ogoldberg avatar quentingrchr avatar tlenclos avatar yakashif avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

next-admin's Issues

Update/Create doesn't work when on form present Select for relation field

I'm using MySQL. After update (3.2.0) update/create doesn't work when on form present Select input for related field

It's works only if I remove "class" field from edit.display array

When I'm editing a record and didn't choose any value in Select input ("class" field), I'm getting error:

Invalid prisma.timetable.update()invocation: { where: { id: "clradekj60001uyo099jqa6vw" }, data: { start: "2024-01-08T07:00:00.000Z", end: "2024-01-08T08:30:00.000Z", class: { disconnect: true, ~~~~~~~~~~ ? create?: ClassCreateWithoutTimetableInput | ClassUncheckedCreateWithoutTimetableInput, ? connectOrCreate?: ClassCreateOrConnectWithoutTimetableInput, ? upsert?: ClassUpsertWithoutTimetableInput, ? connect?: ClassWhereUniqueInput, ? update?: ClassUpdateToOneWithWhereWithoutTimetableInput | ClassUpdateWithoutTimetableInput | ClassUncheckedUpdateWithoutTimetableInput } } } Unknown argumentdisconnect. Did you mean connect? Available options are marked with ?.

If I chose some value in Select input and try to save record, I'm getting another error:

Unhandled Runtime Error
Error: Unexpected token 'c', "cvb9xc7v8b" is not valid JSON

 ⨯ node_modules\@premieroctet\next-admin\dist\utils\server.js (292:56) @ parse
 ⨯ SyntaxError: Unexpected token 'c', "cvb9xc7v8b" is not valid JSON

options,tsx:

   model: {
      Timetable: { 
         list: {
            display: ["start", "end", "class"],
            fields: {                
               "class": {
                  formatter: (classObj) => {
                     return <strong>{classObj.name}</strong>;
                  },                  
               }             
            }
         },
         edit: {
            display: ["id", "start", "end", "class"]
         }
      },
   },

prisma

model Timetable {
    id      String   @id @default(cuid())
    start   DateTime
    end     DateTime
    note    String?
    classId String
    class   Class    @relation(fields: [classId], references: [id], onDelete: Cascade)

    @@index([classId], name: "idx_classId")
}

model Class {
    id              String        @id @default(cuid())
    name            String
    schoolAddreesId String
    schoolAddress   SchoolAddress @relation(fields: [schoolAddreesId], references: [id])
    Timetable       Timetable[]
    User            User[]

    @@index([schoolAddreesId], name: "idx_class_schoolAddressId")
}

Improve the release process

Currently the release process is a bit messy, since merging a PR on main triggers a deploy on the production documentation and example.

The idea would be the following:

  • when merging a non-release PR on main, we deploy the example and documentation in a beta environment
  • when merging a release PR on main, we deploy the example and documentation in the production environment

Columns can be formatted

Add the possibility of adding a function to fields in the options for formatting columns:

model: {
  user: {
    list: {
      fields: {
        id: {
          formatColumn: (id) => id
        }
      }
    }
  }
}

The function must be typed, using the field type

Linked comboboxes

There is pretty common pattern, when we have to select values sequentially in comboboxes, for example:
Country > State > City
or
Category > Subcategory

then save into the table last chosen item (id of a city or subcategory)

So what about to make it possible via options.ts, something like this in display section:

model: {
   user: {
         edit: {  
            display: [               
               "name",
               "email",
               ["Country", "State", "City"]          
            ],
            fields: {
               Country: {
                  formatter: (Country) => {
                     return <strong>{Country.name}</strong>;
                  },                     
              },      
               State: {
                  formatter: (State) => {
                     return <strong>{State.name}</strong>;
                  },                     
              },  
               City: {
                  formatter: (City) => {
                     return <strong>{City.name}</strong>;
                  },                     
              },      
   }
}

Include the possibility of overriding inputs

Include the possibility of overriding inputs, for example if you just want to display the date instead of a disabled input, or an image instead of a text field... etc.

 const options : NextAdminOptions = {
    modelOptions : {
      user : {
        toString : (user : User) => `${user.name} (${user.email})`,
        list : {
          fields : {
            id : {
              search : true,
            },
            name : {
              search : true,
            },
            email : {
              search : true,
            },
            role : {},
            createdAt : {
              formatter : (createdAt) => user.createdAt.toLocaleDateString(),
            }
          }
        },
        edit : {
          fields : {
            createdAt : {
              input : (createdAt) => {
                return <input type="date" value={ createdAt.toString() } />
              }
            }
          }
        }
      },
    }
  } ;

Create a plugin system for inputs

To reduce core dependencies, allow plugin system for input.
A new plugin added should provide a format and an input, all fields containing that format should be that input.

Create other packages for specific internally developed inputs

Dynamic admin base path

Currently we use a constant to determine base path, this should be either automatic if possible or an option to pass to the library.

Allow multiple prisma clients

First of all, great work on making this next admin, works really good.

I was wondering if it's possible to handle more than one prisma client? Right now we have multiple prisma clients that connect to different dbs, and would be really nice to be able to spin up different next admins with these connections within the same next project (so /prisma-1 would use 1 connection + types and /prisma-2 would use another one and so on).

Is this something feasible?
Thanks in advance

Hide models

Based on the template field system, add the ability to hide templates:

  • If no configuration is present, show all models
  • If there is a configuration about a template, show only configured templates

Note that if the user wants to display a template without making a configuration, he can enter the template as follows:
session: {}

Can't create a record if optional Select field (related) is empty

Can't create a record if optional Select field (related) on a form is empty.
I'm getting error:
Invalid prisma.school.create()invocation: { data: { name: "test name", info: "test info", slug: "test-slug", ownerUser: false ~~~~~ } } ArgumentownerUser: Invalid value provided. Expected UserCreateNestedOneWithoutSchoolInput, provided Boolean.

At the same time I can update a record when Select field is empty.

Translation

Is there a way to translate the terms used in the GUI?

One to many relations should be ignored in lists

Given the following list fields config

list: {
          fields: {
            id: {
              search: true,
              display: true,
            },
            name: {
              search: true,
              display: true,
            },
            team: {
              search: true,
              display: true,
            },
            accounts: {
              display: true,
            },
          },
        },

Current behavior

The list page is correctly working, however it is giving a Prisma error because it tries to count team, which is a One-to-Many relation, therefore not possible to apply a count on it.

Expected behavior

The One-to-Many relation should simply be ignored, eventually a warning should pop in dev environment. But it should be removed from the select statement for the count.

Formatting relationships in columns

Linked to ticket #14

Provide for relationships to be able to format the linked object, not just the id.
In One-to-Many relationships, the side carrying the relationship doesn't need to display the link (usually the id) but the object, so that it can be formatted.

The type of the formatColumn function must be overloaded to tolerate objects of type Collection.

Delete the fields carrying the relationship to display only the relationship to the object

In the example :

model Post {
  author User @relation("author", fields: [authorId], references: [id])
  authorId Int
}

Remove from the json-schema the authorId field for the formatted version of the author object, if there is no formatting function return only the id

Explore the possibility of Prisma extensions: https://www.prisma.io/docs/concepts/components/prisma-client/client-extensions/model#example. This would avoid having to modify the schema manually

Data validation

Today there is no server side validation except for Prisma exception that can get thrown when saving the data (for example when saving an incorrect type or a mandatory column).

This is not enough and the library should provide a way to add validation for a specific fields, for example if we want to validate an email.

Proposed API

import { z } from "zod";

const options: NextAdminOptions = {
  model: {
    user: {
      list: {
        fields: {
          email: {
            search: true,
            display: true,
            validate: email => z.string().email().parse(email),
          }
        }
      }
    }
  }
};

Errors should be displayed under each invalid fields.

File upload

A user should be able to use a file input, handle upload and return a blob or file to save in the form.

Html editor fields

Possibility of using some html editor library (Rich text editor) in fields configuration

format: "textarea" - looks bad

Hi.

Can you check textarea in form, something wrong with styles.

example:

      ModelName: {
         edit: {
            display: ["name", "info"],
            fields: {
               info: {
                  format: "textarea",
               }
            } 
         }
      },

image

Only using DataTable

Hi, thanks for this awesome library!

Would it be possible to have an option to only export the data table so that it can be integrated with existing UIs a bit nicer? I'm interested in using this for an application that has an admin view as well as a normal view, but this would require the normal view to look identical to the styling of this library (ie: sidebar, logo, colors), and it would be amazing if we can directly place data tables in existing UIs.

Edit page crash if no field in set for display

Given the following edit configuration:

edit: {
          fields: {
            id: {
              display: false,
            },
            email: {
              display: false,
            },
            name: {
              display: false,
            },
          },
        },

Current behavior

Accessing the ressource edit form makes the app crash with a Prisma error because the select statement is empty

Expected behavior

Accessing the ressource edit form should not crash and we should see at least one field, for example the id

Search UUID fields

Hi, first of all thank you for all the work you've put into this.

I'm having trouble with the search bar since some of the fields I have are UUIDs in a Postgres database and the contains keyword is incompatible with that native type. Is there a way to be able to search through these fields so far? I'm using the latest version 3.2.6.

Invalid `prisma.user.findMany()` invocation:

{
  select: undefined,
  where: {
    OR: [
      {
        id: {
          contains: "A",
          mode: "insensitive"
        }
      },
      {
        externalUserId: {
          contains: "A",
          mode: "insensitive"
        }
      },
      {
        personId: {
          contains: "A",
          mode: "insensitive"
        }
      }
    ]
  },
  orderBy: {},
  skip: 0,
  take: 10
}

Unknown argument `contains`. Available options are marked with ?.
image

This is what my Prisma schema looks like:

model User {
  id        String     @id @default(uuid()) @db.Uuid
  status    UserStatus @default(CREATED)
  externalUserId String     @unique @map("external_user_id")
  personId  String     @map("person_id") @db.Uuid
  createdAt DateTime   @default(now()) @map("created_at") @db.Timestamptz
  updatedAt DateTime   @updatedAt @map("updated_at") @db.Timestamptz

  person              Person                      @relation(fields: [personId], references: [id])
 
  @@map("user")
}

And this is the table definition:

CREATE TABLE "user" (
    "id" UUID NOT NULL,
    "status" "user_status_enum" NOT NULL DEFAULT 'active',
    "external_user_id" TEXT NOT NULL,
    "person_id" UUID NOT NULL,
    "created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
    "updated_at" TIMESTAMPTZ NOT NULL,

    CONSTRAINT "user_pkey" PRIMARY KEY ("id")
);

Also, if I specify which fields to search through (i.e. name) I get console logs of SQL queries containing a WHERE 1=0 clause with no results. Any idea what could be the source of this problem?

model: {
    User: {
      list: {
        display: ["id", "status", "externalUserId", "personId", "createdAt", "updatedAt"],
        search: ["status"],
      },
      edit: {
        display: ['id', 'status', 'personId', 'createdAt'],
      },
    },
}
> prisma:query SELECT "public"."user"."id", "public"."user"."status"::text, "public"."user"."external_user_id", "public"."user"."person_id", "public"."user"."created_at", "public"."user"."updated_at" FROM "public"."user" WHERE 1=0 ORDER BY "public"."user"."id" ASC LIMIT $1 OFFSET $2
> prisma:query SELECT COUNT(*) FROM (SELECT "public"."user"."id" FROM "public"."user" WHERE 1=0 OFFSET $1) AS "sub"
image

Thank you very much.

Error handling in handler.upload

Looks like there is not way to show an user that uploading of file was failed. I'm talking about this example:

          avatar: {
            format: "file",
            handler: {
              /*
               * Include your own upload handler here,
               * for example you can upload the file to an S3 bucket.
               * Make sure to return a string.
               */
              upload: async (file: Buffer) => {
                return "https://www.gravatar.com/avatar/00000000000000000000000000000000";
              },
            },

A table can't show Decimal fields

A table can't show Decimal fields (latitude, longitude) - MySQL
I'm getting warning in console:

Warning: Only plain objects can be passed to Client Components from Server Components. Decimal objects are not supported.
  {id: ..., name: ..., country: ..., latitude: Decimal}

Model:

model City {
    id        Int     @id @default(autoincrement())
    name      String
    latitude  Decimal @db.Decimal(10, 8)
    longitude Decimal @db.Decimal(10, 8)
    countryId Int
    country   Country @relation(fields: [countryId], references: [id])
}

Custom name for fields

Looks like there is no ability to customize names for columns in a table and labels of fields in a form.

Enum support

Does next-admin supports enums?

enum Sex {
man
woman
}

Bug in search

I'm using MySQL and I'm getting error when I try to search.

Invalid prisma.user.findMany()invocation: { select: { id: true, name: true, role: true, dob: true, city: true, slug: true }, where: { OR: [ { name: { contains: "pizza", mode: "insensitive" } }, { email: { contains: "pizza", mode: "insensitive" } } ] }, orderBy: {}, skip: 0, take: 10 } Unknown argumentmode. Did you mean lte? Available options are marked with ?.

Looks like the problem in: mode: "insensitive"

According prisma doc
MySQL uses case-insensitive collation by default. Therefore, filtering with Prisma Client and MySQL is case-insensitive by default.
mode: 'insensitive' property is not required and therefore not available in the generated Prisma Client API.

Responsive layout for form

How about to implement a new option to make the form layout responsive, you could use grid and add tailwind breakpoints to set number of columns in the grid, example:

 edit: {  
    gridColumns: {
       sm: 1, // 640px
       md: 2, // 768px
       lg: 3, // 1024px
       xl: 4, // 1280px
    },
    display: ["field1", "field2", "field_n"]
}

Custom pages

Add the possibility to add pages to the menu and assign components to them.

image

ID field mandatory in fields list configuration

Given the following model config

User: {
        toString: (user) => user.name!,
        list: {
          fields: {
            name: {
              search: true,
              display: true,
            },
            team: {
              search: true,
              display: true,
            },
            accounts: {
              display: true,
            },
          },
        },
      },

Expected behavior

Clicking on a row in the users list should navigate to the user edition form

Current behavior

Navigation leads to an error as is navigates to an undefined id. The URL we navigate to is http://localhost:3000/admin/User/undefined.

Adding id in the fields list fixes the issue

Changing order of fields on a form

A form for creating and updating does not keeping order of fields which were specified in edit.display property

     edit: {
        display: [
           "id",
           "field1",
           "field2",
        ],

Showing related fields in a Select input

Showing related fields in a Select input

For example we have two tables Country and City, the City table has foreign key countryId,
In toString property we could specify a related field like: (city) => ${city.name} - ${city.country.name},

The same in the formatter property.

options.tsx

....
model: {
      City: {
         title: "Cities",
         toString: (city) => `${city.name} (${city.country.name})`,
         list: {
            display: ["name", "country"],
            fields: {
               country: {
                  formatter: (country) => {
                     return <strong>{country?.name}</strong>;
                  },
               }
            }         
         },
         edit: {
            display: ["name", "latitude", "longitude", "country"]   
         }
      },
}

Delete button doesn't work

I'm using MySQL.

When I'm trying to delete record, I'm getting message in console of next.js app:
deleteAction not provided. Delete buttons will have no effect

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.