Introduction

Version Downloads/total License

We try to build Prisma db CRUD tables with ability to customize this tables with beautiful UI.

NOTE: We have already Full stack projects With NextJS and GatsbyJS

Install

yarn add @paljs/admin
or
npm i @paljs/admin

CONTENT

Settings

To be able to custom your tables you need to generate adminSettings.json file and use it as DB to get table settings from it.

Generate settings schema

1- with our cli pal g

add in your pal.js config file this settings

frontend: {
  admin: true,
},

2- with code use our UIGenerator class

Add graphql queries and mutation

To be able to update adminSettings.json file with your custom setting in a beautiful UI you have to add 1 query to pull schema in frontend and 2 mutations one for an update model settings and other for update field.

If you use Nexus all you need to be sure you added nexus-paljs plugin in your backend

If you use another way you need to add these resolvers and typeDev to your backend graphql schema

yarn add lowdb

1- create resolver file and add

import low from 'lowdb';
import FileSync from 'lowdb/adapters/FileSync';

const adapter = new FileSync<{
  [key: string]: { [key: string]: { [key: string]: any }[] }[];
}>('prisma/adminSettings.json');
const db = low(adapter);

export default {
  Query: {
    getSchema: () => {
      return db.value();
    },
  },
  Mutation: {
    updateModel: (_parent, { id, data }) => {
      return db.get('models').find({ id }).assign(data).write();
    },
    updateField: (_parent, { id, modelId, data }) => {
      return db.get('models').find({ id: modelId }).get('fields').find({ id }).assign(data).write();
    },
  },
};

2- create typeDefs file and add

import gql from 'graphql-tag';

export default gql`
  type Schema {
    enums: [Enum!]!
    models: [Model!]!
  }

  type Query {
    getSchema: Schema!
  }

  type Mutation {
    updateField(data: UpdateFieldInput, id: String!, modelId: String!): Field!
    updateModel(data: UpdateModelInput, id: String!): Model!
  }

  type Enum {
    fields: [String!]!
    name: String!
  }

  type Model {
    create: Boolean!
    delete: Boolean!
    displayFields: [String!]!
    fields: [Field!]!
    id: String!
    idField: String!
    name: String!
    update: Boolean!
  }

  type Field {
    create: Boolean!
    editor: Boolean!
    filter: Boolean!
    id: String!
    isId: Boolean!
    kind: KindEnum!
    list: Boolean!
    name: String!
    order: Int!
    read: Boolean!
    relationField: Boolean
    required: Boolean!
    sort: Boolean!
    title: String!
    type: String!
    unique: Boolean!
    update: Boolean!
  }

  input UpdateFieldInput {
    create: Boolean
    editor: Boolean
    filter: Boolean
    id: String
    isId: Boolean
    kind: KindEnum
    list: Boolean
    name: String
    order: Int
    read: Boolean
    relationField: Boolean
    required: Boolean
    sort: Boolean
    title: String
    type: String
    unique: Boolean
    update: Boolean
  }

  input UpdateModelInput {
    create: Boolean
    delete: Boolean
    displayFields: [String!]
    fields: [UpdateFieldInput!]
    idField: String
    name: String
    update: Boolean
  }
  enum KindEnum {
    enum
    object
    scalar
  }
`;

Add Settings React Component

You have Settings react component to change your tables settings.

NOTE: You must add this component under ApolloProvider component because we use @apollo/client to query and update settings.

Now you can add our component to any page like this.

import React from 'react';
import { Settings } from '@paljs/admin/Settings';import '@paljs/admin/style.css';

export default function SettingsPage() {
  // Settings component not have any props  return <Settings />;
}

When you open this in your browser will get it like this image.

settings

Models card

  • Models select menu: to change between your schema models.
  • Database Name: your original Model name like your schema.prisma.
  • Display Name: Model name to display in model table page.
  • Id field: field that have @id attribute in your model.
  • Display Fields you can select one or more to display in relation tables.
  • Actions add actions to your table create, update and delete.

Fields Accordions

order you can sort this fields in table view, update form and create form by Drag and Drop.

