Ngày nay, không có hệ thống web-based hoàn chỉnh nào mà thiếu một giao diện quản trị. Giao diện này cung cấp một nơi cho người dùng có đặc quyền để quản lý toàn bộ hệ thống và thực hiện các hoạt động CRUD (Tạo, Đọc, Cập nhật, Xóa). Rõ ràng rằng việc có một giao diện quản trị sẽ giúp tăng cường khả năng quản lý, sử dụng của hệ thống, tuy nhiên việc tạo ra một trang web có thể mở rộng và quản lý quyền hạn, cập nhật thời gian thực và truy cập nhanh đến thông tin có thể là một thử thách lớn. Mà thường các developer chúng ta hay quan niệm rằng: admin quản lý thôi, không quan trọng.Tuy nhiên, theo mình thì đối với hệ thống nào, việc tạo ra một giao diện dễ hiểu và thân thiện với người dùng là quan trọng để đảm bảo trải nghiệm người dùng quản trị tích cực và giảm thời gian thực hiện các hoạt động quản trị.

Và hôm nay, mình sẽ giới thiệu với các bạn một công cụ phổ biến được sử dụng bởi các developer React và JavaScript. Đó là thư viện react-admin, do Marmelab cung cấp. Công cụ này cho phép bạn tạo ra các thành phần CRUD với rất rất ít code. Ngoài ra, bạn cũng có thể sử dụng thư viện MUI để hỗ trợ tùy chỉnh giao diện người dùng của bạn. Trong bài viết này, chúng ta sẽ xây dựng một dashboard quản trị bằng React JS.

React-admin là gì?


React-admin là một framework mã nguồn mở được sử dụng để xây dựng giao diện quản trị bằng cách sử dụng các API, bao gồm REST, GraphQL, hoặc các API tùy chỉnh sử dụng React. Chúng ta cũng không cần lo lắng về định dạng kiểu vì react-admin cung cấp một collection các thành phần giao diện người dùng đã được xây sẵn dựa trên Material-UI.

Lợi ích của việc sử dụng react-admin

React-admin có thể được thiết kế để phù hợp với các nhu cầu cụ thể của ứng dụng web, mang lại tính linh hoạt và kiểm soát đồng thời cung cấp một trải nghiệm người dùng chất lượng cao. React-admin đi kèm với những tính năng phổ biến của bảng quản trị, bao gồm các tính năng sau đây:

Quản lý dữ liệu: React-admin cung cấp một cách đơn giản để xử lý quản lý dữ liệu. Bạn có thể thực hiện các hoạt động CRUD, lọc, sắp xếp và phân trang với ít code hơn.

Giao diện người dùng có thể tùy chỉnh: React-admin đi kèm với các thành phần giao diện người dùng có thể tùy chỉnh để phù hợp với nhu cầu của bạn bằng cách sử dụng Material-UI hoặc CSS.

Phát triển hiệu quả: React-admin cung cấp các "guesser" giúp tạo ra một thành phần có thể tái sử dụng với các thuộc tính và giá trị từ API của bạn.

Xác thực và phân quyền: React-admin cho phép nhà phát triển dễ dàng quản lý quyền truy cập của người dùng vào bảng quản trị mà không cần tùy chỉnh nhiều.

Internationalization (i18n): React-admin tích hợp sẵn hỗ trợ quốc tế hóa, giúp tạo ra một bảng quản trị hỗ trợ nhiều ngôn ngữ một cách dễ dàng.

Hệ thống react-admin UI

Trước khi chúng ta bắt đầu, hãy xem qua một số phần tử giao diện người dùng thông thường của react-admin và công dụng của chúng. Những phần tử này được xây dựng sẵn và được cung cấp dưới dạng các component React. Các component list được sử dụng để truy xuất và hiển thị một danh sách các bản ghi (dữ liệu). Những component này cũng đi kèm với các user control để filter, sắp xếp và phân trang. Chúng bao gồm <ListGuesser>, <List>, <DataGrid>, <FilterList>, <Pagination>, và nhiều component khác.

React-admin cũng đi kèm với các component hiển thị. Những component này được sử dụng để truy xuất và hiển thị chi tiết của một bản ghi duy nhất. Component <Show>, ví dụ, chịu trách nhiệm truy xuất bản ghi từ dataProvider. hiển thị bố cục trang với tiêu đề trang mặc định hoặc tiêu đề tùy chỉnh, v.v.

