Base CMS library for React
npm install @libeyondea/base-cms> Thư viện React CMS chuyên nghiệp với bộ component, hooks và utilities đầy đủ, được xây dựng trên Material-UI v7 và các công nghệ hiện đại nhất.



- Giới thiệu
- Cài đặt
- Bắt đầu nhanh
- Components
- Hooks
- Services
- Utilities
- Theme System
- Ví dụ chi tiết
- API Reference
- TypeScript Support
- License
@libeyondea/base-cms là một thư viện React toàn diện, cung cấp tất cả những gì bạn cần để xây dựng một hệ thống CMS (Content Management System) hiện đại và mạnh mẽ. Được thiết kế với triết lý "batteries included" - tất cả đều có sẵn và sẵn sàng sử dụng ngay lập tức.
- 🎨 20+ UI Components - Dựa trên Material-UI v7, tùy chỉnh sâu cho use-case CMS
- 📝 Form System hoàn chỉnh - React Hook Form 7.63 + Yup validation
- 📊 Advanced Table - TanStack Table v8 với filtering, sorting, pagination
- 🎭 Theme System linh hoạt - Dark/Light mode, customizable colors
- 🛣️ Advanced Routing System - Custom routes với guards, layouts và nested routes
- 📡 API Integration - BaseService class với full CRUD operations
- 🎯 TypeScript First - 100% type-safe với full type definitions
- 🚀 Production Ready - Optimized build, tree-shakeable, < 200KB gzipped
- ♿ Accessibility - WCAG 2.1 Level AA compliant
- 📱 Responsive - Mobile-first design
| Category | Technologies |
| ----------------- | ------------------------------------- |
| Core | React 19.2, TypeScript 5.9, Vite 7.1 |
| UI Framework | Material-UI v7, Emotion |
| Form | React Hook Form 7.63, Yup 1.7 |
| State | Redux Toolkit 2.9, React Redux 9.2 |
| Data Fetching | TanStack React Query 5.90, Axios 1.12 |
| Table | TanStack React Table 8.21 |
| Routing | React Router DOM 7.9 |
| Utils | Lodash-es, Moment, Qs |
``bash`
npm install @libeyondea/base-cms
`bash`
npm install --save-dev @libeyondea/base-cms-dev
Package @libeyondea/base-cms-dev bao gồm:
- TypeScript 5.9.3
- Vite 7.1.9 + plugins
- ESLint 9.36.0 + Prettier 3.6.2
- Type definitions (@types/\*)
`bash`Cài đặt tất cả peer dependencies cần thiết
npm install react@19.2.0 react-dom@19.2.0 react-router-dom@7.9.3 @reduxjs/toolkit@2.9.0 react-redux@9.2.0 @emotion/react@11.14.0 @emotion/styled@11.14.1 @mui/icons-material@7.3.4 @mui/material@7.3.4 @mui/system@7.3.3 @mui/x-date-pickers@8.12.0 @tanstack/react-query@5.90.2 @tanstack/react-table@8.21.3 @hookform/resolvers@5.2.2 axios@1.12.2 dayjs@1.11.18 js-cookie@3.0.5 lodash-es@4.17.21 qs@6.14.0 react-big-calendar@1.19.4 react-hook-form@7.63.0 react-icons@5.5.0 react-number-format@5.4.4 react-toastify@11.0.5 sweetalert2@11.23.0 yup@1.7.1
Library này yêu cầu các dependencies sau phải được cài đặt trong project của bạn:
Xem danh sách peer dependencies cần cài đặt
#### UI & Styling
- @emotion/react (11.14.0)@emotion/styled
- (11.14.1)@mui/material
- (7.3.4)@mui/system
- (7.3.3)@mui/icons-material
- (7.3.4)@mui/x-date-pickers
- (8.12.0)
#### Form & Validation
- react-hook-form (7.63.0)@hookform/resolvers
- (5.2.2)yup
- (1.7.1)
#### State Management
- @reduxjs/toolkit (2.9.0)react-redux
- (9.2.0)
#### Data Fetching & Tables
- @tanstack/react-query (5.90.2)@tanstack/react-table
- (8.21.3)axios
- (1.12.2)
#### Utilities
- dayjs (1.11.18)lodash-es
- (4.17.21)js-cookie
- (3.0.5)qs
- (6.14.0)
#### UI Components
- react-big-calendar (1.19.4)react-icons
- (5.5.0)react-number-format
- (5.4.4)react-toastify
- (11.0.5)sweetalert2
- (11.23.0)
> ⚠️ Lưu ý quan trọng: Tất cả các dependencies trên đều là peer dependencies và BẮT BUỘC phải cài đặt trong project của bạn.
Library sử dụng peer dependencies thay vì bundle dependencies vì:
- ✅ Tránh xung đột phiên bản: Người dùng có thể kiểm soát phiên bản dependencies
- ✅ Giảm kích thước bundle: Library nhẹ hơn, chỉ chứa code thực tế
- ✅ Tương thích tốt hơn: Hoạt động với project hiện có mà không gây xung đột
- ✅ Flexibility: Người dùng có thể chọn phiên bản dependencies phù hợp
- ✅ Tree-shaking tốt hơn: Chỉ import những gì thực sự cần thiết
`tsx
import React from 'react';
import { AppProvider } from '@libeyondea/base-cms';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
function App() {
return (
Hello Base CMS!
);
}
ReactDOM.createRoot(document.getElementById('root')!).render(
`
`tsx
import { AppProvider, createCustomTheme } from '@libeyondea/base-cms';
import { PaletteMode } from '@mui/material';
// Tạo custom theme
const myCustomTheme = (mode: PaletteMode) => ({
palette: {
mode,
primary: {
main: '#1976d2'
},
secondary: {
main: '#dc004e'
}
},
typography: {
fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif'
}
});
function App() {
return
}
`
`tsx
import { yupResolver } from '@hookform/resolvers/yup';
import { FormProvider, MainCard, RHFTextField } from '@libeyondea/base-cms';
import { useForm } from 'react-hook-form';
import * as yup from 'yup';
const schema = yup.object({
email: yup.string().email('Email không hợp lệ').required('Email là bắt buộc'),
password: yup.string().min(6, 'Mật khẩu tối thiểu 6 ký tự').required('Mật khẩu là bắt buộc')
});
function LoginForm() {
const methods = useForm({
resolver: yupResolver(schema),
defaultValues: { email: '', password: '' }
});
const onSubmit = (data: any) => {
console.log('Login data:', data);
};
return (
);
}
`
> Lưu ý quan trọng: Tất cả component có prefix RHF (React Hook Form) phải được wrap trong FormProvider để hoạt động.
| Component | Mô tả | Props chính |
| ------------------------- | -------------------------------------- | -------------------------------------- |
| FormProvider | Provider cho React Hook Form context | methods, onSubmit, children |label
| FormLabel | Label component với styling nhất quán | , required, tooltip |name
| RHFTextField | Text input với validation | , label, type, placeholder |name
| RHFPhone | Input số điện thoại (format VN) | , label, placeholder |name
| RHFDatePicker | Date picker với validation | , label, minDate, maxDate |name
| RHFTimePicker | Time picker | , label |name
| RHFAutocomplete | Autocomplete single selection | , label, options |name
| RHFAutocompleteMulti | Autocomplete multiple selection | , label, options |name
| RHFNationalID | Input CMND/CCCD với validation | , label |name
| RHFTextFieldSelect | Text field kết hợp dropdown | , label, options |name
| RHFSelect | Select dropdown | , label, options |name
| RHFSwitch | Toggle switch | , label |name
| RHFTextFieldAdvanced | Text field nâng cao với nhiều tùy chọn | , label, multiline, rows |name
| RHFMoney | Input tiền tệ với format VN | , label, currency |name
| RHFScheduleTimePicker | Time picker cho lịch trình | , label, timeSlots |
#### Ví dụ sử dụng Form Components
`tsx
import { yupResolver } from '@hookform/resolvers/yup';
import {
FormLabel,
FormProvider,
RHFAutocomplete,
RHFAutocompleteMulti,
RHFDatePicker,
RHFMoney,
RHFNationalID,
RHFPhone,
RHFSelect,
RHFSwitch,
RHFTextField,
RHFTextFieldAdvanced
} from '@libeyondea/base-cms';
import { Box, Button, Grid } from '@mui/material';
import { useForm } from 'react-hook-form';
import * as yup from 'yup';
// Validation schema
const schema = yup.object({
fullName: yup.string().required('Họ tên là bắt buộc'),
email: yup.string().email('Email không hợp lệ').required('Email là bắt buộc'),
phone: yup.string().required('Số điện thoại là bắt buộc'),
nationalId: yup.string().required('CMND/CCCD là bắt buộc'),
salary: yup.number().required('Lương là bắt buộc'),
birthDate: yup.date().required('Ngày sinh là bắt buộc').nullable(),
role: yup.number().required('Vai trò là bắt buộc'),
department: yup.object().required('Phòng ban là bắt buộc').nullable(),
skills: yup.array().min(1, 'Chọn ít nhất 1 kỹ năng'),
isActive: yup.boolean(),
description: yup.string().max(500, 'Mô tả không quá 500 ký tự')
});
const roleOptions = [
{ id: 1, name: 'Admin' },
{ id: 2, name: 'Manager' },
{ id: 3, name: 'User' }
];
const departmentOptions = [
{ id: 1, name: 'IT' },
{ id: 2, name: 'HR' },
{ id: 3, name: 'Marketing' }
];
const skillOptions = [
{ id: 1, name: 'React' },
{ id: 2, name: 'TypeScript' },
{ id: 3, name: 'Node.js' },
{ id: 4, name: 'Python' }
];
function UserForm() {
const methods = useForm({
resolver: yupResolver(schema),
defaultValues: {
fullName: '',
email: '',
phone: '',
nationalId: '',
salary: 0,
birthDate: null,
role: 1,
department: null,
skills: [],
isActive: true,
description: ''
}
});
const onSubmit = (data: any) => {
console.log('Form data:', data);
// Handle form submission
};
return (
{/ Basic Text Fields /}
{/ Phone & National ID /}
{/ Money Input /}
{/ Date Picker /}
{/ Select Dropdown /}
{/ Autocomplete Single /}
{/ Autocomplete Multiple /}
{/ Switch /}
{/ Advanced Text Field /}
{/ Form Actions /}
);
}
`
Components có prefix N là các input component độc lập, không cần React Hook Form:
| Component | Mô tả | Use Case |
| ---------------------- | ---------------------- | --------------------------------- |
| NTextField | Text input cơ bản | Khi không cần validation phức tạp |
| NSelect | Select dropdown cơ bản | Simple dropdown |
| NAutocomplete | Autocomplete single | Simple autocomplete |
| NAutocompleteMulti | Autocomplete multiple | Tag selection |
| NTextFieldSelect | Text field + dropdown | Combined input |
| NDatePicker | Date picker cơ bản | Simple date selection |
#### Ví dụ sử dụng Input Components
`tsx
import { useState } from 'react';
import { NAutocomplete, NAutocompleteMulti, NDatePicker, NSelect, NTextField, NTextFieldSelect } from '@libeyondea/base-cms';
import { Box, Button, Grid, Typography } from '@mui/material';
const countryOptions = [
{ id: 1, name: 'Việt Nam' },
{ id: 2, name: 'Thái Lan' },
{ id: 3, name: 'Singapore' }
];
const tagOptions = [
{ id: 1, name: 'Frontend' },
{ id: 2, name: 'Backend' },
{ id: 3, name: 'Mobile' },
{ id: 4, name: 'DevOps' }
];
function SearchForm() {
const [searchData, setSearchData] = useState({
keyword: '',
country: null,
tags: [],
category: '',
dateRange: null
});
const handleSearch = () => {
console.log('Search data:', searchData);
// Handle search logic
};
return (
Tìm kiếm nâng cao
{/ Basic Text Field /}
placeholder="Nhập từ khóa tìm kiếm..."
value={searchData.keyword}
onChange={(e) => setSearchData((prev) => ({ ...prev, keyword: e.target.value }))}
fullWidth
/>
{/ Select Dropdown /}
options={countryOptions}
value={searchData.country}
onChange={(value) => setSearchData((prev) => ({ ...prev, country: value }))}
fullWidth
/>
{/ Autocomplete Single /}
options={[
{ id: 1, name: 'Công nghệ' },
{ id: 2, name: 'Kinh doanh' },
{ id: 3, name: 'Giáo dục' }
]}
value={searchData.category}
onChange={(value) => setSearchData((prev) => ({ ...prev, category: value }))}
fullWidth
/>
{/ Autocomplete Multiple /}
options={tagOptions}
value={searchData.tags}
onChange={(value) => setSearchData((prev) => ({ ...prev, tags: value }))}
fullWidth
/>
{/ Date Picker /}
value={searchData.dateRange}
onChange={(value) => setSearchData((prev) => ({ ...prev, dateRange: value }))}
fullWidth
/>
{/ Text Field Select /}
options={[
{ id: 1, name: 'PDF' },
{ id: 2, name: 'Word' },
{ id: 3, name: 'Excel' }
]}
value={searchData.category}
onChange={(value) => setSearchData((prev) => ({ ...prev, category: value }))}
fullWidth
/>
{/ Search Button /}
variant="outlined"
onClick={() =>
setSearchData({
keyword: '',
country: null,
tags: [],
category: '',
dateRange: null
})
}
>
Xóa bộ lọc
);
}
`
Hệ thống table mạnh mẽ dựa trên TanStack Table v8:
| Component | Mô tả |
| ----------------------- | --------------------------------------- |
| StanstackTable | Table component chính với full features |
| SubTable | Nested table cho hierarchical data |
| TableToolbar | Toolbar với search, filters, actions |
| TableSkeletonRow | Loading skeleton cho table |
| DefaultColumnFilter | Default filter component |
| EmptyView | Empty state view |
| Row | Custom row component |
#### Table Props chính
`tsx`
interface StanstackTableProps {
data: any[]; // Dữ liệu table
columns: ColumnDef
enablePagination?: boolean; // Bật pagination
enableSorting?: boolean; // Bật sorting
enableFiltering?: boolean; // Bật filtering
enableRowSelection?: boolean; // Bật row selection
pageSize?: number; // Số rows mỗi page
onRowClick?: (row: any) => void; // Click handler
}
#### Ví dụ Table
`tsx
import { StanstackTable, StatusChip } from '@libeyondea/base-cms';
const columns = [
{
accessorKey: 'id',
header: 'ID'
},
{
accessorKey: 'name',
header: 'Tên'
},
{
accessorKey: 'email',
header: 'Email'
},
{
accessorKey: 'status',
header: 'Trạng thái',
cell: ({ getValue }) =>
}
];
const data = [
{ id: 1, name: 'Nguyễn Văn A', email: 'a@example.com', status: 1 },
{ id: 2, name: 'Trần Thị B', email: 'b@example.com', status: 0 }
];
function UserTable() {
return (
columns={columns}
enablePagination
enableSorting
enableFiltering
pageSize={10}
onRowClick={(row) => console.log('Clicked:', row)}
/>
);
}
`
| Component | Mô tả | Use Case |
| ------------------- | -------------------------- | ---------------------- |
| Avatar | Avatar component với modal | User profile display |
| Breadcrumbs | Breadcrumb navigation | Page hierarchy |
| DateRangePicker | Date range selector | Filter by date range |
| LoadingScreen | Full-screen loader | Loading states |
| MenuPopup | Popup menu | Actions menu |
| Modal | Modal component | Dialogs, confirmations |
| SoundButton | Button với sound effect | Interactive buttons |
| StatusChip | Status badge | Display status |
| Tab | Tab component | Tabbed interfaces |
| Toastify | Toast notifications | Success/error messages |
| TruncatedText | Text với tooltip | Long text handling |
| ScheduleDisplay | Schedule display | Calendar/time display |
| ItemList | List component | Display item lists |
#### Ví dụ sử dụng UI Components
``tsx
import { Avatar, Breadcrumbs, DateRangePicker, LoadingScreen, MenuPopup, Modal, SoundButton, StatusChip, Tab, Toastify, TruncatedText } from '@libeyondea/base-cms';
import { Box, Button, Chip, Grid, Typography } from '@mui/material';
import { useState } from 'react';
function UIComponentsDemo() {
const [modalOpen, setModalOpen] = useState(false);
const [loading, setLoading] = useState(false);
const [dateRange, setDateRange] = useState(null);
const [activeTab, setActiveTab] = useState(0);
const handleAction = (action: string) => {
console.log('Action:', action);
};
const showToast = () => {
Toastify.success('Thành công!');
};
const tabItems = [
{ label: 'Thông tin', value: 0 },
{ label: 'Cài đặt', value: 1 },
{ label: 'Lịch sử', value: 2 }
];
return (
UI Components Demo
{/ Avatar /}
alt="User Avatar"
size={80}
onClick={() => console.log('Avatar clicked')}
/>
{/ Status Chip /}
{/ Sound Button /}
soundUrl="/sounds/click.mp3"
onClick={() => console.log('Button clicked')}
>
Click me!
{/ Date Range Picker /}
onChange={setDateRange}
placeholder="Chọn khoảng thời gian"
/>
{/ Menu Popup /}
{
label: 'Xem',
icon: '👁️',
onClick: () => handleAction('view')
},
{
label: 'Sửa',
icon: '✏️',
onClick: () => handleAction('edit')
},
{
label: 'Xóa',
icon: '🗑️',
onClick: () => handleAction('delete'),
color: 'error'
}
]}
>
{/ Truncated Text /}
maxLength={50}
/>
{/ Tab Component /}
value={activeTab}
onChange={setActiveTab}
/>
{activeTab === 0 &&
{activeTab === 1 &&
{activeTab === 2 &&
{/ Modal /}
onClose={() => setModalOpen(false)}
title="Modal Demo"
content="Đây là nội dung của modal"
actions={[
{
label: 'Hủy',
variant: 'outlined',
onClick: () => setModalOpen(false)
},
{
label: 'Xác nhận',
variant: 'contained',
onClick: () => {
setModalOpen(false);
showToast();
}
}
]}
/>
{/ Loading Screen /}
variant="contained"
onClick={() => {
setLoading(true);
setTimeout(() => setLoading(false), 3000);
}}
>
Show Loading
);
}
| Component | Mô tả |
| ----------------- | ----------------------------------------------- |
| Header | Top navigation header với profile, theme toggle |
| Sidebar | Side navigation menu với submenu support |
| PageContainer | Main page wrapper với breadcrumbs |
| MainCard | Card container với search, actions |
| Drawer | Drawer component cho mobile menu |
#### Ví dụ sử dụng Layout Components
`tsx
import { Drawer, Header, MainCard, PageContainer, Sidebar } from '@libeyondea/base-cms';
import { Box, CssBaseline, Typography } from '@mui/material';
import { useState } from 'react';
const sidebarItems = [
{
id: 'dashboard',
title: 'Dashboard',
icon: '🏠',
path: '/dashboard'
},
{
id: 'users',
title: 'Người dùng',
icon: '👥',
path: '/users',
children: [
{ id: 'user-list', title: 'Danh sách', path: '/users/list' },
{ id: 'user-roles', title: 'Vai trò', path: '/users/roles' }
]
},
{
id: 'products',
title: 'Sản phẩm',
icon: '📦',
path: '/products'
},
{
id: 'settings',
title: 'Cài đặt',
icon: '⚙️',
path: '/settings'
}
];
function DashboardLayout() {
const [drawerOpen, setDrawerOpen] = useState(false);
return (
{/ Header /}
onMenuClick={() => setDrawerOpen(!drawerOpen)}
userProfile={{
name: 'Nguyễn Văn A',
email: 'admin@example.com',
avatar: '/avatar.jpg'
}}
onLogout={() => console.log('Logout')}
/>
{/ Sidebar /}
onClose={() => setDrawerOpen(false)}
items={sidebarItems}
logo="/logo.png"
logoText="Base CMS"
/>
{/ Main Content /}
breadcrumbs={[
{ label: 'Trang chủ', href: '/' },
{ label: 'Dashboard', href: '/dashboard' }
]}
>
searchProps={{
placeholder: 'Tìm kiếm...',
onSearch: (value) => console.log('Search:', value)
}}
actions={[
{
label: 'Xuất Excel',
onClick: () => console.log('Export Excel')
},
{
label: 'Thêm mới',
variant: 'contained',
onClick: () => console.log('Add new')
}
]}
>
Nội dung dashboard sẽ được hiển thị ở đây...
{/ Mobile Drawer /}
onClose={() => setDrawerOpen(false)}
anchor="left"
>
Menu
{/ Drawer content /}
);
}
``
Library cung cấp hệ thống routing mạnh mẽ với guards và layouts tự động:
`tsx
import { lazy } from 'react';
import { Routes, RoutesConfig } from '@libeyondea/base-cms';
const myRoutes: RoutesConfig = {
// Auth routes - tự động có AuthGuard
auth: [
{
path: 'login',
element: lazy(() => import('./pages/Login'))
},
{
path: 'signup',
element: lazy(() => import('./pages/Signup'))
}
],
// Private routes - tự động có PrivateGuard
private: [
{
index: true,
element: lazy(() => import('./pages/Dashboard'))
},
{
path: 'users',
element: lazy(() => import('./pages/Users'))
},
{
path: 'settings',
element: lazy(() => import('./pages/Settings'))
}
],
// Public routes - không có guard
public: [
{
path: 'about',
element: lazy(() => import('./pages/About'))
},
{
path: 'contact',
element: lazy(() => import('./pages/Contact'))
}
],
// Custom route groups
groups: [
{
prefix: '/admin',
guard: 'admin', // Custom guard
routes: [
{
path: 'users',
element: lazy(() => import('./pages/admin/Users'))
}
]
}
],
// Custom 404 page
notFound: lazy(() => import('./pages/NotFound')),
// Custom error page
error: lazy(() => import('./pages/Error'))
};
function App() {
return
}
`
| Prop | Type | Mô tả |
| ------------------- | -------------- | ----------------------------------------------------- |
| config | RoutesConfig | Cấu hình routes (bắt buộc) |basename
| | string | Base path cho router |profileAPI
| | string | API endpoint để check authentication |redirectPrivateTo
| | string | Redirect path khi chưa đăng nhập (default: '/signin') |redirectAuthTo
| | string | Redirect path khi đã đăng nhập (default: '/') |
- AuthGuard: Redirect về private routes nếu đã đăng nhập
- PrivateGuard: Redirect về auth routes nếu chưa đăng nhập
- Custom Guards: Có thể tạo custom guards cho specific routes
Hook để truy cập và điều khiển theme:
`tsx
import { useTheme } from '@libeyondea/base-cms';
function ThemeToggler() {
const { mode, toggleTheme, setThemeMode } = useTheme();
return (
Current mode: {mode}
API:
-
theme - Current theme object (Material-UI Theme)
- mode - Current mode: 'light' | 'dark'
- toggleTheme() - Toggle between light/dark
- setThemeMode(mode) - Set specific mode$3
Hook để điều khiển sidebar:
`tsx
import { useSidebar } from '@libeyondea/base-cms';function SidebarToggle() {
const { drawerOpen, toggleDrawer, setDrawerOpen } = useSidebar();
return (
);
}
`API:
-
drawerOpen - Boolean state của drawer
- toggleDrawer() - Toggle drawer open/close
- setDrawerOpen(open) - Set drawer state$3
Hook để quản lý table filters:
`tsx
import { useTableContext } from '@libeyondea/base-cms';function TableFilter() {
const { filters, setFilterTable } = useTableContext();
const handleFilter = () => {
setFilterTable({
type: 'users',
value: { status: 1, role: 'admin' }
});
};
return ;
}
`API:
-
filters - Object chứa tất cả filters theo type
- setFilterTable({ type, value }) - Set filter cho một table type$3
Hook để truy cập Redux state (deprecated, dùng React Redux hooks thay thế):
`tsx
import { useStateValue } from '@libeyondea/base-cms';function UserProfile() {
const { state, dispatch } = useStateValue();
return
User: {state.user?.name};
}
`$3
Hook để play audio:
`tsx
import { useAudioPlayer } from '@libeyondea/base-cms';function SoundPlayer() {
const { play, pause, stop, isPlaying } = useAudioPlayer('/sounds/click.mp3');
return (
Status: {isPlaying ? 'Playing' : 'Stopped'}
);
}
`API:
-
play() - Play audio
- pause() - Pause audio
- stop() - Stop và reset audio
- isPlaying - Boolean playing state$3
Hook để hiển thị SweetAlert2 dialogs:
`tsx
import { useSweetAlert } from '@libeyondea/base-cms';function DeleteButton() {
const { showConfirm, showSuccess, showError } = useSweetAlert();
const handleDelete = async () => {
const result = await showConfirm({
title: 'Xác nhận xóa',
text: 'Bạn có chắc muốn xóa?'
});
if (result.isConfirmed) {
// Delete logic
showSuccess('Đã xóa thành công!');
}
};
return ;
}
`API:
-
showConfirm(options) - Show confirmation dialog
- showSuccess(message) - Show success message
- showError(message) - Show error message
- showWarning(message) - Show warning message
- showInfo(message) - Show info message🛠️ Services
$3
Abstract class cung cấp các phương thức CRUD chuẩn:
`tsx
import { BaseService } from '@libeyondea/base-cms';// Tạo service cho User
class UserService extends BaseService {
constructor() {
super('/users'); // API endpoint prefix
}
// Custom methods
async getUserProfile(userId: string) {
return this.getById(userId, {}, '/profile');
}
async updateUserRole(userId: string, role: string) {
return this.update(userId, { role }, '/role');
}
}
const userService = new UserService();
// Sử dụng
async function loadUsers() {
// GET /users?page=1&limit=10
const response = await userService.getAll({ page: 1, limit: 10 });
console.log(response.data);
// GET /users/123
const user = await userService.getById('123');
// POST /users
const newUser = await userService.create({ name: 'John', email: 'john@example.com' });
// PUT /users/123
const updated = await userService.update('123', { name: 'Jane' });
// DELETE /users/123
await userService.delete('123');
}
`#### BaseService API
| Method | Signature | Mô tả |
| ------------------- | ------------------------------------------------------ | ---------------------------------------- |
|
getAll | getAll | GET danh sách với filtering & pagination |
| getById | getById | GET theo ID |
| create | create | POST tạo mới |
| update | update | PUT cập nhật |
| delete | delete(id, path?, config?, options?) | DELETE xóa |
| deleteWithPayload | deleteWithPayload(payload, path?, config?, options?) | DELETE với body |
| uploadFile | uploadFile | POST upload file |#### ServiceOptions
`tsx
interface ServiceOptions {
isOtherUrl?: boolean; // Sử dụng URL khác (không prefix apiName)
isFormData?: boolean; // Request là FormData
timeout?: number; // Timeout (ms), default: 30000
}
`#### Ví dụ Upload File
`tsx
class FileService extends BaseService {
constructor() {
super('/files');
} async uploadAvatar(file: File) {
return this.uploadFile('/upload/avatar', file);
}
async uploadMultiple(files: File[]) {
const formData = new FormData();
files.forEach((file, index) => {
formData.append(
file${index}, file);
});
return this.uploadFile('/upload/multiple', formData);
}
}
`🧰 Utilities
$3
Pre-configured axios instance:
`tsx
import { axiosServices } from '@libeyondea/base-cms';// Đã có sẵn interceptors cho auth, error handling
const response = await axiosServices.get('/api/users');
`$3
`tsx
import { generateColorPalette, getContrastColor, hexToRgb } from '@libeyondea/base-cms';// Generate color palette
const palette = generateColorPalette('#1976d2');
// Returns: { 50: '#...', 100: '#...', ..., 900: '#...' }
// Get contrast color (black or white)
const contrast = getContrastColor('#1976d2');
// Returns: '#ffffff' hoặc '#000000'
// Convert hex to RGB
const rgb = hexToRgb('#1976d2');
// Returns: { r: 25, g: 118, b: 210 }
`$3
`tsx
import { formatDate, formatDateTime, formatRelativeTime, formatTime, isToday, isYesterday } from '@libeyondea/base-cms';const now = new Date();
formatDate(now); // "04/10/2025"
formatDateTime(now); // "04/10/2025 14:30"
formatTime(now); // "14:30"
formatRelativeTime(now); // "vừa xong", "5 phút trước", etc.
isToday(now); // true/false
isYesterday(now); // true/false
`$3
`tsx
import { formatCurrency, formatFileSize, formatNumber, formatPhone } from '@libeyondea/base-cms';formatCurrency(1000000); // "1,000,000 VND"
formatNumber(1234.56); // "1,234.56"
formatPhone('0123456789'); // "0123 456 789"
formatFileSize(1024); // "1 KB"
formatFileSize(1048576); // "1 MB"
`$3
`tsx
import { getCookie, removeCookie, setCookie } from '@libeyondea/base-cms';// Set cookie (expires in 7 days)
setCookie('token', 'abc123', 7);
// Get cookie
const token = getCookie('token');
// Remove cookie
removeCookie('token');
// Set cookie with options
setCookie('user', JSON.stringify({ id: 1 }), 7, {
secure: true,
sameSite: 'strict'
});
`$3
`tsx
import { MONTHS, REQUIRED_MESSAGE, STATUS_CONSTANT, USER_CONSTANT, WEEK_DAYS } from '@libeyondea/base-cms';// Validation messages
console.log(REQUIRED_MESSAGE); // "Trường này là bắt buộc"
// Status options
console.log(STATUS_CONSTANT);
// [{ id: 0, name: 'Không hoạt động' }, { id: 1, name: 'Hoạt động' }]
// User role options
console.log(USER_CONSTANT);
// [{ id: 0, name: 'Quản trị viên' }, { id: 1, name: 'Người dùng' }]
// Week days
console.log(WEEK_DAYS);
// [{ id: '0', name: 'CN' }, { id: '1', name: 'T2' }, ...]
// Months
console.log(MONTHS);
// [{ id: '0', name: 'Tháng 1' }, { id: '1', name: 'Tháng 2' }, ...]
`$3
`tsx
import { capitalizeFirstLetter, groupBy, slugify, sortBy, truncate, uniqueArray } from '@libeyondea/base-cms';// Unique array
uniqueArray([1, 2, 2, 3]); // [1, 2, 3]
// Group by property
const users = [
{ id: 1, role: 'admin' },
{ id: 2, role: 'user' },
{ id: 3, role: 'admin' }
];
groupBy(users, 'role');
// { admin: [{...}, {...}], user: [{...}] }
// Sort by property
sortBy(users, 'id', 'desc');
// Capitalize
capitalizeFirstLetter('hello'); // "Hello"
// Slugify
slugify('Xin chào Việt Nam'); // "xin-chao-viet-nam"
// Truncate
truncate('Long text...', 10); // "Long text..."
`🎨 Theme System
$3
`tsx
import { AppProvider } from '@libeyondea/base-cms';function App() {
return (
{/ Theme tự động: light/dark dựa trên system preference /}
{/ Theme được persist vào localStorage /}
);
}
`$3
`tsx
import { AppProvider } from '@libeyondea/base-cms';
import { PaletteMode } from '@mui/material';const customTheme = (mode: PaletteMode) => ({
palette: {
mode,
primary: {
main: '#00acc1', // Cyan
light: '#5ddef4',
dark: '#007c91',
contrastText: '#fff'
},
secondary: {
main: '#f50057', // Pink
light: '#ff5983',
dark: '#bb002f',
contrastText: '#fff'
},
background: {
default: mode === 'light' ? '#f5f5f5' : '#121212',
paper: mode === 'light' ? '#ffffff' : '#1e1e1e'
}
},
typography: {
fontFamily: '"Inter", "Roboto", "Helvetica", "Arial", sans-serif',
h1: { fontSize: '2.5rem', fontWeight: 700 },
h2: { fontSize: '2rem', fontWeight: 600 },
button: { textTransform: 'none' }
},
shape: {
borderRadius: 12
},
shadows: [
'none',
'0px 2px 4px rgba(0,0,0,0.1)'
// ... more shadows
],
components: {
MuiButton: {
styleOverrides: {
root: {
borderRadius: 8,
padding: '8px 16px'
}
}
},
MuiCard: {
styleOverrides: {
root: {
borderRadius: 12,
boxShadow: '0 2px 8px rgba(0,0,0,0.1)'
}
}
}
}
});
function App() {
return {/ Your app /} ;
}
`$3
`tsx
import { useTheme } from '@libeyondea/base-cms';
import { Box } from '@mui/material';function ThemedComponent() {
const { theme, mode, toggleTheme } = useTheme();
return (
sx={{
backgroundColor: theme.palette.background.paper,
color: theme.palette.text.primary,
padding: theme.spacing(2),
borderRadius: theme.shape.borderRadius
}}
>
Current mode: {mode}
);
}
`📚 Ví dụ chi tiết
$3
`tsx
import React from 'react';import { yupResolver } from '@hookform/resolvers/yup';
import { FormProvider, MainCard, RHFDatePicker, RHFNationalID, RHFPhone, RHFSelect, RHFSwitch, RHFTextField } from '@libeyondea/base-cms';
import { Box, Button, Grid } from '@mui/material';
import { useForm } from 'react-hook-form';
import * as yup from 'yup';
// Schema validation
const schema = yup.object({
fullName: yup.string().required('Họ tên là bắt buộc'),
email: yup.string().email('Email không hợp lệ').required('Email là bắt buộc'),
phone: yup.string().required('Số điện thoại là bắt buộc'),
nationalId: yup.string().required('CMND/CCCD là bắt buộc'),
birthDate: yup.date().required('Ngày sinh là bắt buộc').nullable(),
gender: yup.number().required('Giới tính là bắt buộc'),
address: yup.string().required('Địa chỉ là bắt buộc'),
agreeTerms: yup.boolean().oneOf([true], 'Bạn phải đồng ý với điều khoản')
});
const genderOptions = [
{ id: 1, name: 'Nam' },
{ id: 2, name: 'Nữ' },
{ id: 3, name: 'Khác' }
];
function RegisterForm() {
const methods = useForm({
resolver: yupResolver(schema),
defaultValues: {
fullName: '',
email: '',
phone: '',
nationalId: '',
birthDate: null,
gender: 1,
address: '',
agreeTerms: false
}
});
const onSubmit = async (data: any) => {
try {
console.log('Form data:', data);
// Call API to register
// await userService.create(data);
alert('Đăng ký thành công!');
} catch (error) {
console.error('Registration failed:', error);
}
};
return (
);
}
export default RegisterForm;
`$3
`tsx
import React, { useState } from 'react';import { MainCard, MenuPopup, StanstackTable, StatusChip } from '@libeyondea/base-cms';
import { IconButton } from '@mui/material';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { FiEdit, FiEye, FiTrash2 } from 'react-icons/fi';
// Service
class UserService extends BaseService {
constructor() {
super('/users');
}
}
const userService = new UserService();
function UserManagement() {
const queryClient = useQueryClient();
const [page, setPage] = useState(1);
const [limit, setLimit] = useState(10);
// Fetch users
const { data, isLoading, error } = useQuery({
queryKey: ['users', page, limit],
queryFn: () => userService.getAll({ page, limit })
});
// Delete mutation
const deleteMutation = useMutation({
mutationFn: (id: string) => userService.delete(id),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['users'] });
alert('Xóa thành công!');
},
onError: (error) => {
console.error('Delete failed:', error);
alert('Xóa thất bại!');
}
});
// Table columns
const columns = [
{
accessorKey: 'id',
header: 'ID',
size: 80
},
{
accessorKey: 'avatar',
header: 'Avatar',
cell: ({ getValue }) => })
},
{
accessorKey: 'name',
header: 'Họ và tên'
},
{
accessorKey: 'email',
header: 'Email'
},
{
accessorKey: 'phone',
header: 'Số điện thoại'
},
{
accessorKey: 'role',
header: 'Vai trò',
cell: ({ getValue }) => {getValue() === 1 ? 'Admin' : 'User'}
},
{
accessorKey: 'status',
header: 'Trạng thái',
cell: ({ getValue }) =>
},
{
accessorKey: 'createdAt',
header: 'Ngày tạo',
cell: ({ getValue }) => formatDateTime(getValue())
},
{
id: 'actions',
header: 'Thao tác',
cell: ({ row }) => (
options={[
{
label: 'Xem',
icon: ,
onClick: () => console.log('View', row.original.id)
},
{
label: 'Sửa',
icon: ,
onClick: () => console.log('Edit', row.original.id)
},
{
label: 'Xóa',
icon: ,
onClick: () => {
if (confirm('Bạn có chắc muốn xóa?')) {
deleteMutation.mutate(row.original.id);
}
},
color: 'error'
}
]}
/>
)
}
];
if (error) {
return
Error: {error.message};
} return (
data={data?.data || []}
columns={columns}
enablePagination
enableSorting
enableFiltering
enableRowSelection
pageSize={limit}
isLoading={isLoading}
onPageChange={(newPage) => setPage(newPage)}
onPageSizeChange={(newLimit) => setLimit(newLimit)}
onRowClick={(row) => console.log('Row clicked:', row)}
/>
);
}
export default UserManagement;
`$3
`tsx
import React from 'react';
import { lazy } from 'react';import { AppProvider, Routes, RoutesConfig } from '@libeyondea/base-cms';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { Provider } from 'react-redux';
import { store } from './store';
// Lazy load pages
const Login = lazy(() => import('./pages/auth/Login'));
const Signup = lazy(() => import('./pages/auth/Signup'));
const Dashboard = lazy(() => import('./pages/private/Dashboard'));
const Users = lazy(() => import('./pages/private/Users'));
const Settings = lazy(() => import('./pages/private/Settings'));
const About = lazy(() => import('./pages/public/About'));
const Contact = lazy(() => import('./pages/public/Contact'));
const NotFound = lazy(() => import('./pages/NotFound'));
// Query client
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: 1,
refetchOnWindowFocus: false
}
}
});
// Routes configuration
const routesConfig: RoutesConfig = {
auth: [
{
path: 'login',
element: Login
},
{
path: 'signup',
element: Signup
}
],
private: [
{
index: true,
element: Dashboard
},
{
path: 'users',
element: Users
},
{
path: 'settings',
element: Settings
}
],
public: [
{
path: 'about',
element: About
},
{
path: 'contact',
element: Contact
}
],
notFound: NotFound
};
function App() {
return (
);
}
export default App;
`📘 API Reference
$3
`tsx
interface AppProviderProps {
children: ReactNode;
customTheme?: (mode: PaletteMode) => any;
}
`$3
`tsx
interface FormProviderProps {
children: ReactNode;
methods: UseFormReturn;
onSubmit: (data: any) => void | Promise;
}
`$3
`tsx
interface StanstackTableProps {
data: any[];
columns: ColumnDef[];
enablePagination?: boolean;
enableSorting?: boolean;
enableFiltering?: boolean;
enableRowSelection?: boolean;
enableColumnVisibility?: boolean;
pageSize?: number;
isLoading?: boolean;
onRowClick?: (row: any) => void;
onPageChange?: (page: number) => void;
onPageSizeChange?: (size: number) => void;
onSelectionChange?: (selectedRows: any[]) => void;
}
`$3
`tsx
interface RoutesProps {
config: RoutesConfig;
basename?: string;
profileAPI?: string;
redirectPrivateTo?: string;
redirectAuthTo?: string;
}interface RoutesConfig {
auth?: RouteConfig[];
private?: RouteConfig[];
public?: RouteConfig[];
groups?: RouteGroupConfig[];
notFound?: ComponentType | LazyExoticComponent>;
error?: ComponentType | LazyExoticComponent>;
basePath?: string;
}
`$3
`tsx
constructor(apiName: string, options?: ServiceOptions)interface ServiceOptions {
isOtherUrl?: boolean;
isFormData?: boolean;
timeout?: number;
}
`💡 TypeScript Support
Library được viết 100% bằng TypeScript và cung cấp đầy đủ type definitions:
`tsx
import type { ApiResponse, FilterObject, IUniqueId, ServiceOptions } from '@libeyondea/base-cms';// Type-safe service
class ProductService extends BaseService {
constructor() {
super('/products');
}
async getProducts(filters: FilterObject): Promise> {
const response = await this.getAll(filters);
return response.data;
}
}
// Type-safe form
interface UserFormData {
name: string;
email: string;
role: number;
}
const methods = useForm({
defaultValues: {
name: '',
email: '',
role: 1
}
});
`🎓 Best Practices
$3
Luôn sử dụng Yup schema cho validation phức tạp:
`tsx
const schema = yup.object({
email: yup.string().email('Email không hợp lệ').required('Email là bắt buộc'),
password: yup
.string()
.min(8, 'Mật khẩu tối thiểu 8 ký tự')
.matches(/[A-Z]/, 'Phải có ít nhất 1 chữ hoa')
.matches(/[0-9]/, 'Phải có ít nhất 1 số')
.required('Mật khẩu là bắt buộc')
});
`$3
Tổ chức services theo domain:
`
services/
├── core/
│ └── baseService.ts
├── auth/
│ ├── authService.ts
│ └── tokenService.ts
├── user/
│ └── userService.ts
└── product/
└── productService.ts
`$3
Tái sử dụng components thông qua composition:
`tsx
// Bad
function UserForm() {
return (
);
}// Good
function UserForm() {
return (
);
}
`$3
Luôn handle errors properly:
`tsx
async function loadData() {
try {
const response = await userService.getAll();
setData(response.data);
} catch (error) {
console.error('Failed to load data:', error);
showError('Không thể tải dữ liệu');
}
}
`$3
Sử dụng React Query cho data fetching:
`tsx
const { data, isLoading, error } = useQuery({
queryKey: ['users', filters],
queryFn: () => userService.getAll(filters),
staleTime: 5 60 1000 // 5 minutes
});
`🐛 Troubleshooting
$3
Nguyên nhân: Component sử dụng
useTheme không được wrap trong AppProvider.Giải pháp:
`tsx
// ❌ Bad
function App() {
return ;
}// ✅ Good
function App() {
return (
);
}
`$3
Nguyên nhân: Chưa wrap RHF components trong
FormProvider.Giải pháp:
`tsx
// ❌ Bad
// ✅ Good
`$3
Nguyên nhân: Chưa cài đặt đầy đủ peer dependencies.
Giải pháp: Cài đặt tất cả peer dependencies theo hướng dẫn ở phần Cài đặt.
$3
Nguyên nhân: Phiên bản dependencies không tương thích với yêu cầu của library.
Giải pháp: Sử dụng đúng phiên bản dependencies như đã liệt kê trong peer dependencies.
🔄 Migration Guide
$3
⚠️ BREAKING CHANGES: Từ v1.0.21, tất cả dependencies đã được chuyển thành peer dependencies.
Migration steps:
1. Update library:
`bash
npm update @libeyondea/base-cms
`2. Cài đặt peer dependencies:
`bash
npm install react@19.2.0 react-dom@19.2.0 react-router-dom@7.9.3 @reduxjs/toolkit@2.9.0 react-redux@9.2.0 @emotion/react@11.14.0 @emotion/styled@11.14.1 @mui/icons-material@7.3.4 @mui/material@7.3.4 @mui/system@7.3.3 @mui/x-date-pickers@8.12.0 @tanstack/react-query@5.90.2 @tanstack/react-table@8.21.3 @hookform/resolvers@5.2.2 axios@1.12.2 dayjs@1.11.18 js-cookie@3.0.5 lodash-es@4.17.21 qs@6.14.0 react-big-calendar@1.19.4 react-hook-form@7.63.0 react-icons@5.5.0 react-number-format@5.4.4 react-toastify@11.0.5 sweetalert2@11.23.0 yup@1.7.1
`3. Xóa dependencies cũ (nếu có):
`bash
Xóa các dependencies đã được chuyển thành peer dependencies
npm uninstall @emotion/react @emotion/styled @mui/material @mui/icons-material @mui/system @mui/x-date-pickers @tanstack/react-query @tanstack/react-table react-redux @hookform/resolvers axios dayjs js-cookie lodash-es qs react-big-calendar react-hook-form react-icons react-number-format react-toastify sweetalert2 yup
`📄 License
MIT License - xem file LICENSE để biết thêm chi tiết.
Copyright (c) 2025 Nguyen Thuc
👨💻 Tác giả
Nguyen Thuc
- GitHub: @libeyondea
- Twitter: @libeyondea
🔗 Liên kết
- 📦 NPM Package
- 🐙 GitHub Repository
- 📝 Changelog
- 🐛 Issue Tracker
- 💬 Discussions
🙏 Acknowledgements
Cảm ơn các thư viện open-source tuyệt vời:
- Material-UI - UI Framework
- React Hook Form - Form Management
- TanStack Table - Table Component
- TanStack Query - Data Fetching
- Redux Toolkit - State Management
📚 Tài liệu bổ sung
$3
Library giờ đây hỗ trợ linh hoạt nhiều cấu trúc role khác nhau:
-
role: 'admin' - Single string
- roles: ['admin', 'user'] - Array of strings
- roles: [{ten_vai_tro: 'admin'}]` - Array of objects📖 Xem hướng dẫn chi tiết: ROLE_CONFIG_GUIDE.md
📖 Xem README trong Sidebar component: README_ROLE_CONFIG.md
📖 Xem ví dụ config: roleConfig.example.ts
---