Every field has Accordion with this content:

  • Database Name: your original Field name like your schema.prisma.
  • Display Name: it will display into table page, update form and create form.
  • Actions
    • read show this field in table view.
    • create show this field in create record form.
    • update show this field in update record form.
    • filter add filter option to this field in table if read checked.
    • sort add sortBy option to this field in table if read checked.
    • editor use the Editor component in the update and create. You must send this component into formInputs prop because we do not have any default one for this option.
    • upload use the Upload component in the update and create. You must send this component into formInputs prop because we do not have any default one for this option.

Props

language object with the language keys

const defaultLanguage = {
  dir: 'ltr',
  header: 'Update models Tables',
  dbName: 'Database Name',
  displayName: 'Display Name',
  modelName: 'Model Name',
  idField: 'Id Field',
  displayFields: 'Display Fields',
  fieldName: 'Field Name',
  actions: 'Actions',
  create: 'create',
  update: 'update',
  delete: 'delete',
  read: 'read',
  filter: 'filter',
  sort: 'sort',
  editor: 'editor',
  upload: 'upload',
  tableView: 'Table View',
  inputType: 'Input Type',
};

Prisma table

React component to list, create, update and delete your model data.

NOTE: This component use 3 queries (findUnique, findMany, findManyCount) and 3 mutations (createOne, updateOne, deleteOne) be sure to add them in your backend by using our CLI pal generate

table

Using with NextJS

Adding style to _app.tsx file.

src/pages/_app.tsx

import '@paljs/admin/style.css';

src/Components/PrismaTable.tsx

import React from 'react';
import { useRouter } from 'next/router';
import { PrismaTable } from '@paljs/admin/PrismaTable';
const Table: React.FC<{ model: string }> = ({ model }) => {
  const router = useRouter();
  return <PrismaTable model={model} push={router.push} query={router.query} />;};

export default Table;

Using with GatsbyJS

src/components/PrismaTable.tsx

import React from 'react';
import { PrismaTable } from '@paljs/admin/PrismaTable';import { navigate } from 'gatsby';
import { useLocation } from '@reach/router';
import * as queryString from 'query-string';

import '@paljs/admin/style.css';

const Table: React.FC<{ model: string }> = ({ model }) => {
  const location = useLocation();
  const query = queryString.parse(location.search);
  return <PrismaTable model={model} push={navigate} query={query} />;
};
export default Table;

Props

Prisma Table has many props to can custom it like you want.

To customize tableColumns and formInputs components you need to look to default components and have good react skills.

interface ModelTableProps {
  // customize your table permissions and overwrite the settings it's allow you to give users different permissions
  actions?: ('create' | 'update' | 'delete')[];  // model name like in `schema.prisma` file
  model: string;  // push function to move between tables and change link
  push: (url: string) => void;  // link query object used in filters `?id=1` => {id: 1}
  query: { [key: string]: any };  // model pages path you must have all models pages in same path to can move between them.
  // default `/admin/models/`
  pagesPath?: string;  // in table pagination you can select pageSize you can pass this options here.
  // default: [10, 20, 30, 40, 50, 100]
  pageSizeOptions?: number[];  // moving between table pages. we not show you all available pages we just see current page and other 3 options.
  // default: 4
  paginationOptions?: number;  // add a checkbox for every record on the table you can use for custom cases like delete many
  onSelect?: (values: any[]) => void;  // this event call when you click cancel button in create record modal
  onCancelCreate?: (options: { model: string; setCreateModal: (state: boolean) => void }) => void;  // this event call when you click save button in create record modal
  onSaveCreate?: (options: {    model: string;    setCreateModal: (state: boolean) => void;    refetchTable: (options?: any) => void;  }) => void;  // this event call when you click cancel button in edit record page
  onCancelUpdate?: (options: { model: string }) => void;  // this event call when you click save button in edit record page
  onSaveUpdate?: (options: { model: string; refetchTable: (options?: any) => void }) => void;  // In create and update form when you click save this function will call before take every value to apollo mutation
  // Here we handle numbers list json values you can use it if you need to add any featuer
  valueHandler?: (value: string, field?: SchemaField, create?: boolean) => any;  // it's function return object with react table columns https://github.com/tannerlinsley/react-table
  // default here: https://github.com/paljs/prisma-tools/blob/master/admin/src/PrismaTable/Table/Columns.tsx
  tableColumns?: GetColumnsPartial;  // it's object with form input components for every field type we use this package https://react-hook-form.com/
  // default here: https://github.com/paljs/prisma-tools/blob/master/admin/src/PrismaTable/Form/Inputs.tsx
  formInputs?: Partial<FormInputs>;  // you can customize the actions buttons
  actionButtons?: {
    Add?: React.FC;
    Update?: React.FC<{ id: any }>;
    Delete?: React.FC<{ id: any }>;
  };
  lang: typeof Language;
  dir: 'rtl' | 'ltr';
}