Một số component khác được sử dụng trong việc hiển thị một bản ghi cùng với <Show> bao gồm <SimpleShowLayout>, <TabbedShowLayout>, các component field <TextField>, <DateField>, v.v. Cuối cùng, chúng ta có các component tạo và chỉnh sửa. Những component này được sử dụng để tạo và chỉnh sửa các bản ghi. Chúng bao gồm <Create>, <Edit>, <SimpleForm>, <Form>, <EditGuesser>.

Những component này được sử dụng với các phần tử nhập như <TextInput>, <NumberInput>, và nhiều phần tử khác. Các component <Create><Edit> tạo trang để tạo và chỉnh sửa một bản ghi, tương ứng, trong khi component <SimpleForm> tạo biểu mẫu để tạo hoặc chỉnh sửa một bản ghi.

Thực hành thôi

Prerequisites

Trước khi bắt đầu code, hãy đảm bảo các vấn đề sau đây để bạn dễ follow hơn nhé:

  • Hiểu biết về JavaScript và thư viện React.
  • Node hoặc Yarn đã được cài đặt trên máy của bạn.

Khởi tạo project

Set up server

Lưu ý: Vì mình muốn đi sâu vào phần react-admin, nên phần server này mình tạo ta chỉ để mục đích set up 1 fake API để chúng ta có thể thao tác trong quá trình code với react-admin.

Hãy bắt đầu bằng cách tạo một thư mục mới có tên là new-react-admin và cài đặt các gói package.json, concurrently, và json-server trong một thư mục server mới, như được hiển thị dưới đây:

mkdir new-react-admin
cd new-react-admin/
mkdir server
cd server/
npm init -y
npm install json-server
npm install concurrently

Thay vì cài đặt json-server global, chúng ta sẽ tạo một npm script cho nó. Mở tệp package.json và thay đổi giá trị mặc định của  scripts thành như sau:

// /new-react-admin/server/package.json
# - "test": "echo \"Error: no test specified \" && exit 1"

+ "server": "json-server --watch db.json --port 5000",
+ "client": "npm start --prefix ../admin-demo",
+ "dev": "concurrently \"npm run server\" \"npm run client\""

# "admin-demo" will be the name of our react app

Trong đoạn code trên, chúng ta muốn chạy json-server và theo dõi một tệp có tên là db.json. Tệp db.json chứa dữ liệu được tạo ra bởi  fake API REST mà chúng ta sẽ sử dụng. Theo mặc định, nó chạy trên cổng 3000, điều này sẽ xung đột với quá trình chạy ứng dụng React của chúng ta. Thay vào đó, chúng ta sẽ đặt nó ở cổng 5000.

Concurrently framework chúng ta sử dụng ở đây cho phép API và ứng dụng React chạy đồng thời thay vì chạy trong các terminal riêng biệt. Như đã hiển thị ở trên, script client được gán để bắt đầu ứng dụng React và script dev chạy cả hai script server và client đồng thời.

Tiếp theo, chúng ta cần điền dữ liệu vào tệp db.json. Dưới đây là một mẫu dữ liệu mà chúng ta sẽ sử dụng:

// /new-react-admin/server/db.json

{
  "users": [
    {
      "id": 1,
      "name": "Nguyen Van A",
      "username": "nguyenvana",
      "email": "nguyenvana@example.com",
      "phone": "123-456-7890",
      "company": "ABC Company"
    },
    {
      "id": 2,
      "name": "Tran Thi B",
      "username": "tranthib",
      "email": "tranthib@example.com",
      "phone": "987-654-3210",
      "company": "XYZ Corporation"
    },
    {
      "id": 3,
      "name": "Le Van C",
      "username": "levanc",
      "email": "levanc@example.com",
      "phone": "555-123-4567",
      "company": "123 Industries"
    },
    {
      "id": 4,
      "name": "Pham Van D",
      "username": "phamvand",
      "email": "phamvand@example.com",
      "phone": "777-888-9999",
      "company": "DEF Limited"
    },
    {
      "id": 5,
      "name": "Vo Thi E",
      "username": "vothie",
      "email": "vothie@example.com",
      "phone": "333-999-7777",
      "company": "LMN Enterprises"
    }
  ],
  "posts": [
    {
      "id": 1,
      "title": "Post 1",
      "body": "This is the body of post 1. Lorem ipsum dolor sit amet.",
      "publishedAt": "2023-01-01T10:00:00Z",
      "userId": 1
    },
    {
      "id": 2,
      "title": "Post 2",
      "body": "This is the body of post 2. Lorem ipsum dolor sit amet.",
      "publishedAt": "2023-02-15T14:30:00Z",
      "userId": 2
    },
    {
      "id": 3,
      "title": "Post 3",
      "body": "This is the body of post 3. Lorem ipsum dolor sit amet.",
      "publishedAt": "2023-03-20T08:45:00Z",
      "userId": 3
    }
  ]
}

