Cấu trúc Frontend
Frontend sử dụng React 19 + Vite 7 với TanStack Router (file-based routing).
File-based Routing
Routing được tổ chức theo convention trong thư mục src/features/:
features/
├── __root.tsx # Root layout (providers, error boundary)
├── (authenticated)/ # Yêu cầu đăng nhập
│ ├── route.tsx # Auth guard layout
│ ├── index.tsx # Redirect mặc định
│ ├── overview/ # Dashboard
│ ├── admin/ # Quản trị hệ thống
│ ├── business/ # Kinh doanh (customers, contracts)
│ ├── contracts/ # Hợp đồng
│ ├── contacts/ # Danh bạ
│ ├── operations/ # Vận hành
│ │ ├── projects/ # Dự án
│ │ ├── sprint/ # Sprint
│ │ ├── tasks/ # Công việc
│ │ ├── posts/ # Bài đăng
│ │ ├── shooting-sessions/ # Phiên chụp/quay
│ │ ├── non-billable-requests/# Yêu cầu ngoài HĐ
│ │ └── resource-dispatch/ # Phân bổ nguồn lực
│ ├── organization/ # Tổ chức nhân sự
│ ├── notifications/ # Thông báo
│ └── support/ # Hỗ trợ
├── (authentication)/ # Đăng nhập, quên MK
└── (errors)/ # 404, 500
Route Groups
Thư mục có dấu ngoặc tròn (...) là route group -- chúng không tạo URL segment, chỉ dùng để nhóm layout. Ví dụ (authenticated)/admin -> URL là /admin.
Component Library
UI Components (components/ui/)
30 component dựa trên Radix UI + Tailwind CSS 4, tương thích shadcn/ui:
| Component | Component | Component |
|---|---|---|
alert-dialog | dropdown-menu | select |
avatar | form | separator |
badge | input | sheet |
breadcrumb | label | sidebar |
button | navigation-menu | skeleton |
card | progress | switch |
checkbox | radio-group | table |
collapsible | rich-text-editor | tabs |
dialog | scroll-area | textarea |
toggle | toggle-group | tooltip |
Shared Components (components/shared/)
components/shared/
├── empty-state.tsx # Placeholder khi không có dữ liệu
├── error-boundary/ # Bắt lỗi React, hiển thị fallback
└── loading/ # Loading spinners, skeletons
Layouts
layouts/
├── classic/ # Sidebar layout (chính)
│ └── classic-sidebar-layout.tsx
├── empty/ # Layout không sidebar (login, errors)
└── components/ # Header, sidebar items, breadcrumbs
Custom Hooks
hooks/
├── auth/ # useAuth, usePermissions, useCurrentUser
├── navigation/ # useNavigate, useBreadcrumb
├── organization/ # useOrganization context
├── ui/ # useMediaQuery, useDebounce
├── use-notifications.ts # WebSocket notifications
├── use-cache-version-polling.ts # Auto-refresh khi config đổi
└── use-typed-form.ts # Type-safe form hook wrapper
State Management
Zustand Stores
stores/auth-store.ts
// Lưu thông tin user đã đăng nhập, permissions, menu
import { create } from 'zustand';
interface AuthStore {
user: User | null;
permissions: string[];
menus: Menu[];
setUser: (user: User) => void;
logout: () => void;
}
stores/notification-store.ts
// Quản lý state thông báo realtime
interface NotificationStore {
notifications: Notification[];
unreadCount: number;
markAsRead: (id: string) => void;
}
TanStack Query (Server State)
API data được quản lý hoàn toàn qua React Query -- auto-generated từ Orval:
// Import hooks từ packages/api-client
import { useGetProjects, useCreateProject } from '@smartck/api-client';
function ProjectList() {
const { data, isLoading } = useGetProjects({ page: 1, limit: 10 });
const { mutate: createProject } = useCreateProject();
}
Form Pattern
Tất cả form sử dụng React Hook Form + Zod validation:
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
const schema = z.object({
name: z.string().min(1, 'Tên không được để trống'),
email: z.string().email('Email không hợp lệ'),
});
function CustomerForm() {
const form = useForm({
resolver: zodResolver(schema),
defaultValues: { name: '', email: '' },
});
return (
<Form {...form}>
<FormField name="name" render={({ field }) => (
<FormItem>
<FormLabel>Tên</FormLabel>
<FormControl><Input {...field} /></FormControl>
<FormMessage />
</FormItem>
)} />
</Form>
);
}
Cách thêm trang mới
Bước 1: Tạo route file
features/(authenticated)/reports/index.tsx
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/(authenticated)/reports/')({
component: ReportsPage,
});
function ReportsPage() {
return <div>Báo cáo</div>;
}
Bước 2: Generate route tree
# TanStack Router tự detect file mới khi chạy dev
# Hoặc chạy thủ công:
cd apps/frontend && npx tsr generate
Bước 3: Thêm menu (nếu cần)
Thêm menu mới qua module IAM > Admin > Menus trong Swagger hoặc seed data.
Hot Reload
Khi chạy pnpm dev, TanStack Router sẽ tự động detect file route mới và regenerate routeTree.gen.ts. Không cần restart server.