type FormInputs = Record<'Default' | 'Editor' | 'Enum' | 'Object' | 'Date' | 'Boolean', React.FC<InputProps>>;

interface InputProps {
  field: SchemaField;
  value: any;
  data: any;
  error: any;
  // import { UseFormReturn } from 'react-hook-form';
  register: UseFormReturn['register'];
  setValue: UseFormReturn['setValue'];
  getValues: UseFormReturn['getValues'];
  watch: UseFormReturn['watch'];
  disabled: boolean;
}

// import {Column,UseFiltersColumnOptions,UseSortByColumnOptions} from 'react-table';
type Columns = Record<
  'boolean' | 'number' | 'enum' | 'DateTime' | 'object' | 'string' | 'list',
  Column & UseFiltersColumnOptions<any> & UseSortByColumnOptions<any>
>;

export type GetColumnsPartial = (field: SchemaField, model?: SchemaModel) => Partial<Columns>;

The default language object

const Language = {
  yes: 'yes',
  no: 'no',
  all: 'All',
  startDate: 'Start Date',
  endDate: 'End Date',
  min: 'Min',
  max: 'Max',
  range: 'Range',
  equals: 'Equals',
  deleteConfirm: 'Are you sure you want to delete this record ?',
  select: 'Select',
  actions: 'Actions',
  relation: 'Relation',
  viewAll: 'View All',
  viewRelated: 'View Related',
  connected: 'Connected',
  connect: 'Connect',
  disConnect: 'DisConnect',
  editRow: 'Edit Row',
  viewRow: 'View Row',
  deleteRow: 'Delete Row',
  showing: 'Showing',
  of: 'of',
  results: 'results',
  goToFirstPage: 'Go to first page',
  goToLastPage: 'Go to last page',
  goPageNumber: 'Go Page Number',
  setPageSize: 'Set page size ',
  filter: 'Filter',
  save: 'Save',
  cancel: 'Cancel',
  close: 'close',
  create: 'create',
  update: 'update',
  view: 'view',
  isRequired: ' is required',
  show: 'SHOW',
  clear: 'clear',
  clearAll: 'Clear All',
};

Generate pages

Now we need to generate a page for every model with our prisma table here src/components/PrismaTable.tsx.

You can add them manulay or use our cli pal generate

Add to your pal.js config file

frontend: {
  admin: boolean or object,
},
  • Add true To generate pages with default settings
  • you can customize it by add object with this proparty AdminPagesOptions

How to add and update list values?

As we know we can use list values with prisma but how we work on this fields in admin forms

Example


model SchemaModel {
  id            Int           @id @default(autoincrement())
  string        String[]
  integer       Int[]
  boolean       Boolean[]
  float         Float[]
  json          Json[]
  enums         FieldKind[]
}

enum FieldKind {
  object
  enum
  scalar
}

This fields inputs will ba as text type

  • Int[] => 1,2,3,4 converted to [1,2,3,4]
  • Flout[] => 1.2,2.3,3.5 converted to [1.2,2.3,3.5 ]
  • String[] => first,second converted to ['first','second']
  • Boolean[] => true,false converted to [true,false]
  • enum[] => object,enum converted to ['object','enum']
  • Json[] => [{"first": 1},{"second": 2}] we will pass value throught JSON.parse()