Để hiển thị dữ liệu từ tệp db.json, máy chủ API cần có giá trị phạm vi nội dung để tránh gây lỗi. Với sự giúp đỡ của middleware, chúng ta sẽ tạo một tệp có tên là range.js có một hàm để đặt số lượng nội dung để hiển thị:

// /new-react-admin/server/range.js
module.exports = (req, res, next) => {
    res.header('Content-Range', 'posts 0-20/20');
    next();
}

Và cuối cùng, để hàm middleware chạy, chúng ta phải thêm --middlewares ./range.js vào script server trong tệp package.json.

Như vậy là việc set up server của chúng ta đã xong. :D

Set up client

Bây giờ, chúng ta có thể tạo một dự án React trong một thư mục mới có tên là "admin-demo" và cài đặt react-admin trong thư mục này:

cd ..
npx create-react-app admin-demo
cd /admin-demo
npm install react-admin

Chức năng CRUD là cần thiết cho một trang quản trị. Chúng ta sẽ sử dụng một data provider để thể hiện cách react-admin thực hiện điều này. Simple REST là một data provider phù hợp với các REST APIs sử dụng các tham số GET đơn giản tồn tại để lọc và sắp xếp. Đầu tiên, chúng ta cần cài đặt Simple REST vào ứng dụng react-admin của chúng ta với command sau:

// /new-react-admin/admin-demo
npm install ra-data-simple-rest

Sau khi khởi tạo xong project, chúng ta sẽ add proxy vào package.json. proxy này sẽ trỏ đến server script như dưới đây:

{
  "name": "admin-demo",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^5.17.0",
    "@testing-library/react": "^13.4.0",
    "@testing-library/user-event": "^13.5.0",
    "ra-data-simple-rest": "^4.16.0",
    "react": "^18.2.0",
    "react-admin": "^4.16.0",
    "react-dom": "^18.2.0",
    "react-scripts": "5.0.1",
    "web-vitals": "^2.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "proxy": "http://localhost:5000" // <- add here
}

Hãy thử truy xuất dữ liệu với db.json. Để làm điều này, react-admin sử dụng <Admin>, thành phần gốc của nó, để cung cấp sự trao đổi dữ liệu giữa các API và ứng dụng. Thay thế cú pháp mặc định trong src/App.js bằng đoạn code sau:

import React from 'react'
import { Admin} from 'react-admin'
import restProvider from 'ra-data-simple-rest'

const dataProvider = restProvider('http://localhost:3000');

function App() {
  return (
      <Admin dataProvider={dataProvider} />
    );
  }
export default App;

Bây giờ, thay đổi thư mục về thư mục chính bằng lệnh cd .., và chạy npm run dev tại thời điểm này, ứng dụng  của bạn sẽ được hiển thị trong trình duyệt:

Vậy là chúng ta cũng đã set up xong đối với phần client.

Sử dụng component Guesser để tạo các component CRUD nhanh chóng

Với mình, đây là 1 tính năng rất tuyệt vời mà React-admin cung cấp. Trong quá trình phát triển, react-admin có thể tạo giao diện quản trị thông qua các guessers. Các guesser ở đây nhận dữ liệu từ API, xác định loại dữ liệu là gì và quyết định kiểu định dạng nào dữ liệu nên hiển thị. Hãy thử hiển thị một danh sách người dùng bằng cách áp dụng các guesser bằng cách implement ListGuesser như sau:

import { Admin, Resource,ListGuesser } from 'react-admin'
import restProvider from 'ra-data-simple-rest'

const dataProvider = restProvider('http://localhost:3000');

function App() {
    return (
      <Admin dataProvider={dataProvider}>
        <Resource name="users" list={ListGuesser} />
      </Admin>
    );
  }
export default App;

Trong đoạn code trên, phần tử <resource> có trách nhiệm ánh xạ thuộc tính name thành một endpoint trong API. Ở đây, <resource> thêm giá trị users vào API của chúng ta (thành http://localhost:3000/users) và truy xuất dữ liệu người dùng từ API. Thuộc tính list sử dụng thành phần <ListGuesser> để hiển thị dữ liệu này dưới dạng danh sách người dùng. Bây giờ, reload lại browser, chúng ta sẽ có được thành quả:

Rất tiện lợi phải không, nhưng tin mình đi, đây chưa phải là phần hay nhất. ListGuesser có vẻ tiện lợi bằng cách đã hiển thị ra thứ mà chúng ta muốn, tuy nhiên nó không nên được sử dụng trên production. Do đó, nó phải được thay thế bằng một thành phần tùy chỉnh. Một tính năng tuyệt vời của các guesser là hiển thị mã nguồn dữ liệu được lấy từ API trong console của trình duyệt. Và đây chính là thứ hay ho nhất mình đã nói:

Như vậy, nó cho chúng ta biết danh sách người dùng của chúng ta nên được tạo ra như thế nào. Hãy sao chép dữ liệu này vào ứng dụng của chúng ta. Trong thư mục src của dự án, tạo một thư mục components, và trong đó, tạo một tệp và đặt tên là UserList.js:

import {
  List,
  Datagrid,
  TextField,
  EmailField,
  DeleteButton,
} from "react-admin";

export const UserList = () => {
  return (
    <List>
      <Datagrid rowClick="edit">
        <TextField source="id" />
        <TextField source="name" />
        <TextField source="username" />
        <EmailField source="email" />
        <TextField source="phone" />
        <TextField source="company" />
        <DeleteButton />
      </Datagrid>
    </List>
  );
};


Trong đoạn code trên, có một số thay đổi đã được thực hiện. Trước tiên, chúng ta đã sử dụng phần tử <EmailField> để làm cho các liên kết trong cột email của chúng ta có thể được nhấp để mở. Sau đó, chúng ta đã thêm một thành phần <DeleteButton> để dễ dàng xóa bất kỳ bản ghi (dữ liệu) nào. Tất nhiên dựa trên những gì mà ListGuesser đã gợi ý, chúng ta có thể tùy biến thế nào cũng được theo ý muốn của mình.

Giờ chúng ta sẽ thay thế ListGuesser với component mà chúng ta vừa mới tạo:

/src/App.js
import { Admin, Resource } from "react-admin";
import { UserList } from "./components/users/UserList";
import restProvider from 'ra-data-simple-rest';

const dataProvider = restProvider('http://localhost:3000');
function App() {
  return (
      <Admin dataProvider={dataProvider}>
        <Resource name="users" list={UserList} />
      </Admin>
    );
  }
export default App;

Chức năng Edit

Các trang quản trị cũng phải có khả năng chỉnh sửa, và tạo dữ liệu. Tiếp theo chúng ta sẽ làm các chức năng này. Nếu bạn nhớ, chúng ta đã thêm prop rowClick ='edit'vào thành phần DataGrid trong UserList. Điều này có nghĩa là khi một dòng được nhấp, nó sẽ chuyển bạn đến một trang chỉnh sửa nơi bạn có thể cập nhật bản ghi đó. Tương tự với ListGuesser, chúng ta có EditGuesser. Trong App.js, nhập EditGuesser từ react-admin với đoạn code sau:

import { Admin, Resource, EditGuesser } from "react-admin";
import { UserList } from "./components/users/UserList";
import restProvider from "ra-data-simple-rest";

const dataProvider = restProvider("http://localhost:3000");
function App() {
  return (
    <Admin dataProvider={dataProvider}>
      <Resource name="users" list={UserList} edit={EditGuesser} />
    </Admin>
  );
}
export default App;

Giờ chúng ta có thể sửa 1 user thành công:

Update record 
Kết quả


Một điều quan trọng cần lưu ý là Simple REST, data provider mà chúng ta đang sử dụng cho fake API có các chức năng chỉnh sửa và tạo mới. Ở đây, react-admin hiển thị các thay đổi được thực hiện đồng thời gửi một truy vấn cập nhật đến dữ liệu trong tệp db.json. Tương tự như việc liệt kê người dùng, việc kiểm tra console của chúng ta sẽ cho chúng ta một ý tưởng về cách nhập markup. Dưới đây là component UserEdit mình tạo sau khi tham khảo EditGuesser:

// src/components/users/UserEdit.js

import { Edit, SimpleForm, TextInput } from "react-admin";

export const UserEdit = () => {
  return (
    <Edit>
      <SimpleForm>
        <TextInput disabled source="id" />
        <TextInput source="name" />
        <TextInput source="username" />
        <TextInput source="email" />
        <TextInput source="phone" />
        <TextInput source="company" />
      </SimpleForm>
    </Edit>
  );
};

Thuộc tính disabled trong phần tử TextInput ngăn chặn việc chỉnh sửa các thuộc tính nhạy cảm. Chúng ta import component UserEdit này vào App.js giống với UserList để thay thế EditGuesser.

Chức năng Create

Quy trình tạo một người dùng mới gần như giống như quy trình chỉnh sửa. Tuy nhiên, chúng ta phải tạo một data mới trong tệp db.json. Tham khảo file UserEdit, tạo UserCreate:

// src/components/users/UserCreate.js

import { Create, SimpleForm, TextInput, NumberInput } from "react-admin";

export const UserCreate = () => {
  return (
    <Create title="Create User">
      <SimpleForm>
        <NumberInput source="id" />
        <TextInput source="name" />
        <TextInput source="username" />
        <TextInput source="email" />
        <TextInput source="phone" />
        <TextInput source="company" />
      </SimpleForm>
    </Create>
  );
};

Update App.js:

// src/App.js

import { Admin, Resource } from "react-admin";
import { UserList } from "./components/users/UserList";
import restProvider from "ra-data-simple-rest";
import { UserCreate } from "./components/users/UserCreate";
import { UserEdit } from "./components/users/UserEdit";

const dataProvider = restProvider("http://localhost:3000");
function App() {
  return (
    <Admin dataProvider={dataProvider}>
      <Resource
        name="users"
        list={UserList}
        edit={UserEdit}
        create={UserCreate}
      />
    </Admin>
  );
}
export default App;

Như vậy là chúng ta có thể tạo được 1 user mới, rất đơn giản phải không nào:

Create User
Kết quả

Chức năng Delete

Bên phải của mỗi record, chúng ta đã có Button Delete, react-admin tự động sẽ gọi phương thức delete của API để xóa record đó. Chẳng hạn, khi mình bấm để xóa record id = 6 mình vừa gọi, nó sẽ xóa thành công:

Như vậy là chúng ta đã hoàn thành xong chức năng CRUD của users rồi, rất đơn giản phải không nào.

Tạo CRUD cho Posts

Tương tự như chúng ta đã tạo UserList, UserEdit và UserCreate với sự giúp đỡ của các guesser, chúng ta sẽ thực hiện điều tương tự để tạo PostList, PostEdit và PostCreate. Tuy nhiên, chúng ta sẽ không đi qua từng chi tiết như chúng ta đã làm cho người dùng. Do đó, mình sẽ update lại code sau khi mình đã tham khảo các component do guesser gợi ý:

// src/App.js

import { Admin, Resource } from "react-admin";
import { UserList } from "./components/users/UserList";
import restProvider from "ra-data-simple-rest";
import { UserCreate } from "./components/users/UserCreate";
import { UserEdit } from "./components/users/UserEdit";
import { PostList } from "./components/posts/PostList";
import { PostEdit } from "./components/posts/PostEdit";
import { PostCreate } from "./components/posts/PostCreate";

const dataProvider = restProvider("http://localhost:3000");
function App() {
  return (
    <Admin dataProvider={dataProvider}>
      <Resource
        name="users"
        list={UserList}
        edit={UserEdit}
        create={UserCreate}
      />
      <Resource
        name="posts"
        list={PostList}
        edit={PostEdit}
        create={PostCreate}
      />
    </Admin>
  );
}
export default App;
// src/components/posts/PostList.js

import { Datagrid, DateField, List, TextField } from "react-admin";

export const PostList = () => {
  return (
    <List>
      <Datagrid rowClick="edit">
        <TextField source="id" />
        <TextField source="title" />
        <DateField source="publishedAt" />
        <TextField source="userId" label="Author" />
      </Datagrid>
    </List>
  );
};
// src/components/posts/PostEdit.js

import {
  DateInput,
  Edit,
  NumberInput,
  SimpleForm,
  TextInput,
} from "react-admin";

export const PostEdit = () => {
  return (
    <Edit>
      <SimpleForm>
        <NumberInput disabled source="id" />
        <TextInput source="title" />
        <TextInput multiline source="body" />
        <DateInput label="Published" source="publishedAt" />
      </SimpleForm>
    </Edit>
  );
};
// src/components/posts/PostCreate.js

import {
  Create,
  DateInput,
  NumberInput,
  SimpleForm,
  TextInput,
} from "react-admin";

export const PostCreate = () => {
  return (
    <Create title="Create a Post">
      <SimpleForm>
        <NumberInput source="id" />
        <TextInput source="title" />
        <TextInput multiline source="body" />
        <DateInput label="Published" source="publishedAt" />
      </SimpleForm>
    </Create>
  );
};

Và đây là thành quả của chúng ta:

Authentication

Mỗi trang quản trị đều cần một quy trình xác thực. Nó có thể đơn giản hoặc phức tạp hơn một chút, chẳng hạn như JSON Web Tokens (JWT) hoặc OAuth. Mặc dù, theo mặc định, ứng dụng react-admin không cần xác thực để hoạt động, nhưng vào một số trường hợp vẫn cần thiết để tích hợp xác thực vào các trang quản trị.

React-admin cho phép chúng ta linh hoạt trong cách triển khai xác thực. Simple REST không có mô hình xác thực, vì vậy chúng ta sẽ tạo một quy trình xác thực giả mạo chấp nhận bất kỳ giá trị nào làm tên người dùng và mật khẩu, và lưu trữ những giá trị này trong localStorage. Trong thư mục src của chúng ta, tạo một tệp có tên là authProvider:

// src/auth/authProvider.js

import { AUTH_LOGIN, AUTH_LOGOUT, AUTH_ERROR, AUTH_CHECK } from "react-admin";

export const authProvider = (type, params) => {
  // when a user tries to log in
  if (type === AUTH_LOGIN) {
    const { username } = params;
    localStorage.setItem("username", username);
    return Promise.resolve();
  }
  // when a user tries to logout
  if (type === AUTH_LOGOUT) {
    localStorage.removeItem("username");
    return Promise.resolve();
  }
  // when the API throws an error
  if (type === AUTH_ERROR) {
    const { status } = params;
    if (status === 401 || status === 403) {
      localStorage.removeItem("username");
      return Promise.reject();
    }
    return Promise.resolve();
  }
  // when a user navigates to a new location
  if (type === AUTH_CHECK) {
    return localStorage.getItem("username")
      ? Promise.resolve()
      : Promise.reject();
  }
  return Promise.reject("Unknown Method");
};

Sau đó, vào file App.js và truyền đối tượng authProvider vào thuộc tính <Admin> component:

// src/App.js

import { Admin, Resource } from "react-admin";
import { UserList } from "./components/users/UserList";
import restProvider from "ra-data-simple-rest";
import { UserCreate } from "./components/users/UserCreate";
import { UserEdit } from "./components/users/UserEdit";
import { PostList } from "./components/posts/PostList";
import { PostEdit } from "./components/posts/PostEdit";
import { PostCreate } from "./components/posts/PostCreate";
import { authProvider } from "./auth/authProvider";
import PeopleIcon from "@mui/icons-material/People";
import ArticleIcon from "@mui/icons-material/Article";

const dataProvider = restProvider("http://localhost:3000");
function App() {
  return (
    <Admin dataProvider={dataProvider} authProvider={authProvider}>
      <Resource
        name="users"
        list={UserList}
        edit={UserEdit}
        create={UserCreate}
        recordRepresentation={(user) => user.username}
        icon={PeopleIcon}
      />
      <Resource
        name="posts"
        list={PostList}
        edit={PostEdit}
        create={PostCreate}
        icon={ArticleIcon}
      />
    </Admin>
  );
}
export default App;

Bây giờ, khi chúng ta khởi động lại ứng dụng, chúng ta sẽ thấy một trang đăng nhập:

Đừng lo, chỉ cần nhập bất kì giá trị nào chúng ta cũng vượt qua được việc xác thực này :D

Referencing records

Cập nhật thành phần PostList của bạn như sau:

// src/components/posts/PostList.js

import {
  Datagrid,
  DateField,
  List,
  ReferenceField,
  TextField,
} from "react-admin";

export const PostList = () => {
  return (
    <List>
      <Datagrid rowClick="edit">
        <TextField source="id" />
        <TextField source="title" />
        <DateField source="publishedAt" />
        <ReferenceField source="userId" label="Author" reference="users" />
      </Datagrid>
    </List>
  );
};

Ở đây, chúng ta giới thiệu một thành phần ReferenceField và thiết lập thuộc tính reference trên users. Giá trị của reference nên phù hợp với tên mà chúng ta đã cung cấp cho Resource của users trong thành phần Admin. Bây giờ, những gì chúng ta sẽ làm là khiến cho mỗi danh sách trong cột Author có thể nhấp để mở và khi nhấp, nó sẽ chuyển bạn đến trang users.

Tuy nhiên, hiện tại nó vẫn đang hiển thị ID của người viết thay vì tên người dùng của họ. Để thay đổi điều đó, hãy di chuyển đến tệp App.js và cập nhật Resource của users thành như sau:

...
<Resource
  name="users"
  list={UserList}
  create={UserCreate}
  edit={UserEdit}
  recordRepresentation={(user) => user.username}
/>
...

Mặc định, react-admin biểu thị mỗi bản ghi bằng ID của nó, nhưng bây giờ chúng ta đã thay đổi biểu thị này thành tên người dùng. Kết quả như sau:

Khi cập nhật hoặc tạo bài viết mới, chúng ta có thể sử dụng ReferenceInput để giúp các quản trị viên chỉ định author dễ dàng hơn. Hãy cập nhật PostCreate và PostEdit của bạn như sau:

// src/components/posts/PostCreate.js

import {
  AutocompleteInput,
  Create,
  DateInput,
  NumberInput,
  ReferenceInput,
  SimpleForm,
  TextInput,
} from "react-admin";

export const PostCreate = () => {
  return (
    <Create title="Create a Post">
      <SimpleForm>
        <NumberInput source="id" />
        <ReferenceInput source="userId" reference="users">
          <AutocompleteInput label="Author" />
        </ReferenceInput>
        <TextInput source="title" />
        <TextInput multiline source="body" />
        <DateInput label="Published" source="publishedAt" />
      </SimpleForm>
    </Create>
  );
};
// src/components/posts/PostEdit.js

import {
  AutocompleteInput,
  DateInput,
  Edit,
  NumberInput,
  ReferenceInput,
  SimpleForm,
  TextInput,
} from "react-admin";

export const PostEdit = () => {
  return (
    <Edit>
      <SimpleForm>
        <NumberInput disabled source="id" />
        <ReferenceInput source="userId" reference="users">
          <AutocompleteInput label="Author" />
        </ReferenceInput>
        <TextInput source="title" />
        <TextInput multiline source="body" />
        <DateInput label="Published" source="publishedAt" />
      </SimpleForm>
    </Edit>
  );
};

Kết quả:

Field Author sẽ trở thành 1 dropdown để chúng ta select với giá trị là những bản ghi trên trang users. Rất tuyệt đúng không.

Theming

Mỗi thành phần của react-admin có thể được tạo kiểu với prop sx từ MUI System. Nó sử dụng CSS-in-JS, giống như prop style của React. Chúng ta sẽ sử dụng nó để tạo kiểu cho thành phần DataGrid trong UserList, như dưới đây:

import {
  List,
  Datagrid,
  TextField,
  EmailField,
  DeleteButton,
} from "react-admin";

export const UserList = () => {
  return (
    <List>
      <Datagrid
        rowClick="edit"
        sx={{
          ".RaDatagrid-rowEven": {
            backgroundColor: "lavender",
          },
          ".RaDatagrid-headerCell": {
            backgroundColor: "MistyRose",
          },
        }}
      >
        <TextField source="id" />
        <TextField source="name" />
        <TextField source="username" />
        <EmailField source="email" />
        <TextField source="phone" />
        <TextField source="company" />
        <DeleteButton />
      </Datagrid>
    </List>
  );
};

class .RaDatagrid-rowEven là một class-selector chọn tất cả các dòng chẵn của bảng, trong khi .RaDatagrid-headerCell chọn phần header của bảng.


Nếu chúng ta muốn áp dụng cùng một kiểu cho nhiều thành phần DataGrid trong ứng dụng của chúng ta, chúng ta sử dụng hàm styled từ MUI System để tạo một thành phần DataGrid được tạo kiểu có thể tái sử dụng:

import { styled } from "@mui/system";
import { Datagrid } from "react-admin";
export const CustomDataGrid = styled(Datagrid)({
  ".RaDatagrid-rowEven": {
    backgroundColor: "lavender",
  },
  ".RaDatagrid-headerCell": {
    backgroundColor: "MistyRose",
  },
});

Bây giờ,  chúng ta sẽ thêm biểu tượng đặc biệt cho cả hai menu users và posts trên thanh bên trái. Thành phần Resource có một prop icon mà bạn có thể sử dụng như sau:

// src/App.js

...
import PeopleIcon from "@mui/icons-material/People";
import ArticleIcon from "@mui/icons-material/Article";

const dataProvider = restProvider("http://localhost:3000");
function App() {
  return (
    <Admin dataProvider={dataProvider} authProvider={authProvider}>
      <Resource
        name="users"
       	...
        icon={PeopleIcon}
      />
      <Resource
        name="posts"
        ...
        icon={ArticleIcon}
      />
    </Admin>
  );
}
export default App;

Cuối cùng, hãy tạo một tùy chọn để chuyển đổi giữa chủ đề tối và chủ đề sáng và ngược lại. Để làm điều này, chúng ta sẽ phải tạo một bố cục mới sẽ chứa một nút chuyển trạng thái trên phần đầu của tất cả các trang. Vì vậy, hãy tạo một thư mục layouts trong thư mục src. Trong thư mục đó, tạo một tệp AppLayout.js và paste đoạn code sau vào đó:

// src/layouts/AppLayout.js
import {
  defaultTheme,
  Layout,
  AppBar,
  ToggleThemeButton,
  TitlePortal,
} from "react-admin";
import { createTheme } from "@mui/material";

const darkTheme = createTheme({
  palette: { mode: "dark" },
});

const CustomAppBar = () => (
  <AppBar>
    <TitlePortal />
    <ToggleThemeButton lightTheme={defaultTheme} darkTheme={darkTheme} />
  </AppBar>
);
export const AppLayout = (props) => <Layout {...props} appBar={CustomAppBar} />;

Bây giờ, hãy di chuyển đến App.js và chuyển đối tượng AppLayout mới vào prop layout của Admin như sau:

<Admin
  dataProvider={dataProvider}
  authProvider={authProvider}
  layout={AppLayout}
>
...
</Admin>

Kết luận

Việc tạo các bảng điều khiển quản trị React không cần phải phức tạp như trước đây. Với thư viện react-admin của React, chúng ta có thể dễ dàng tạo các giao diện quản trị. Trên đây, mình đã giới thiệu và demo cho các bạn thấy 1 ví dụ đơn giản, nhanh chóng sử dụng react-admin. Còn rất nhiều điều hay ho trong bài viết này mình chưa tiện đề cập do vấn đề thời gian như: Filter, Pagination, Sidebar, DataProvider, etc. Các bạn có tthể tham khảo thêm trên trang chủ của React-admin nhé. Ngoài ra, các bạn có thể tìm thấy code cho dự án này trên GitHub tại đây. Hẹn gặp lại các bạn ở những bài blog tiếp theo.

References

https://marmelab.com/react-admin/

https://www.airplane.dev/blog/react-js-tutorial-how-to-build-an-admin-panel-using-react-js