Chuyển tới nội dung chính

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 (...)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:

ComponentComponentComponent
alert-dialogdropdown-menuselect
avatarformseparator
badgeinputsheet
breadcrumblabelsidebar
buttonnavigation-menuskeleton
cardprogressswitch
checkboxradio-grouptable
collapsiblerich-text-editortabs
dialogscroll-areatextarea
toggletoggle-grouptooltip

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.