[CLAUDE] Scaffold: khoi tao SOLUTION_ERP Phase 0
- .NET 10 Clean Architecture: Domain/Application/Infrastructure/Api (4 project) - 2 React + Vite + TS app: fe-admin (:8082), fe-user (:8080) voi proxy /api - Node engines >=20, .nvmrc = 20 cho CI (bai hoc NamGroup) - SQL Server 2022 qua docker-compose (dev) - Parse 8 FORM -> docs/forms-spec.md (catalog + ma HD format RG-001) - Parse QUY_TRINH -> docs/workflow-contract.md (9 phase state machine + role matrix) - docs: CLAUDE.md, STATUS.md, PROJECT-MAP.md, migration-todos.md (roadmap 5 phase) - .claude/skills: 3 placeholder (contract-workflow, form-engine, permission-matrix) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
55
.claude/skills/README.md
Normal file
@ -0,0 +1,55 @@
|
||||
# Skill Library — SOLUTION_ERP
|
||||
|
||||
Skill này là tài liệu chuyên biệt để Claude (và developer khác) dùng khi cần deep-dive 1 domain area. Claude tự động invoke qua Skill tool dựa trên semantic matching với `description` trong từng `SKILL.md`.
|
||||
|
||||
## Skills hiện có
|
||||
|
||||
| Skill | Mục đích | Trigger ví dụ | Trạng thái |
|
||||
|---|---|---|---|
|
||||
| `contract-workflow` | State machine 9 phase, role × phase guard, SLA timer, auto-approve | "approve contract", "chuyển phase", "auto-approve quá hạn" | 📝 Placeholder (Phase 3) |
|
||||
| `form-engine` | Render template docx/xlsx, parse 8 form, field mapping, PO generator | "export contract as word", "điền form", "render template" | 📝 Placeholder (Phase 2) |
|
||||
| `permission-matrix` | Role × MenuKey × CRUD, seed, reset password, 3-layer resolution | "permission denied", "gán role", "menu không hiện" | 📝 Placeholder (Phase 1) |
|
||||
|
||||
## Format chuẩn 1 skill
|
||||
|
||||
Mỗi skill là 1 folder với ít nhất `SKILL.md` + optional `examples/` + `references/`:
|
||||
|
||||
```
|
||||
.claude/skills/<skill-name>/
|
||||
├── SKILL.md ← Entry point: description, when-to-use, workflow
|
||||
├── examples/ ← Code snippets mẫu
|
||||
│ └── *.cs | *.tsx
|
||||
└── references/ ← Link đến file code thật, docs
|
||||
```
|
||||
|
||||
**Frontmatter `SKILL.md`:**
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: contract-workflow
|
||||
description: State machine 9 phase cho hợp đồng — guard rule, SLA, auto-approve
|
||||
when-to-use:
|
||||
- "approve contract"
|
||||
- "state machine bug"
|
||||
- "SLA expired"
|
||||
---
|
||||
|
||||
# Contract Workflow Skill
|
||||
|
||||
## Context
|
||||
...
|
||||
|
||||
## Workflow
|
||||
...
|
||||
|
||||
## Code pointers
|
||||
- `src/Backend/SolutionErp.Domain/Contracts/ContractPhase.cs`
|
||||
- `src/Backend/SolutionErp.Application/Contracts/Commands/TransitionContractCommand.cs`
|
||||
```
|
||||
|
||||
## Tạo skill mới — checklist
|
||||
|
||||
1. Tạo folder `.claude/skills/<kebab-case-name>/`
|
||||
2. Viết `SKILL.md` với frontmatter + sections: Context / Workflow / Code pointers / Common pitfalls
|
||||
3. Add row vào bảng "Skills hiện có" phía trên
|
||||
4. Commit `[CLAUDE] Skill: add <name>`
|
||||
34
.claude/skills/contract-workflow/SKILL.md
Normal file
@ -0,0 +1,34 @@
|
||||
---
|
||||
name: contract-workflow
|
||||
description: State machine 9 phase cho hợp đồng TP/NCC/Tổ đội — guard rule, SLA auto-approve, role × phase matrix. Dùng khi debug transition, approve HĐ, xử lý HĐ quá hạn.
|
||||
when-to-use:
|
||||
- "transition contract"
|
||||
- "chuyển phase hợp đồng"
|
||||
- "HĐ quá hạn auto-approve"
|
||||
- "role không duyệt được"
|
||||
- "reject contract về draft"
|
||||
---
|
||||
|
||||
# Contract Workflow Skill
|
||||
|
||||
> **Phase 3 deliverable.** Hiện tại skill này là PLACEHOLDER — sẽ được expand khi implement Phase 3.
|
||||
|
||||
## Context
|
||||
|
||||
Xem đầy đủ ở [`docs/workflow-contract.md`](../../../docs/workflow-contract.md):
|
||||
- 9 state: `DangChon` → `DangSoanThao` → `DangGopY` → `DangDamPhan` → `DangInKy` → `DangKiemTraCCM` → `DangTrinhKy` → `DangDongDau` → `DaPhatHanh` (+ `TuChoi`)
|
||||
- SLA mỗi phase: Draft 7d, GópÝ 7d, ĐàmPhán 7d, InKý 1d, CCMCheck 3d, BOD 1d
|
||||
- Role × Phase matrix (Drafter, TBP/TPB, PD/PM, PRO/EQU/FIN/ACT, CCM, BOD/NĐUQ, HRA)
|
||||
|
||||
## Code pointers (sẽ có sau Phase 3)
|
||||
|
||||
- `src/Backend/SolutionErp.Domain/Contracts/ContractPhase.cs` (enum)
|
||||
- `src/Backend/SolutionErp.Domain/Contracts/Contract.cs` (aggregate root)
|
||||
- `src/Backend/SolutionErp.Application/Contracts/Services/IContractWorkflowService.cs`
|
||||
- `src/Backend/SolutionErp.Infrastructure/HostedServices/SlaExpiryJob.cs`
|
||||
|
||||
## Common pitfalls (dự kiến — update khi build)
|
||||
|
||||
- Không check bypass flag khi HĐ với Chủ đầu tư → sẽ reject oan ở CCM phase
|
||||
- Gen mã HĐ trước khi BOD approve → có thể waste số thứ tự nếu reject sau đó
|
||||
- Auto-approve chạy trong transaction dài → lock table → timeout
|
||||
34
.claude/skills/form-engine/SKILL.md
Normal file
@ -0,0 +1,34 @@
|
||||
---
|
||||
name: form-engine
|
||||
description: Template engine render 8 form hợp đồng ra .docx/.xlsx giống 100% mẫu gốc. Gen mã HĐ theo RG-001. Dùng khi export HĐ, upload template mới, debug render lỗi format.
|
||||
when-to-use:
|
||||
- "export contract to word"
|
||||
- "render template docx"
|
||||
- "xuất đơn đặt hàng excel"
|
||||
- "gen mã hợp đồng"
|
||||
- "upload template mới"
|
||||
---
|
||||
|
||||
# Form Engine Skill
|
||||
|
||||
> **Phase 2 deliverable.** Hiện tại skill này là PLACEHOLDER.
|
||||
|
||||
## Context
|
||||
|
||||
Xem đầy đủ ở [`docs/forms-spec.md`](../../../docs/forms-spec.md):
|
||||
- 8 form (6 .docx/.doc + 1 .xlsx + 1 .docx quy định)
|
||||
- Mã HĐ format theo SOL-CCM-RG-001: `{Project}/{Type}/SOL&{Partner}/{Seq}` và biến thể
|
||||
- 3 file `.doc` cần convert qua Word COM / LibreOffice headless trước khi parse
|
||||
|
||||
## Tech stack dự kiến
|
||||
|
||||
- **.docx render:** DocumentFormat.OpenXml (free, verbose) hoặc Aspose.Words (phí, dễ)
|
||||
- **.xlsx render:** EPPlus (free non-commercial) hoặc ClosedXML (free)
|
||||
- **PDF preview:** wkhtmltopdf hoặc LibreOffice `--convert-to pdf`
|
||||
|
||||
## Code pointers (sẽ có sau Phase 2)
|
||||
|
||||
- `src/Backend/SolutionErp.Application/Forms/Services/IFormRenderer.cs`
|
||||
- `src/Backend/SolutionErp.Infrastructure/Forms/DocxRenderer.cs`
|
||||
- `src/Backend/SolutionErp.Infrastructure/Forms/XlsxRenderer.cs`
|
||||
- `src/Backend/SolutionErp.Infrastructure/Services/ContractCodeGenerator.cs`
|
||||
41
.claude/skills/permission-matrix/SKILL.md
Normal file
@ -0,0 +1,41 @@
|
||||
---
|
||||
name: permission-matrix
|
||||
description: Hệ thống phân quyền Role × MenuKey × CRUD. Sidebar gating, permission guard, seed default, reset password. Dùng khi debug access denied, gán role, menu không hiện.
|
||||
when-to-use:
|
||||
- "permission denied"
|
||||
- "access denied"
|
||||
- "menu không hiện"
|
||||
- "gán role cho user"
|
||||
- "reset password"
|
||||
- "seed permission"
|
||||
---
|
||||
|
||||
# Permission Matrix Skill
|
||||
|
||||
> **Phase 1 deliverable.** Hiện tại skill này là PLACEHOLDER.
|
||||
|
||||
## Context
|
||||
|
||||
Pattern copy từ **NamGroup** skill `permission-system` nhưng đơn giản hóa:
|
||||
- 1 User có N Role
|
||||
- 1 Role có ma trận (MenuKey, CRUD flags) — `Permission` table
|
||||
- Không có per-user override (giữ đơn giản cho Phase 1)
|
||||
- Menu tree flat 2 cấp, hardcode `MenuKey`
|
||||
|
||||
## Tech
|
||||
|
||||
- BE: `[Authorize(Policy = "Menu.Read")]` attribute
|
||||
- FE: `<PermissionGuard menuKey="Contracts" action="Update">` + `usePermission().can("Contracts", "Update")`
|
||||
- Resolution: API `/api/menus/me` trả về tree + permissions đã resolved theo user's roles
|
||||
|
||||
## Code pointers (sẽ có sau Phase 1)
|
||||
|
||||
- `src/Backend/SolutionErp.Domain/Identity/Permission.cs`
|
||||
- `src/Backend/SolutionErp.Application/Permissions/Queries/GetMyMenuTreeQuery.cs`
|
||||
- `fe-admin/src/components/PermissionGuard.tsx`
|
||||
- `fe-admin/src/hooks/usePermission.ts`
|
||||
|
||||
## Common pitfalls (dự kiến)
|
||||
|
||||
- Quên refresh token sau khi admin update permission → user phải logout/login mới thấy
|
||||
- MenuKey hardcode dễ typo → tập trung vào file `src/lib/menuKeys.ts` (FE) + `MenuKeys.cs` (BE const)
|
||||
79
.gitignore
vendored
Normal file
@ -0,0 +1,79 @@
|
||||
# =============================================================
|
||||
# .NET
|
||||
# =============================================================
|
||||
bin/
|
||||
obj/
|
||||
out/
|
||||
*.user
|
||||
*.suo
|
||||
*.ide/
|
||||
*.VisualState.xml
|
||||
*.pidb
|
||||
*.booproj
|
||||
*.svd
|
||||
*.pdb
|
||||
*.mdb
|
||||
.vs/
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# EF Core migrations scratch
|
||||
src/**/Migrations/*.Designer.cs.bak
|
||||
|
||||
# =============================================================
|
||||
# Node / React / Vite
|
||||
# =============================================================
|
||||
node_modules/
|
||||
dist/
|
||||
dist-ssr/
|
||||
.vite/
|
||||
*.local
|
||||
coverage/
|
||||
.eslintcache
|
||||
|
||||
# =============================================================
|
||||
# Logs
|
||||
# =============================================================
|
||||
logs/
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# =============================================================
|
||||
# Editor / OS
|
||||
# =============================================================
|
||||
.idea/
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
Thumbs.db
|
||||
desktop.ini
|
||||
.DS_Store
|
||||
|
||||
# =============================================================
|
||||
# Secrets / env
|
||||
# =============================================================
|
||||
appsettings.*.Local.json
|
||||
appsettings.Production.json
|
||||
*.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
src/Backend/SolutionErp.Api/.env
|
||||
|
||||
# =============================================================
|
||||
# Build artifacts / uploads
|
||||
# =============================================================
|
||||
src/Backend/SolutionErp.Api/wwwroot/uploads/
|
||||
src/Backend/SolutionErp.Api/wwwroot/exports/
|
||||
|
||||
# =============================================================
|
||||
# Claude / skills (keep tracked — these ARE source of truth)
|
||||
# =============================================================
|
||||
# .claude/ ← DO NOT ignore, skills are committed
|
||||
!.claude/
|
||||
!.claude/**
|
||||
80
CLAUDE.md
Normal file
@ -0,0 +1,80 @@
|
||||
# CLAUDE.md — AI Agent Context
|
||||
|
||||
> **Full content:** [`docs/CLAUDE.md`](docs/CLAUDE.md)
|
||||
|
||||
---
|
||||
|
||||
**SOLUTION_ERP** — Hệ thống Quản lý Hợp đồng Nhà cung cấp / Thầu phụ / Tổ đội cho Công ty TNHH Xây dựng Solutions.
|
||||
|
||||
Kiến trúc: **.NET 10 Clean Architecture + 2 React FE (admin + user) + SQL Server + IIS**.
|
||||
|
||||
## 🚀 BẮT ĐẦU SESSION — 4 file đọc trước tiên
|
||||
|
||||
```
|
||||
1. docs/STATUS.md ← Snapshot HIỆN TẠI (phase nào, việc gì đang làm)
|
||||
2. docs/PROJECT-MAP.md ← Bản đồ toàn cảnh (nên đọc đầu dự án)
|
||||
3. docs/changelog/migration-todos.md ← Atomic tasks theo từng phase
|
||||
4. docs/workflow-contract.md ← ⭐ State machine 9 phase — ảnh hưởng mọi thiết kế
|
||||
```
|
||||
|
||||
## ⚡ Quick Rules
|
||||
|
||||
### Backend — `.NET 10` Clean Architecture
|
||||
|
||||
- Solution: `SolutionErp.slnx` ở root, projects ở `src/Backend/`
|
||||
- **4 layer:** `Api → Application ← Domain` + `Infrastructure → Application`
|
||||
- Pattern: **CQRS + MediatR**, **FluentValidation**, **AutoMapper**
|
||||
- Repository qua `IApplicationDbContext` interface (Application layer)
|
||||
- Auth: **JWT Bearer + ASP.NET Identity**
|
||||
- DB: **SQL Server** (LocalDB dev / SQL Server prod), **EF Core 10 Code-First migrations**
|
||||
- Error handling: `GlobalExceptionMiddleware` map exception → HTTP status
|
||||
- Commit scope tech stack: `Api` · `App` · `Domain` · `Infra`
|
||||
|
||||
### Frontend — 2 app React 18 + Vite + TS + shadcn/ui + TanStack Query
|
||||
|
||||
- `fe-admin/` (port **8082**) · `fe-user/` (port **8080**)
|
||||
- Vite proxy `/api → http://localhost:5443` (SolutionErp.Api)
|
||||
- **Named export**, không default export (trừ `App`)
|
||||
- shadcn/ui copy-paste, **duplicate giữa 2 app là CÓ CHỦ ĐÍCH** (mỗi app UX riêng)
|
||||
- Auth context: `solution-erp-admin-token` / `solution-erp-user-token` ở `localStorage`
|
||||
- TanStack Query cho data fetching
|
||||
- **Node pin `>=20`** trong `engines`; CI pin `20.x` qua `.nvmrc` (bài học NamGroup — KHÔNG dùng Node latest trên CI)
|
||||
- UI **100% tiếng Việt**
|
||||
|
||||
### Database conventions
|
||||
|
||||
- Schema: `dbo` (single schema)
|
||||
- Table: **PascalCase tiếng Anh** (Contracts, Suppliers, Projects, ContractApprovals, ...)
|
||||
- PK: `Id` (Guid), FK: `{Entity}Id`
|
||||
- Audit fields: `CreatedAt`, `UpdatedAt`, `CreatedBy`, `UpdatedBy` (`BaseEntity`)
|
||||
- Soft delete: `IsDeleted`, `DeletedAt`, `DeletedBy` (`AuditableEntity`)
|
||||
- Migrations: `dotnet ef migrations add <Name> --project src/Backend/SolutionErp.Infrastructure --startup-project src/Backend/SolutionErp.Api`
|
||||
|
||||
### Commit convention
|
||||
|
||||
```
|
||||
[CLAUDE] <scope>: <imperative message>
|
||||
```
|
||||
|
||||
**Scope:** `Contract` · `Form` · `Workflow` · `Supplier` · `Auth` · `Admin` · `Api` · `App` · `Domain` · `Infra` · `FE-Admin` · `FE-User` · `Docs` · `CICD` · `Scripts`
|
||||
|
||||
## 📖 Tài liệu quan trọng
|
||||
|
||||
| File | Nội dung |
|
||||
|---|---|
|
||||
| [`docs/STATUS.md`](docs/STATUS.md) | **🔥 Current state** — đọc đầu tiên |
|
||||
| [`docs/PROJECT-MAP.md`](docs/PROJECT-MAP.md) | Bản đồ tổng quan |
|
||||
| [`docs/changelog/migration-todos.md`](docs/changelog/migration-todos.md) | Roadmap 5 phase + atomic tasks |
|
||||
| [`docs/CLAUDE.md`](docs/CLAUDE.md) | Full context — tech stack, architecture |
|
||||
| [`docs/workflow-contract.md`](docs/workflow-contract.md) | State machine 9 phase + role matrix |
|
||||
| [`docs/forms-spec.md`](docs/forms-spec.md) | Catalog 8 form + quy định mã HĐ |
|
||||
|
||||
## ⚠️ Kết thúc session
|
||||
|
||||
1. Update `docs/STATUS.md` (`In Progress` → `Recently Done`)
|
||||
2. Tick checklist tương ứng trong `docs/changelog/migration-todos.md`
|
||||
3. Tạo session log `docs/changelog/sessions/YYYY-MM-DD-HHMM-{topic}.md` nếu đáng ghi
|
||||
4. Commit `[CLAUDE] <scope>: <message>`
|
||||
5. ⚠️ **Update `SolutionErp.slnx`** nếu có `.cs/.csproj` mới
|
||||
|
||||
> Bỏ qua nếu chỉ trả lời câu hỏi, không sửa file nào.
|
||||
68
README.md
Normal file
@ -0,0 +1,68 @@
|
||||
# SOLUTION_ERP
|
||||
|
||||
Hệ thống quản lý Hợp đồng Nhà cung cấp / Thầu phụ / Tổ đội cho **Công ty TNHH Xây dựng Solutions**.
|
||||
|
||||
> 📘 AI context: [`CLAUDE.md`](CLAUDE.md) (pointer) → [`docs/CLAUDE.md`](docs/CLAUDE.md) (full)
|
||||
|
||||
## Quick start (dev)
|
||||
|
||||
**Yêu cầu:** .NET 10 SDK, Node 20+, SQL Server (local hoặc qua Docker).
|
||||
|
||||
```powershell
|
||||
# 1. DB — chạy SQL Server qua Docker (nếu chưa có local)
|
||||
docker compose up -d
|
||||
|
||||
# 2. Backend — migration + run Api (port 5443)
|
||||
dotnet ef database update --project src/Backend/SolutionErp.Infrastructure --startup-project src/Backend/SolutionErp.Api
|
||||
dotnet run --project src/Backend/SolutionErp.Api
|
||||
|
||||
# 3. Frontend admin (port 8082) — terminal mới
|
||||
cd fe-admin
|
||||
npm install
|
||||
npm run dev
|
||||
|
||||
# 4. Frontend user (port 8080) — terminal mới
|
||||
cd fe-user
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Admin mặc định (sẽ seed sau Phase 1): `admin@solutionerp.local` / `Admin@123456`
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
fe-admin (:8082) fe-user (:8080)
|
||||
│ │
|
||||
└────── /api proxy ──────┘
|
||||
▼
|
||||
SolutionErp.Api (:5443)
|
||||
│
|
||||
┌──────────────┼──────────────┐
|
||||
▼ ▼ ▼
|
||||
Application Domain Infrastructure ── SQL Server
|
||||
```
|
||||
|
||||
## Tech stack
|
||||
|
||||
- **Backend:** .NET 10 + Clean Architecture + CQRS (MediatR) + FluentValidation + AutoMapper + EF Core + ASP.NET Identity + JWT
|
||||
- **Frontend:** React 18 + Vite + TypeScript + Tailwind + shadcn/ui + TanStack Query
|
||||
- **DB:** SQL Server 2022
|
||||
- **Deploy:** Windows Server + IIS
|
||||
|
||||
## Roadmap
|
||||
|
||||
| Phase | Tuần | Focus |
|
||||
|---|---|---|
|
||||
| 0 Draft | T1 | Scaffold, parse FORM + QUY_TRINH |
|
||||
| 1 Alpha Core | T2-4 | Auth, Permission, CRUD master |
|
||||
| 2 Form Engine | T5-6 | Render template docx/xlsx |
|
||||
| 3 Workflow | T7-9 | State machine 9 phase |
|
||||
| 4 Report + Polish | T10-11 | Dashboard + Excel export |
|
||||
| 5 Production | T12-13 | CI/CD IIS, UAT, go-live |
|
||||
|
||||
Chi tiết ở [`docs/changelog/migration-todos.md`](docs/changelog/migration-todos.md).
|
||||
|
||||
## License
|
||||
|
||||
Proprietary — Công ty TNHH Xây dựng Solutions.
|
||||
9
SolutionErp.slnx
Normal file
@ -0,0 +1,9 @@
|
||||
<Solution>
|
||||
<Folder Name="/src/" />
|
||||
<Folder Name="/src/Backend/">
|
||||
<Project Path="src/Backend/SolutionErp.Api/SolutionErp.Api.csproj" />
|
||||
<Project Path="src/Backend/SolutionErp.Application/SolutionErp.Application.csproj" />
|
||||
<Project Path="src/Backend/SolutionErp.Domain/SolutionErp.Domain.csproj" />
|
||||
<Project Path="src/Backend/SolutionErp.Infrastructure/SolutionErp.Infrastructure.csproj" />
|
||||
</Folder>
|
||||
</Solution>
|
||||
16
docker-compose.yml
Normal file
@ -0,0 +1,16 @@
|
||||
services:
|
||||
sqlserver:
|
||||
image: mcr.microsoft.com/mssql/server:2022-latest
|
||||
container_name: solution-erp-sql
|
||||
environment:
|
||||
- ACCEPT_EULA=Y
|
||||
- MSSQL_SA_PASSWORD=SolutionErp@2026
|
||||
- MSSQL_PID=Developer
|
||||
ports:
|
||||
- "1433:1433"
|
||||
volumes:
|
||||
- sqldata:/var/opt/mssql
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
sqldata:
|
||||
139
docs/CLAUDE.md
Normal file
@ -0,0 +1,139 @@
|
||||
# CLAUDE.md — Full Context (SOLUTION_ERP)
|
||||
|
||||
## 1. Giới thiệu
|
||||
|
||||
**SOLUTION_ERP** là hệ thống quản lý Hợp đồng Nhà cung cấp / Thầu phụ / Tổ đội / Dịch vụ cho **Công ty TNHH Xây dựng Solutions**, thay thế quy trình giấy / Word+Excel hiện tại.
|
||||
|
||||
**Scope chính:**
|
||||
1. Số hóa 8 form hợp đồng (template engine điền field → export .docx/.xlsx)
|
||||
2. Workflow 9 phase trình ký (state machine + role guard + SLA auto-approve)
|
||||
3. Tự gen mã HĐ theo Quy định SOL-CCM-RG-001
|
||||
4. Dashboard báo cáo HĐ theo NCC, dự án, trạng thái, giá trị
|
||||
|
||||
## 2. Kiến trúc tổng thể
|
||||
|
||||
```
|
||||
┌────────────────┐ ┌────────────────┐
|
||||
│ fe-admin │ │ fe-user │ React 18 + Vite + TS + shadcn/ui
|
||||
│ :8082 │ │ :8080 │ TanStack Query, Tailwind
|
||||
└────────┬───────┘ └────────┬───────┘
|
||||
│ │
|
||||
│ Vite proxy /api → :5443
|
||||
▼ ▼
|
||||
┌──────────────────────────────────────┐
|
||||
│ SolutionErp.Api :5443 │ ASP.NET Core 10 + JWT
|
||||
│ Controllers / Middleware / Swagger │
|
||||
└──────────────┬───────────────────────┘
|
||||
│
|
||||
┌────────▼─────────┐
|
||||
│ SolutionErp. │ CQRS + MediatR
|
||||
│ Application │ FluentValidation + AutoMapper
|
||||
└────────┬─────────┘
|
||||
│
|
||||
┌────────▼─────────┐ ← SolutionErp.Infrastructure
|
||||
│ SolutionErp. │ (EF Core, Identity, Repos, External services)
|
||||
│ Domain │
|
||||
└──────────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────┐
|
||||
│ SQL Server │ dbo schema, GUID PK
|
||||
└────────────────┘
|
||||
```
|
||||
|
||||
## 3. Project layout
|
||||
|
||||
```
|
||||
SOLUTION_ERP/
|
||||
├── src/Backend/
|
||||
│ ├── SolutionErp.Domain/ Entities, ValueObjects, Enums, DomainEvents
|
||||
│ ├── SolutionErp.Application/ CQRS handlers, DTOs, Validators, Mappings, Interfaces
|
||||
│ ├── SolutionErp.Infrastructure/ EF DbContext, Identity, Repositories, External
|
||||
│ └── SolutionErp.Api/ Controllers, Middleware, Program.cs, appsettings
|
||||
├── fe-admin/ (admin UI — CRUD master data, approve, dashboards)
|
||||
│ └── src/
|
||||
├── fe-user/ (user UI — soạn thảo HĐ, comment, upload)
|
||||
│ └── src/
|
||||
├── docs/
|
||||
│ ├── CLAUDE.md (file này)
|
||||
│ ├── STATUS.md
|
||||
│ ├── PROJECT-MAP.md
|
||||
│ ├── forms-spec.md ⭐ 8 form catalog + RG-001 code format
|
||||
│ ├── workflow-contract.md ⭐ 9 phase state machine + role matrix
|
||||
│ ├── guides/ setup, cicd, code-rules...
|
||||
│ ├── database/ schema docs
|
||||
│ └── changelog/
|
||||
│ ├── migration-todos.md Roadmap 5 phase
|
||||
│ └── sessions/ YYYY-MM-DD session logs
|
||||
├── .claude/skills/ Skill library (contract-workflow, form-engine, ...)
|
||||
├── scripts/ parse_forms.py, parse_workflow.py, seed, deploy
|
||||
├── SolutionErp.slnx Solution file
|
||||
├── global.json .NET 10 SDK pin
|
||||
├── docker-compose.yml SQL Server cho dev
|
||||
└── CLAUDE.md Pointer → docs/CLAUDE.md
|
||||
```
|
||||
|
||||
## 4. Tech stack chi tiết
|
||||
|
||||
| Layer | Tech | Version |
|
||||
|---|---|---|
|
||||
| .NET SDK | .NET | 10.0.104 |
|
||||
| ORM | EF Core | 10 |
|
||||
| DB | SQL Server | 2019+ (LocalDB dev) |
|
||||
| API | ASP.NET Core Web API | 10 |
|
||||
| Mediator | MediatR | latest |
|
||||
| Validation | FluentValidation | latest |
|
||||
| Mapping | AutoMapper | latest |
|
||||
| Auth | JWT Bearer + ASP.NET Identity | 10 |
|
||||
| Logging | Serilog | latest |
|
||||
| OpenAPI | Swashbuckle | latest |
|
||||
| FE Build | Vite | 8 |
|
||||
| FE Lib | React | 19 (auto-scaffolded) |
|
||||
| FE Lang | TypeScript | 6 |
|
||||
| FE Styling | Tailwind CSS + shadcn/ui | Phase 1 install |
|
||||
| FE Data | TanStack Query | Phase 1 install |
|
||||
| Node (local) | >= 20 | local có thể Node 22 |
|
||||
| Node (CI) | **pin 20.x** | bài học từ NamGroup |
|
||||
|
||||
## 5. Reference projects
|
||||
|
||||
- **NamGroup** (`D:\Dropbox\CONG_VIEC\NAMGROUP\SOURCECODE_CÔNG_TY\NAMGROUP\`) — template 2 FE + IIS deploy + permission matrix
|
||||
- **DH_Y_DUOC** (`D:\Dropbox\CONG_VIEC\DAI_Y_DUOC\DH_Y_DUOC_SOURCECODE\DH_Y_DUOC\`) — template backend hiện đại (Clean Arch + CQRS + EF migrations)
|
||||
|
||||
## 6. Roadmap 5 phase (summary)
|
||||
|
||||
| Phase | Tuần | Focus | Exit criteria |
|
||||
|---|---|---|---|
|
||||
| **0 Draft** | T1 | Scaffold, parse FORM + QUY_TRINH | Build pass, docs đủ |
|
||||
| **1 Alpha Core** | T2-4 | Auth + Permission + CRUD Supplier/Project/Contract | Admin tạo HĐ draft + gán role |
|
||||
| **2 Form Engine** | T5-6 | Template engine 8 form, export .docx/.xlsx | Export 1 HĐ giống mẫu 100% |
|
||||
| **3 Workflow** | T7-9 | State machine 9 phase + SLA + notify | HĐ đi hết quy trình → có mã số |
|
||||
| **4 Report + Polish** | T10-11 | Dashboard, Excel export, UX pass | UAT 5-10 HĐ thật |
|
||||
| **5 Production** | T12-13 | CI/CD IIS, security, runbook, UAT | Go-live |
|
||||
|
||||
Chi tiết atomic tasks ở [`changelog/migration-todos.md`](changelog/migration-todos.md).
|
||||
|
||||
## 7. Quy ước code
|
||||
|
||||
- **Ngôn ngữ UI:** 100% tiếng Việt. Code + comment + tên DB table: **English**.
|
||||
- **Tên file:** PascalCase cho `.cs`, kebab-case cho TS/TSX (trừ React component = PascalCase).
|
||||
- **Null safety:** `.NET` bật nullable reference types. TS `strict: true`.
|
||||
- **Async/await** cho mọi I/O.
|
||||
- **Error handling BE:** `GlobalExceptionMiddleware` → map exception → ProblemDetails. KHÔNG try-catch ở controller.
|
||||
- **Error handling FE:** error boundary + toast. Dùng `useErrorHandler` hook.
|
||||
- **Logging:** Serilog structured. Không `Console.WriteLine`.
|
||||
|
||||
## 8. Security baseline
|
||||
|
||||
- HTTPS everywhere (IIS redirect HTTP → HTTPS)
|
||||
- JWT expiry 1h, refresh token 7d
|
||||
- Password hash: Identity default (PBKDF2)
|
||||
- CORS: whitelist chỉ 2 FE origin
|
||||
- Rate limit: 100 req/min/IP cho endpoint auth
|
||||
- SQL injection: EF Core parameterized (không raw SQL trừ khi cần)
|
||||
- Audit log: mọi write → `AuditLog` table
|
||||
|
||||
## 9. Liên hệ
|
||||
|
||||
- **Dev:** pqhuy1987@gmail.com (solo)
|
||||
- **Domain expert:** TBD (sẽ update sau UAT đầu tiên)
|
||||
154
docs/PROJECT-MAP.md
Normal file
@ -0,0 +1,154 @@
|
||||
# PROJECT-MAP — Bản đồ toàn cảnh
|
||||
|
||||
> Đọc file này nếu cần deep context (~15 phút). Nếu chỉ cần snapshot → đọc [`STATUS.md`](STATUS.md).
|
||||
|
||||
## Module map
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ SOLUTION_ERP │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
|
||||
╔════════════════╗ ╔════════════════╗ ╔════════════════╗
|
||||
║ IDENTITY ║ ║ DANH MỤC ║ ║ QUẢN LÝ HĐ ║
|
||||
║ (Phase 1) ║ ║ (Phase 1) ║ ║ (Phase 1-3) ║
|
||||
╠════════════════╣ ╠════════════════╣ ╠════════════════╣
|
||||
║ User ║ ║ Supplier ║ ║ Contract ║
|
||||
║ Role ║ ║ Project ║ ║ ContractType ║
|
||||
║ Permission ║ ║ Department ║ ║ ContractForm ║
|
||||
║ MenuKey ║ ║ ContractClause ║ ║ Approval ║
|
||||
║ AuditLog ║ ║ (điều kiện ║ ║ Comment ║
|
||||
║ ║ ║ chung - 002.04)║ ║ Attachment ║
|
||||
╚════════════════╝ ╚════════════════╝ ║ AuditTrail ║
|
||||
╚════════════════╝
|
||||
|
||||
╔════════════════╗ ╔════════════════╗ ╔════════════════╗
|
||||
║ FORM ENGINE ║ ║ WORKFLOW ║ ║ BÁO CÁO ║
|
||||
║ (Phase 2) ║ ║ (Phase 3) ║ ║ (Phase 4) ║
|
||||
╠════════════════╣ ╠════════════════╣ ╠════════════════╣
|
||||
║ Template ║ ║ StateMachine ║ ║ Dashboard ║
|
||||
║ Renderer ║ ║ Transition ║ ║ ExcelExport ║
|
||||
║ (DOCX/XLSX) ║ ║ SlaTimer ║ ║ FilterQuery ║
|
||||
║ Field mapping ║ ║ Notification ║ ║ ║
|
||||
║ PO gen (F.07) ║ ║ CodeGenerator ║ ║ ║
|
||||
║ ║ ║ (RG-001) ║ ║ ║
|
||||
╚════════════════╝ ╚════════════════╝ ╚════════════════╝
|
||||
```
|
||||
|
||||
## Domain entities chính (dự kiến)
|
||||
|
||||
```
|
||||
User ────< Role ────< Permission (Role × MenuKey × CRUD)
|
||||
User ────< AuditLog
|
||||
|
||||
Supplier (NCC)
|
||||
Project (Dự án)
|
||||
Department (Phòng ban)
|
||||
|
||||
Contract
|
||||
├── Type: HĐTP | HĐGK | NCC | HĐDV | HĐ Mua bán | ...
|
||||
├── Phase (9 state — xem workflow-contract.md)
|
||||
├── Supplier, Project, Drafter
|
||||
├── MaHopDong (gen theo RG-001)
|
||||
├── Approvals[] (audit ai ký phase nào)
|
||||
├── Comments[] (thread góp ý Phase 3 của workflow)
|
||||
├── Attachments[] (scan bản gốc, file export)
|
||||
└── TemplateData (JSON — field đã điền khi render form)
|
||||
|
||||
ContractTemplate (ánh xạ Type → File mẫu FO-002.xx)
|
||||
ContractClause (điều khoản chung FO-002.04 — rich text)
|
||||
PurchaseOrder (có thể đính với Contract hoặc standalone)
|
||||
```
|
||||
|
||||
## API namespace dự kiến
|
||||
|
||||
```
|
||||
/api/auth — login, refresh, logout, register (admin gate)
|
||||
/api/users — CRUD user, assign role, reset password
|
||||
/api/roles — CRUD role, permission matrix
|
||||
/api/menus — menu tree + permission resolution
|
||||
|
||||
/api/suppliers — CRUD NCC
|
||||
/api/projects — CRUD dự án
|
||||
/api/departments — CRUD phòng ban
|
||||
|
||||
/api/contracts — CRUD + query by phase/project/supplier
|
||||
/api/contracts/{id}/transitions — state machine action
|
||||
/api/contracts/{id}/comments — thread góp ý
|
||||
/api/contracts/{id}/attachments — upload/download
|
||||
|
||||
/api/forms — template catalog
|
||||
/api/forms/{id}/render — render template → docx/xlsx (Phase 2)
|
||||
|
||||
/api/reports/dashboard — KPI tổng hợp
|
||||
/api/reports/export — Excel download
|
||||
```
|
||||
|
||||
## FE screens dự kiến
|
||||
|
||||
### fe-admin (:8082) — cho Admin + Role quản lý
|
||||
|
||||
- `/login`
|
||||
- `/dashboard` — KPI system
|
||||
- `/master/users` + `/master/roles` + `/master/permissions`
|
||||
- `/master/suppliers` + `/master/projects` + `/master/departments`
|
||||
- `/master/contract-templates` + `/master/contract-clauses`
|
||||
- `/contracts` — danh sách toàn bộ, filter phase/dự án
|
||||
- `/contracts/{id}` — detail + approval panel + audit log
|
||||
- `/reports` + `/system/audit-log`
|
||||
|
||||
### fe-user (:8080) — cho Drafter, TBP, PD/PM, BOD, CCM, ...
|
||||
|
||||
- `/login`
|
||||
- `/inbox` — HĐ đang chờ tôi xử lý (filter theo role × phase)
|
||||
- `/contracts/new` — chọn template + điền form + submit
|
||||
- `/contracts/{id}` — detail, comment, approve/reject
|
||||
- `/my-contracts` — HĐ tôi đã tạo/tham gia
|
||||
|
||||
## Flow chính (Phase 3 — trình ký HĐ end-to-end)
|
||||
|
||||
```
|
||||
Drafter (QS/NV.PB)
|
||||
├─ [POST /api/contracts] tạo draft + chọn template
|
||||
├─ [POST /api/forms/{id}/render] fill + preview
|
||||
├─ [POST /api/contracts/{id}/transitions] DangSoanThao → DangGopY
|
||||
│
|
||||
PD/PM/PRO/CCM/FIN/ACT (song song)
|
||||
└─ [POST /api/contracts/{id}/comments] góp ý
|
||||
|
||||
Drafter
|
||||
├─ [PATCH /api/contracts/{id}] revise
|
||||
├─ [POST /api/contracts/{id}/transitions] DangGopY → DangDamPhan → DangInKy
|
||||
│
|
||||
NTP/NCC/TĐ (external — Drafter update thay)
|
||||
└─ upload scan có chữ ký đối tác
|
||||
|
||||
Drafter
|
||||
└─ [POST /api/contracts/{id}/transitions] → DangKiemTraCCM
|
||||
|
||||
CCM
|
||||
└─ [POST /api/contracts/{id}/transitions] → DangTrinhKy
|
||||
|
||||
BOD/NĐUQ
|
||||
└─ [POST /api/contracts/{id}/transitions] → DangDongDau (GEN mã HĐ ở đây!)
|
||||
|
||||
HRA
|
||||
└─ [POST /api/contracts/{id}/transitions] → DaPhatHanh (upload scan có dấu)
|
||||
```
|
||||
|
||||
## External dependencies
|
||||
|
||||
- **SQL Server** — chính thức, dev có thể LocalDB hoặc Docker (`docker-compose.yml`)
|
||||
- **IIS** — deploy target (Windows Server 2019+)
|
||||
- **Gitea** — git remote (URL chờ user cấp)
|
||||
- **Aspose.Words / DocumentFormat.OpenXml** — render .docx (Phase 2, quyết định khi đó)
|
||||
- **EPPlus hoặc ClosedXML** — render .xlsx (Phase 2)
|
||||
|
||||
## Non-goals (KHÔNG làm)
|
||||
|
||||
- ❌ Python AI service (user đã quyết gác vô thời hạn)
|
||||
- ❌ Mobile app
|
||||
- ❌ Multi-tenant (1 instance / 1 công ty)
|
||||
- ❌ Tích hợp e-signature (Phase 6+ nếu có)
|
||||
- ❌ Tích hợp SAP/Bravo ERP (Phase 6+ nếu có)
|
||||
- ❌ Public API / external webhooks
|
||||
55
docs/STATUS.md
Normal file
@ -0,0 +1,55 @@
|
||||
# STATUS — Snapshot hiện tại
|
||||
|
||||
> **Update rule:** trước khi bắt đầu 1 task → ghi row vào `🔥 In Progress`. Xong → chuyển sang `✅ Recently Done`.
|
||||
|
||||
**Last updated:** 2026-04-21
|
||||
|
||||
## 📍 Phase hiện tại: **Phase 0 — Draft Scaffold** (in progress)
|
||||
|
||||
## 🔥 In Progress
|
||||
|
||||
| Ai | Task | Bắt đầu |
|
||||
|---|---|---|
|
||||
| Claude | Hoàn tất Phase 0 — docs + git init | 2026-04-21 |
|
||||
|
||||
## ✅ Recently Done (newest on top)
|
||||
|
||||
| Ngày | Ai | Task | Commit |
|
||||
|---|---|---|---|
|
||||
| 2026-04-21 | Claude | Parse QUY_TRINH → `workflow-contract.md` (9 phase state machine + role matrix) | — |
|
||||
| 2026-04-21 | Claude | Parse 8 FORM → `forms-spec.md` (catalog + RG-001 code format) | — |
|
||||
| 2026-04-21 | Claude | Scaffold 2 React + Vite apps (fe-admin :8082, fe-user :8080) + proxy config + Node engines pin | — |
|
||||
| 2026-04-21 | Claude | Scaffold .NET 10 solution + 4 project (Domain/Application/Infrastructure/Api) + references + packages | — |
|
||||
| 2026-04-21 | Claude | Tạo cấu trúc thư mục `SOLUTION_ERP/` + MEMORY.md | — |
|
||||
|
||||
## 🎯 Next up (Phase 0 còn lại)
|
||||
|
||||
- [ ] Tạo file root: `.gitignore`, `README.md`, `global.json`, `docker-compose.yml`
|
||||
- [ ] Tạo 3 skill folder placeholders (`contract-workflow`, `form-engine`, `permission-matrix`)
|
||||
- [ ] `git init` + commit đầu tiên `[CLAUDE] Scaffold: khởi tạo SOLUTION_ERP Phase 0`
|
||||
- [ ] Set Gitea remote (khi user cấp URL)
|
||||
|
||||
## 📋 Phase 1 — Alpha Core (sắp tới)
|
||||
|
||||
Xem chi tiết ở [`changelog/migration-todos.md`](changelog/migration-todos.md) section **Phase 1**.
|
||||
|
||||
Summary:
|
||||
1. Install Tailwind + shadcn/ui cho 2 FE + setup `BrandingProvider`
|
||||
2. Tạo `BaseEntity`, `AuditableEntity`, `ApplicationDbContext`, migration đầu tiên
|
||||
3. ASP.NET Identity + JWT endpoint (login, refresh, logout, seed admin)
|
||||
4. Permission Matrix (Role × MenuKey × CRUD) — copy pattern từ NamGroup skill
|
||||
5. CRUD Supplier + Project + Contract (draft only, chưa workflow)
|
||||
6. FE admin: login page + layout + permission guard + 3 CRUD page
|
||||
7. FE user: login + layout + list HĐ của tôi
|
||||
|
||||
## 🚨 Blockers / risks
|
||||
|
||||
- **Gitea remote** chưa có URL — chờ user cấp để push code
|
||||
- **3 file `.doc`** (FO-002.02, .03, .06) không parse được bằng python-docx — cần Word COM hoặc LibreOffice ở Phase 2
|
||||
- **Node 22 local vs 20 CI** — phải test CI sớm (Phase 5) để tránh surprise
|
||||
|
||||
## 📊 Thông số
|
||||
|
||||
- LOC: ~0 (scaffold chỉ)
|
||||
- Build time: ~10s (.NET)
|
||||
- Test coverage: 0% (chưa có test)
|
||||
164
docs/changelog/migration-todos.md
Normal file
@ -0,0 +1,164 @@
|
||||
# Migration To-dos — Atomic Roadmap
|
||||
|
||||
> Mỗi item là 1 task atomic (~2-8h work). Tick `[x]` khi xong. Link session log nếu có.
|
||||
|
||||
## Phase 0 — Draft Scaffold (T1)
|
||||
|
||||
- [x] Tạo cấu trúc thư mục `SOLUTION_ERP/`
|
||||
- [x] Scaffold .NET 10 solution `SolutionErp.slnx`
|
||||
- [x] Scaffold 4 project: `SolutionErp.{Domain, Application, Infrastructure, Api}`
|
||||
- [x] Wire Clean Arch references (Api → App/Infra, Infra → App, App → Domain)
|
||||
- [x] Install NuGet base: MediatR, FluentValidation, AutoMapper, EF Core SqlServer, Identity, JWT, Swagger, Serilog
|
||||
- [x] Scaffold 2 React + Vite apps `fe-admin` + `fe-user` với TS template
|
||||
- [x] Config vite.config.ts: port, strictPort, proxy `/api`, alias `@`
|
||||
- [x] Pin Node `>=20` trong package.json + `.nvmrc` cho CI
|
||||
- [x] Parse 8 form → `docs/forms-spec.md`
|
||||
- [x] Parse quy trình → `docs/workflow-contract.md`
|
||||
- [x] Viết `docs/{CLAUDE,STATUS,PROJECT-MAP}.md`
|
||||
- [ ] Viết `.gitignore`, `README.md`, `global.json`, `docker-compose.yml`
|
||||
- [ ] Tạo placeholder skill folders: `contract-workflow`, `form-engine`, `permission-matrix`
|
||||
- [ ] `git init` + commit đầu
|
||||
- [ ] Push Gitea remote (chờ URL từ user)
|
||||
|
||||
## Phase 1 — Alpha Core (T2-4)
|
||||
|
||||
### Backend foundation
|
||||
|
||||
- [ ] `Domain/BaseEntity.cs` (Id, CreatedAt, UpdatedAt, CreatedBy, UpdatedBy)
|
||||
- [ ] `Domain/AuditableEntity : BaseEntity` (IsDeleted, DeletedAt, DeletedBy)
|
||||
- [ ] `Domain/ValueObjects/ContractCode.cs` (wrap string theo format RG-001)
|
||||
- [ ] `Domain/Enums/ContractType.cs`, `ContractPhase.cs`, `ApprovalDecision.cs`
|
||||
- [ ] `Application/Common/IApplicationDbContext.cs` interface
|
||||
- [ ] `Application/Common/IDateTime.cs`, `ICurrentUser.cs`
|
||||
- [ ] `Application/DependencyInjection.cs` — register MediatR, FluentValidation, AutoMapper
|
||||
- [ ] `Infrastructure/Persistence/ApplicationDbContext.cs` : `IdentityDbContext<User, Role, Guid>`, `IApplicationDbContext`
|
||||
- [ ] Configurations per entity qua `IEntityTypeConfiguration<T>`
|
||||
- [ ] `Infrastructure/DependencyInjection.cs` — register DbContext, Identity, services
|
||||
- [ ] `Api/Program.cs` setup: services, Serilog, auth, Swagger, CORS, middleware
|
||||
- [ ] `Api/Middleware/GlobalExceptionMiddleware.cs`
|
||||
|
||||
### Auth + Identity
|
||||
|
||||
- [ ] `Domain/Entities/User : IdentityUser<Guid>`, `Role : IdentityRole<Guid>`
|
||||
- [ ] Migration 1: `Init` (Identity tables)
|
||||
- [ ] `Application/Auth/Commands/LoginCommand` + handler + validator
|
||||
- [ ] `Application/Auth/Commands/RefreshTokenCommand`
|
||||
- [ ] `Api/Controllers/AuthController` (login, refresh, logout, me)
|
||||
- [ ] JWT config: issuer, audience, key, expiry 1h + refresh 7d
|
||||
- [ ] Seed admin: `admin@solutionerp.local` / `Admin@123456`
|
||||
- [ ] Test login → get token → call `/me` OK
|
||||
|
||||
### Permission Matrix
|
||||
|
||||
- [ ] `Domain/Entities/MenuItem` (Key, Label, ParentKey, Order, Icon)
|
||||
- [ ] `Domain/Entities/Permission` (RoleId, MenuKey, CanRead, CanCreate, CanUpdate, CanDelete)
|
||||
- [ ] Seed default menu tree (based on FE screens list)
|
||||
- [ ] `Application/Permissions/Queries/GetMyMenuTree` — resolve per-user
|
||||
- [ ] `Api/Controllers/MenusController` + `RolesController` + `PermissionsController`
|
||||
- [ ] Admin UI: Permission Matrix grid (role × menu × CRUD checkbox)
|
||||
|
||||
### CRUD master data
|
||||
|
||||
- [ ] `Domain/Entities/Supplier` (Code, Name, TaxCode, Phone, Email, Address, Type: NCC/NTP/TĐ/ĐVDV)
|
||||
- [ ] `Domain/Entities/Project` (Code, Name, StartDate, EndDate, Manager)
|
||||
- [ ] `Domain/Entities/Department` (Code, Name, Manager)
|
||||
- [ ] CQRS + Controller + Migration cho 3 entity
|
||||
- [ ] FE admin 3 trang CRUD (list, create, edit, delete confirm)
|
||||
- [ ] Pagination, search, sort server-side
|
||||
|
||||
### Contract draft (chưa workflow — chỉ CRUD)
|
||||
|
||||
- [ ] `Domain/Entities/Contract` (skeleton: Id, Type, SupplierId, ProjectId, Phase=DangChon, DraftData)
|
||||
- [ ] API create/update/list/delete draft
|
||||
- [ ] FE admin: list contracts + filter
|
||||
- [ ] FE user: "HĐ của tôi" list
|
||||
|
||||
### FE setup
|
||||
|
||||
- [ ] Install Tailwind CSS cho 2 app + config content paths
|
||||
- [ ] Install shadcn/ui CLI, init 2 app
|
||||
- [ ] Install: `@tanstack/react-query`, `react-router-dom`, `axios`, `lucide-react`, `sonner`
|
||||
- [ ] `src/lib/api.ts` — axios instance + interceptor JWT
|
||||
- [ ] `src/contexts/AuthContext.tsx` — token từ localStorage
|
||||
- [ ] `src/components/PermissionGuard.tsx` + `usePermission()` hook
|
||||
- [ ] Layout shell: sidebar + header + content
|
||||
- [ ] Route với protected route + role guard
|
||||
- [ ] Toast notifications (sonner)
|
||||
|
||||
### Exit criteria Phase 1
|
||||
|
||||
- [ ] Admin login → tạo NCC/Project → tạo role "Nhân viên CCM" → gán permission menu "Contracts.Read"
|
||||
- [ ] User CCM login → thấy menu Contracts, không thấy menu Admin
|
||||
- [ ] Tạo Contract draft → list hiển thị, không bị 403 sai
|
||||
|
||||
## Phase 2 — Form Engine (T5-6)
|
||||
|
||||
- [ ] Khảo sát: OpenXml vs Aspose.Words — chọn 1 (Aspose có license phí; OpenXml free nhưng verbose)
|
||||
- [ ] Convert 3 file `.doc` → `.docx` (COM automation PowerShell hoặc LibreOffice headless)
|
||||
- [ ] Parse chi tiết field của 5 template HĐ — mỗi form thành JSON spec
|
||||
- [ ] `Domain/Entities/ContractTemplate` (Id, FormCode, Name, TemplateFile path, FieldSpec JSON)
|
||||
- [ ] `Application/Forms/Services/IFormRenderer` — input: template + data dict → output: byte[] (.docx)
|
||||
- [ ] Implement `DocxRenderer` (OpenXml-based replace placeholder)
|
||||
- [ ] Implement `XlsxRenderer` cho FO-002.07 (dùng EPPlus/ClosedXML)
|
||||
- [ ] `Api/Controllers/FormsController` — GET /templates, POST /render
|
||||
- [ ] FE user: form builder — chọn template → dynamic form → preview → export
|
||||
- [ ] FE admin: upload template mới, edit field mapping
|
||||
- [ ] Lưu `ContractClause` (FO-002.04) dạng rich text, admin edit qua TipTap/TinyMCE
|
||||
- [ ] Import/export template (để backup)
|
||||
- [ ] Test: 1 HĐ Giao khoán filled → export .docx mở bằng Word y hệt mẫu
|
||||
|
||||
## Phase 3 — Workflow State Machine (T7-9)
|
||||
|
||||
- [ ] `Domain/Entities/ContractApproval` + `ContractComment` + `ContractAttachment`
|
||||
- [ ] `Domain/Entities/Contract` update: thêm `Phase`, `SlaDeadline`, `BypassProcurementAndCCM`
|
||||
- [ ] `Domain/Services/IContractWorkflowService.TransitionAsync(...)` — state guard + role guard + side effects
|
||||
- [ ] `Infrastructure/Services/ContractCodeGenerator` (implement RG-001) với locking cho seq
|
||||
- [ ] `Infrastructure/HostedServices/SlaExpiryJob` — check mỗi 15min, auto-approve quá hạn
|
||||
- [ ] `Infrastructure/Services/NotificationService` — email (MailKit) + in-app (SignalR optional)
|
||||
- [ ] MediatR behavior: `AuditBehavior` — log mọi command
|
||||
- [ ] API: `POST /api/contracts/{id}/transitions` body: `{targetPhase, comment}`
|
||||
- [ ] FE user Inbox: list "HĐ chờ tôi xử lý" (query by current phase + user role)
|
||||
- [ ] FE Contract detail page: timeline 9 phase, approval panel, comment thread
|
||||
- [ ] Upload attachment (scan có chữ ký đối tác)
|
||||
- [ ] Notification UI: badge count, dropdown, click → detail
|
||||
- [ ] E2E test: happy path end-to-end 1 HĐ qua 9 phase
|
||||
- [ ] E2E test: reject → quay về DangSoanThao
|
||||
- [ ] E2E test: SLA expired → auto-approve + log
|
||||
|
||||
## Phase 4 — Reporting + Polish (T10-11)
|
||||
|
||||
- [ ] Dashboard admin: số HĐ theo phase, top NCC, top dự án, tổng giá trị theo tháng
|
||||
- [ ] Excel export theo bộ lọc (dùng EPPlus)
|
||||
- [ ] Report: HĐ quá hạn SLA bao nhiêu lần theo phase/role
|
||||
- [ ] UX polish: skeleton loader, empty state, error boundary có recovery button
|
||||
- [ ] Accessibility: keyboard nav, focus trap modal, aria labels
|
||||
- [ ] Dark mode (optional, nếu rảnh)
|
||||
- [ ] Performance: index DB cho query hot (SupplierId, ProjectId, Phase)
|
||||
- [ ] Tài liệu user guide: quy trình tạo HĐ + duyệt
|
||||
- [ ] UAT với 5-10 HĐ dữ liệu thật từ bộ phận Cung ứng
|
||||
|
||||
## Phase 5 — Production (T12-13)
|
||||
|
||||
- [ ] `docs/guides/cicd.md` — CI/CD runbook
|
||||
- [ ] Gitea Actions workflow `.gitea/workflows/deploy.yml` — build .NET + 2 FE, deploy IIS qua SSH/WinRM
|
||||
- [ ] Pin Node 20.x trong workflow, test CI sớm (không để surprise cuối dự án)
|
||||
- [ ] `scripts/deploy-iis.ps1` — stop app pool, xcopy, start app pool
|
||||
- [ ] Windows Server setup: IIS + URL Rewrite + ARR (reverse proxy FE → IIS)
|
||||
- [ ] SQL Server prod: backup plan daily + weekly full
|
||||
- [ ] HTTPS certificate (Let's Encrypt qua win-acme hoặc mua cert)
|
||||
- [ ] `appsettings.Production.json` + user secrets
|
||||
- [ ] Security audit: owasp top 10 check
|
||||
- [ ] Rate limiting middleware (AspNetCoreRateLimit hoặc built-in)
|
||||
- [ ] Health check endpoint `/health` cho IIS probe
|
||||
- [ ] Error tracking: Serilog → file rolling daily, retention 30 ngày
|
||||
- [ ] Runbook: restart app, rollback migration, restore backup
|
||||
- [ ] UAT production 1 tuần với 2-3 user thật
|
||||
- [ ] Go-live checklist: backup, rollback plan, on-call contact
|
||||
|
||||
## Post-launch (Phase 6+ — future)
|
||||
|
||||
- [ ] E-signature integration (VNPT CA hoặc FPT CA)
|
||||
- [ ] Tích hợp Bravo / SAP ERP import NCC
|
||||
- [ ] Mobile app (React Native?) cho BOD duyệt ngoài giờ
|
||||
- [ ] AI: gợi ý điền form dựa HĐ cũ, OCR scan HĐ đối tác
|
||||
- [ ] Multi-tenant nếu có công ty thứ 2
|
||||
657
docs/forms-spec-raw.md
Normal file
108
docs/forms-spec.md
Normal file
@ -0,0 +1,108 @@
|
||||
# Forms Specification — SOLUTION_ERP
|
||||
|
||||
> **Nguồn:** `D:\Dropbox\CONG_VIEC\SOLUTION\FORM\` (8 file)
|
||||
> **Raw dump:** [`forms-spec-raw.md`](forms-spec-raw.md) — text gốc extract từ docx/xlsx
|
||||
> **Phase 2 deliverable:** parse từng form → template engine (điền field tự động → export .docx/.xlsx giống 100% mẫu)
|
||||
|
||||
## Danh mục 8 form
|
||||
|
||||
| Mã form | Tên | Loại file | Mục đích | Trạng thái parse |
|
||||
|---|---|---|---|---|
|
||||
| **SOL-CCM-FO-002.01** | Bảng kiểm tra hợp đồng | `.docx` | Checklist 4 bộ phận (Đề xuất / Cung ứng / CCM / Giám đốc) review HĐ trước ký | ✅ Parsed — table 40×13 |
|
||||
| **SOL-CCM-FO-002.02** | Hợp đồng trọn gói (nhân công + vật tư) | `.doc` | Template HĐ thầu phụ trọn gói cung cấp cả NC + VT | ⚠️ Cần convert `.doc` → `.docx` (COM/LibreOffice) ở Phase 2 |
|
||||
| **SOL-CCM-FO-002.03** | Hợp đồng trọn gói (nhân công + thiết bị) | `.doc` | Template HĐ thầu phụ trọn gói cung cấp NC + TB | ⚠️ Cần convert `.doc` → `.docx` ở Phase 2 |
|
||||
| **SOL-CCM-FO-002.04** | Điều kiện chung hợp đồng trọn gói | `.docx` | Phần điều khoản chung (5+ điều: am hiểu TL, nội dung CV, tiến độ, xử lý tranh chấp, sửa đổi HĐ) | ✅ Parsed — narrative text |
|
||||
| **SOL-CCM-FO-002.05** | Hợp đồng Giao khoán | `.docx` | Template HĐ giao khoán NC/Tổ đội — 10 table (thông tin, giá trị, hạng mục, nghiệm thu, thanh toán) | ✅ Parsed — 10 tables |
|
||||
| **SOL-CCM-FO-002.06** | Hợp đồng mua bán | `.doc` | Template HĐ mua bán (NCC cấp vật tư, thiết bị) | ⚠️ Cần convert `.doc` → `.docx` ở Phase 2 |
|
||||
| **SOL-CCM-FO-002.07** | Đơn đặt hàng (PO) | `.xlsx` | 3 sheet: `(F.01)_SS GIA` (so sánh giá NCC), `PO` (đơn đặt hàng chính), `Po01 NLT (sua)` (PO nguyên liệu) | ✅ Parsed — 3 sheets |
|
||||
| **SOL-CCM-RG-001** | Quy định mã số hợp đồng | `.docx` | Regex + convention đánh mã HĐ và PO (bảng ví dụ) | ✅ Parsed |
|
||||
|
||||
---
|
||||
|
||||
## Detail — các form đã parse được
|
||||
|
||||
### SOL-CCM-FO-002.01 — Bảng kiểm tra hợp đồng
|
||||
|
||||
**Nhóm field thông tin HĐ (phần trên bảng):**
|
||||
- `phong_ban_du_an` (Phòng ban / Dự án) — string
|
||||
- `loai_hop_dong` — enum: `ChuDauTu` | `NCC` | `ThauPhu` | `ToDoi` | `ThietBiThue` | `Khac`
|
||||
- `ten_doi_tac` — string
|
||||
- `noi_dung_hop_dong` — text
|
||||
|
||||
**Nhóm field checklist theo bộ phận (mỗi row = 1 bộ phận × nội dung × ý kiến):**
|
||||
|
||||
| Bộ phận | Nội dung kiểm tra (check) | Field dữ liệu |
|
||||
|---|---|---|
|
||||
| PHÒNG BAN/DỰ ÁN (Đề xuất) | Soạn thảo (✓), Nội dung HĐ | `de_xuat_soan_thao: bool`, `de_xuat_noi_dung_ok: bool`, `de_xuat_ho_ten`, `de_xuat_ngay`, `de_xuat_y_kien: text` |
|
||||
| PHÒNG CUNG ỨNG | Điều khoản HĐ, Điều khoản t/toán, Rủi ro pháp lý HĐ | `cung_ung_dieu_khoan: bool`, `cung_ung_thanh_toan: bool`, `cung_ung_rui_ro: bool`, `cung_ung_ho_ten`, `cung_ung_ngay`, `cung_ung_y_kien: text` |
|
||||
| PHÒNG CCM | G/trị HĐ so với NS, Rủi ro pháp lý HĐ | `ccm_gia_tri_so_ns: bool`, `ccm_rui_ro: bool`, `ccm_ho_ten`, `ccm_ngay`, `ccm_y_kien: text` |
|
||||
| GIÁM ĐỐC (hoặc ủy quyền) | Ký kết, phát hành | `gd_ky_ket: bool`, `gd_ho_ten`, `gd_ngay`, `gd_y_kien: text` |
|
||||
|
||||
**Business rule (từ ghi chú cuối bảng):**
|
||||
- HĐ với Chủ đầu tư → Đơn vị đề xuất có thể **bypass** Cung ứng + CCM, chuyển thẳng Giám đốc.
|
||||
- Các loại khác → PHẢI qua Cung ứng + CCM trước.
|
||||
- Mỗi bộ phận **01 ngày** xử lý. Quá 1 ngày không phản hồi → **auto approve** ("xem như đã thông qua").
|
||||
- Nếu cần kéo dài → phải ghi chú vào "Ý kiến" + thông báo cho Đề xuất.
|
||||
|
||||
→ **Mapping sang workflow state:** file này chính là thể hiện UI của workflow bên `workflow-contract.md`.
|
||||
|
||||
### SOL-CCM-FO-002.04 — Điều kiện chung
|
||||
|
||||
Pure narrative template — 5+ điều khoản cố định (AM HIỂU TÀI LIỆU, NỘI DUNG CV, TIẾN ĐỘ, XỬ LÝ TRANH CHẤP, SỬA ĐỔI HĐ). Không có field động — dùng làm **appendix cố định đính kèm** cho HĐ trọn gói.
|
||||
|
||||
→ **Data model:** lưu dạng `ContractClauseTemplate` với `Content: text (markdown)`, version theo đợt cập nhật. Cho phép admin edit qua rich text editor.
|
||||
|
||||
### SOL-CCM-FO-002.05 — Hợp đồng Giao khoán
|
||||
|
||||
10 bảng. Các nhóm field chính (suy từ số row/col):
|
||||
- Thông tin 2 bên (Bên A + Bên B) — 4 row × 2 col
|
||||
- Giá trị HĐ + hạng mục công việc — 8 row × 8 col (chi tiết hạng mục, đơn giá, khối lượng, thành tiền)
|
||||
- Nghiệm thu + thanh toán — 6 row × 4 col
|
||||
- Bảng tiến độ — 9 row × 11 col
|
||||
- Ký tên 2 bên — 1 row × 2 col
|
||||
|
||||
→ **Phase 2:** parse từng table chi tiết sau khi có cấu trúc DB ổn định.
|
||||
|
||||
### SOL-CCM-FO-002.07 — Đơn đặt hàng (xlsx)
|
||||
|
||||
| Sheet | Rows × Cols | Mục đích |
|
||||
|---|---|---|
|
||||
| `(F.01)_SS GIA` | 84 × 36 | So sánh giá nhiều NCC cho cùng mặt hàng (chọn NCC tốt nhất) |
|
||||
| `PO` | 80 × 17 | Đơn đặt hàng chính — template chuẩn |
|
||||
| `Po01 NLT (sua)` | 74 × 12 | Variant PO cho NLT (nguyên liệu?) |
|
||||
|
||||
→ **Phase 2:** implement xuất file Excel đúng format — dùng **EPPlus** hoặc **ClosedXML** thay vì OpenXml thủ công (giữ được style, formula, merged cells).
|
||||
|
||||
### SOL-CCM-RG-001 — Quy định mã số hợp đồng ⭐ (critical — dùng ở Phase 3)
|
||||
|
||||
**Contract code format:**
|
||||
|
||||
| Loại HĐ | Format | Ví dụ |
|
||||
|---|---|---|
|
||||
| HĐ Thầu phụ (TP) | `{ProjectCode}/HĐTP/SOL&{PartnerAbbr}/{Seq}` | `FLOCK 01/HĐTP/SOL&HD/01` |
|
||||
| HĐ Giao khoán / Tổ đội / Nhân công cơ hữu | `{ProjectCode}/HĐGK/SOL&{PartnerAbbr}/{Seq}` | `FLOCK 01/HĐGK/SOL&PVL/01` |
|
||||
| HĐ NCC (cấp riêng từng dự án) | `{ProjectCode}/NCC/SOL&{PartnerAbbr}/{Seq}` | `FLOCK 01/NCC/SOL&CC1/01` |
|
||||
| HĐ nguyên tắc NCC | `{Year}/NCC/SOL&{PartnerAbbr}/{Seq}` | `2026/NCC/SOL&AKATI/01` |
|
||||
| HĐ Dịch vụ (cấp riêng) | `{ProjectCode}/HĐDV/SOL&{PartnerAbbr}/{Seq}` | `FLOCK 01/HĐDV/SOL&KG/01` |
|
||||
| HĐ nguyên tắc Dịch vụ | `{Year}/HĐDV/SOL&{PartnerAbbr}/{Seq}` | `2026/HĐDV/SOL&TTL/01` |
|
||||
|
||||
**PO code format:**
|
||||
|
||||
| Loại PO | Format | Ví dụ |
|
||||
|---|---|---|
|
||||
| PO vật tư, thiết bị (theo HĐ Nguyên tắc) | `{ProjectCode}/{PartnerAbbr}/PO {Seq}` | `FLOCK 01/TRUNGDUNG/PO 01` |
|
||||
| PO với TP/NCC/ĐVDV (không theo HĐ Nguyên tắc) | `{ProjectCode}/PO/SOL&{PartnerAbbr}/{Seq}` | `FLOCK 01/PO/SOL&PVL/01` |
|
||||
|
||||
**Seq** bắt đầu từ `01`, tăng dần theo dự án + loại HĐ + đối tác.
|
||||
|
||||
→ **Domain service Phase 3:** `IContractCodeGenerator.Generate(project, type, partner) → string` — transactional để tránh race condition khi gen Seq.
|
||||
|
||||
---
|
||||
|
||||
## TODO Phase 2 (form engine)
|
||||
|
||||
- [ ] Convert 3 file `.doc` (FO-002.02, 002.03, 002.06) sang `.docx` qua COM automation hoặc LibreOffice headless
|
||||
- [ ] Parse chi tiết field của 5 file template HĐ (002.02, .03, .04, .05, .06) — mỗi form thành 1 spec JSON
|
||||
- [ ] Xây template engine: `Template + data → output .docx/.xlsx` (dùng **DocumentFormat.OpenXml** hoặc **Aspose.Words** + **EPPlus**)
|
||||
- [ ] UI form builder cho admin: gán template ↔ loại HĐ ↔ field layout
|
||||
- [ ] Preview PDF trước khi export final
|
||||
156
docs/workflow-contract.md
Normal file
@ -0,0 +1,156 @@
|
||||
# Workflow — Quy trình trình ký Hợp đồng TP/NCC/Tổ đội
|
||||
|
||||
> **Nguồn:** `QUY_TRINH/QT TRINH KY HOP DONG TP-NCC.docx`
|
||||
> **Raw dump:** [`workflow-raw.md`](workflow-raw.md)
|
||||
> **Phase 3 deliverable:** Implement state machine + role guard + SLA timer + notification
|
||||
|
||||
## 1. Phạm vi
|
||||
|
||||
Áp dụng cho Hợp đồng/Phụ lục HĐ ký với: **Thầu phụ (NTP)** · **Nhà cung cấp (NCC)** · **Tổ đội (TĐ)**. Tham chiếu ISO 9001.
|
||||
|
||||
## 2. Glossary (viết tắt)
|
||||
|
||||
| Mã | Nghĩa | Role trong system |
|
||||
|---|---|---|
|
||||
| **BOD** | Ban Giám đốc | `Director` |
|
||||
| **NĐUQ** | Người được ủy quyền | `AuthorizedSigner` |
|
||||
| **CCM** | Phòng Kiểm soát Chi phí | `CostControl` |
|
||||
| **PRO** | Phòng Cung ứng | `Procurement` |
|
||||
| **FIN** | Phòng Tài chính | `Finance` |
|
||||
| **ACT** | Phòng Kế toán | `Accounting` |
|
||||
| **EQU** | Phòng Thiết bị | `Equipment` |
|
||||
| **HRA** | Nhân sự - Hành chính (đóng dấu) | `HR_Admin` |
|
||||
| **PB** | Phòng ban | `Department` |
|
||||
| **BCH CT** | Ban chỉ huy công trường | `SiteCommand` |
|
||||
| **PD** | Giám đốc Thi công | `ProjectDirector` |
|
||||
| **PM** | Giám đốc Dự án | `ProjectManager` |
|
||||
| **TPB** | Trưởng Phòng ban | `DeptManager` |
|
||||
| **TBP** | Trưởng Bộ phận | `SectionLeader` |
|
||||
| **QS/NV.PB** | QS công trường / Nhân viên PB | `Drafter` (người soạn) |
|
||||
| **NTP/NCC/TĐ** | Đối tác ký HĐ | Partner (external — không login) |
|
||||
|
||||
## 3. State Machine (9 phase)
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> DangChon: Tạo mới
|
||||
DangChon --> DangSoanThao: Chọn NCC xong (chuyển Drafter)
|
||||
DangSoanThao --> DangGopY: Gửi email góp ý (7d)
|
||||
DangGopY --> DangDamPhan: Nhận xong comment (7d)
|
||||
DangDamPhan --> DangInKy: Thỏa thuận xong (7d)
|
||||
DangInKy --> DangKiemTraCCM: Đã ký nháy QS/PD/TPB, chuyển CCM (1d)
|
||||
DangKiemTraCCM --> DangTrinhKy: CCM ký nháy xong (3d)
|
||||
DangTrinhKy --> DangDongDau: BOD/NĐUQ ký duyệt (1d)
|
||||
DangDongDau --> DaPhatHanh: HRA đóng dấu xong
|
||||
DaPhatHanh --> [*]: PB scan + gửi bản gốc + lưu Filing
|
||||
|
||||
DangSoanThao --> TuChoi: Cancel
|
||||
DangGopY --> DangSoanThao: Revise
|
||||
DangKiemTraCCM --> DangSoanThao: CCM reject
|
||||
DangTrinhKy --> DangSoanThao: BOD reject
|
||||
TuChoi --> [*]
|
||||
```
|
||||
|
||||
## 4. Chi tiết từng phase
|
||||
|
||||
| # | Phase (state) | Role thực hiện | Input | Output | SLA | Form liên quan |
|
||||
|---|---|---|---|---|---|---|
|
||||
| 1 | `DangChon` — Lựa chọn NTP/NCC | `PB` / `BCH CT` | Yêu cầu công việc | Chốt đối tác | — | (theo Quy trình Cung ứng SOL-PRO-SP-001) |
|
||||
| 2 | `DangSoanThao` — Soạn thảo HĐ | `Drafter` (QS/NV.PB) + `TBP`/`TPB` check | Template HĐ | Dự thảo HĐ | **7 ngày** | FO-002.02/.03/.04/.05/.06 (chọn loại phù hợp) |
|
||||
| 3 | `DangGopY` — Góp ý nội dung | `PD`/`PM`/`PRO`/`CCM`/`FIN`/`ACT`/`EQU` | Dự thảo | Comment (email → hệ thống) | **7 ngày** | (đính kèm comment thread) |
|
||||
| 4 | `DangDamPhan` — Đàm phán | `Drafter` + `TBP`/`TPB`/`PD`/`PM` | Comment | Bản cuối thỏa thuận | **7 ngày** | — |
|
||||
| 5 | `DangInKy` — In + đối tác ký | `Drafter` + **NTP/NCC/TĐ** (external) + `PD`/`PM`/`TPB` ký nháy | Bản cuối | 2 mặt, có chữ ký đối tác + ký nháy nội bộ | **1 ngày** | FO-002.01 (cover approval) |
|
||||
| 6 | `DangKiemTraCCM` — CCM kiểm tra | `CCM` (`TP.CCM` ký nháy) | HĐ đã ký nháy | HĐ CCM approved | **3 ngày** | FO-002.01 |
|
||||
| 7 | `DangTrinhKy` — BOD ký duyệt | `BOD` hoặc `NĐUQ` | HĐ CCM approved | HĐ ký duyệt | **1 ngày** | FO-002.01 |
|
||||
| 8 | `DangDongDau` — Đóng dấu | `HRA`/`ISO` | HĐ đã ký | HĐ có dấu | — | — |
|
||||
| 9 | `DaPhatHanh` — Phát hành + lưu trữ | `PB`/`BCH CT` + `CCM` | HĐ có dấu | Scan + bản gốc gửi NCC + lưu Filing System | — | — |
|
||||
|
||||
**Tổng SLA:** ~19 ngày (phase 2-7) cho 1 HĐ hoàn chỉnh.
|
||||
|
||||
## 5. Role × Phase Matrix (quyền xem/thao tác)
|
||||
|
||||
Ký hiệu: `R` = read, `W` = write/update draft, `A` = approve (chuyển phase tiếp), `-` = không có quyền.
|
||||
|
||||
| Role \ Phase | 1.Chọn | 2.Soạn | 3.GópÝ | 4.ĐàmPhán | 5.InKý | 6.CCMCheck | 7.BODKý | 8.ĐóngDấu | 9.PhátHành |
|
||||
|---|---|---|---|---|---|---|---|---|---|
|
||||
| **Drafter** (QS/NV.PB) | R | W,A | R | W,A | W,A | R | R | R | W |
|
||||
| **TBP/TPB** | R | R,A | R,A | R,A | R,A | R | R | R | R |
|
||||
| **PD/PM** | R | R | W,A | R,A | R,A | R | R | R | R |
|
||||
| **PRO/EQU/FIN/ACT** | A | R | W,A | R | - | - | - | - | R |
|
||||
| **CCM** | - | R | W,A | R | R | W,A | R | R | W |
|
||||
| **BOD/NĐUQ** | - | - | - | - | - | - | A | - | R |
|
||||
| **HRA** | - | - | - | - | - | - | - | W,A | R |
|
||||
| **Admin** (system) | R | R | R | R | R | R | R | R | R (+ override) |
|
||||
|
||||
**Guard rule:**
|
||||
- Đặc biệt: HĐ với **Chủ đầu tư** → có thể **bypass** phase 3 + 6 (không cần PRO/CCM góp ý + kiểm tra). Cờ `BypassProcurementAndCCM: bool` ở entity HĐ.
|
||||
- Quá SLA mỗi phase không action → **auto-approve** (chuyển phase tiếp). Gửi notification warning khi còn 2h.
|
||||
- Nếu cần kéo dài → bộ phận kiểm tra phải comment vào "Ý kiến" trước khi SLA hết.
|
||||
|
||||
## 6. Notification triggers
|
||||
|
||||
| Event | Người nhận | Kênh |
|
||||
|---|---|---|
|
||||
| Chuyển `DangSoanThao` → `DangGopY` | Tất cả role góp ý (PD/PM/PRO/CCM/FIN/ACT) | email + in-app |
|
||||
| Chuyển `DangKiemTraCCM` | `CCM` | email + in-app |
|
||||
| Chuyển `DangTrinhKy` | `BOD` + `NĐUQ` | email + in-app (high priority) |
|
||||
| Quá SLA 80% thời gian | Role đang giữ phase | in-app warning |
|
||||
| Quá SLA → auto-approve | Drafter + role giữ phase | email + in-app (log audit) |
|
||||
| Reject (quay về `DangSoanThao`) | Drafter | email + in-app |
|
||||
|
||||
## 7. Data model implication (cho Phase 3)
|
||||
|
||||
```csharp
|
||||
// Domain
|
||||
public enum ContractPhase {
|
||||
DangChon = 1,
|
||||
DangSoanThao,
|
||||
DangGopY,
|
||||
DangDamPhan,
|
||||
DangInKy,
|
||||
DangKiemTraCCM,
|
||||
DangTrinhKy,
|
||||
DangDongDau,
|
||||
DaPhatHanh,
|
||||
TuChoi
|
||||
}
|
||||
|
||||
public class Contract : AuditableEntity {
|
||||
public Guid Id { get; set; }
|
||||
public string MaHopDong { get; set; } // tự gen theo RG-001
|
||||
public ContractType Type { get; set; } // HDTP, HDGK, NCC, HDDV...
|
||||
public ContractPhase Phase { get; set; }
|
||||
public Guid SupplierId { get; set; }
|
||||
public Guid ProjectId { get; set; }
|
||||
public decimal GiaTri { get; set; }
|
||||
public bool BypassProcurementAndCCM { get; set; }
|
||||
public DateTime? SlaDeadline { get; set; } // khi nào phase hiện tại hết hạn
|
||||
// ...
|
||||
public List<ContractComment> Comments { get; set; } // thread góp ý phase 3
|
||||
public List<ContractApproval> Approvals { get; set; } // ai ký phase nào, lúc nào
|
||||
public List<ContractAttachment> Attachments { get; set; } // scan bản gốc, file export
|
||||
}
|
||||
|
||||
public class ContractApproval {
|
||||
public Guid ContractId { get; set; }
|
||||
public ContractPhase Phase { get; set; }
|
||||
public Guid ApproverUserId { get; set; }
|
||||
public DateTime? ApprovedAt { get; set; }
|
||||
public ApprovalDecision Decision { get; set; } // Approve | Reject | AutoApprove
|
||||
public string? Comment { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
**Service chính:**
|
||||
- `IContractWorkflowService.TransitionAsync(contractId, targetPhase, userId, comment)` — check guard + update state + tạo approval + notify
|
||||
- `IContractCodeGenerator.GenerateAsync(projectId, type, supplierId)` — dùng SEMAPHORE/transaction tránh race condition
|
||||
- `ISlaExpiryJob` — hosted service chạy mỗi 15 phút, auto-approve các HĐ quá hạn
|
||||
|
||||
## 8. Business rules summary
|
||||
|
||||
1. **Một role chỉ có 1 phase active tại 1 thời điểm** cho 1 HĐ.
|
||||
2. **Auto-approve nếu quá SLA** — nhưng phải log `Decision=AutoApprove` rõ ràng trong `ContractApproval`.
|
||||
3. **Reject → quay về `DangSoanThao`** — Drafter nhận lại, toàn bộ approval trước đó bị invalidate (kept as history).
|
||||
4. **Không cho xóa HĐ** đã qua phase 5 (`DangInKy`) — chỉ soft delete.
|
||||
5. **Mã HĐ** gen theo `forms-spec.md § RG-001` — chỉ gen khi transition sang phase 5.
|
||||
6. **Audit log đầy đủ** — mọi transition đều ghi `AuditLog(entityId, action, oldPhase, newPhase, userId, timestamp, diff)`.
|
||||
63
docs/workflow-raw.md
Normal file
@ -0,0 +1,63 @@
|
||||
MỤC ĐÍCH/PURPOSE:
|
||||
Nhằm thống nhất các bước thực hiện trình ký Hợp đồng Thầu phụ/Nhà cung cấp/Tổ đội.
|
||||
To unify the steps in the process of signing Subcontractor/Supplier/Team Contract.
|
||||
Đảm bảo sự tuân thủ và vận hành Hệ thống một cách đồng nhất trên tất cả các Phòng Ban/Dự án của Công ty TNHH Xây dựng Solutions.
|
||||
Ensure compliance and consistent of system operation through all Departments and Projects of Solutions Construction Co., Ltd.
|
||||
PHẠM VI VÀ ĐỐI TƯỢNG ÁP DỤNG/SCOPE AND OBJECTS OF APPLICATION:
|
||||
Áp dụng đối với các hợp đồng/phụ lục hợp đồng Thầu phụ/Nhà cung cấp/Tổ đội tại các Phòng ban/Dự án của Công ty TNHH Xây dựng Solutions như sau:
|
||||
This procedure shall apply to Subcontractor/Supplier/Team contracts and contract appendices at the Departments and Projects of Solutions Construction Co., Ltd., as follows:
|
||||
TÀI LIỆU THAM KHẢO/REFERENCES:
|
||||
Tiêu chuẩn ISO 9001/ISO 9001 standard.
|
||||
ĐỊNH NGHĨA, TỪ VIẾT TẮT/DEFINITIONS, ABBREVIATIONS:
|
||||
SOL : Công ty TNHH Xây dựng Solutions/Solutions Construction Co., Ltd
|
||||
BOD : Ban Giám đốc/Board of Directors
|
||||
NĐUQ : Người được ủy quyền/Authorized person
|
||||
CCM : Phòng Kiểm soát Chi phí/Cost Control Management Department
|
||||
PRO : Phòng Cung ứng/Procurement Department
|
||||
FIN : Phòng Tài chính/Financial Department
|
||||
ACT : Phòng Kế toán/Accounting Department
|
||||
EQU : Phòng Thiết bị/Equipment Department
|
||||
HRA : Phòng Nhân sự - Hành chính/HRA Department
|
||||
PB : Phòngban/Department
|
||||
TP : Trưởng Phòng ban/Department Manager
|
||||
TBP : Trưởng Bộ phận/Section Leader
|
||||
NV : Nhân viên/Officer
|
||||
BCH CT : Ban chỉ huy công trường/Site Management Team
|
||||
PD : Giám đốc Thi công/Project Director
|
||||
PM : Giám đốc Dự án/Project Manager
|
||||
QS : QS công trường/QS
|
||||
HĐ : Hợp đồng/Contract
|
||||
HĐTG : Hợp đồng Trọn gói/Lump sum Contract
|
||||
HĐNC : Hợp đồng Nhân công/Workmen Contract
|
||||
NTP : Nhà thầu phụ/Subcontractor
|
||||
NCC : Nhà cung cấp/Supplier
|
||||
TĐ : Tổ đội/Team
|
||||
QUY TRÌNH/PROCEDURE:
|
||||
Lưu đồ/Flow chart
|
||||
Diễn giải/Description
|
||||
|
||||
--- TABLE 1 (12r x 5c) ---
|
||||
Bước | Step || Quy trình | Procedure || Trách nhiệm | Responsibilities || Biểu mẫu | Forms || Thời gian | Time
|
||||
|| || || ||
|
||||
|| || PB/BCH CT || SOL-PRO-SP-001 Quy trình Cung ứng | SOL-PRO-SP-001 Procurement Procedure ||
|
||||
|| || QS/NV.PB/ | TBP/TPB || SOL-CCM-FO-002.01.v01 | Chấp thuận Hợp đồng Contract approval cover | SOL-CCM-FO-002.02.v01 | Hợp đồng trọn gói - Nhân công, Vật tư | Lump sum contract - Labor and Material | SOL-CCM-FO-002.03.v01 | Hợp đồng trọn gói - Nhân công, Thiết bị | Lump sum contract - Labor and Equipment | SOL-CCM-FO-002.04.v01 | Hợp đồng trọn gói – Điều kiện chung | Lump sum contract – General conditions | SOL-CCM-FO-002.05.v01 | Hợp đồng giao khoán | Workmen contract | SOL-CCM-FO-002.06.v01 | Hợp đồng mua bán | Purchase contract | SOL-CCM-FO-002.07.v01 | Đơn đặt hàng | Purchase order | SOL-CCM-RG-001 | Quy định mã số hợp đồng và đơn hàng | Regulation on contract and purchasing order code || 07 ngày | 07 days
|
||||
|| || PD/PM/PRO/ | CCM/FIN/ACT || SOL-CCM-FO-002.01.v01 | Chấp thuận Hợp đồng Contract approval cover | SOL-CCM-FO-002.02.v01 | Hợp đồng trọn gói - Nhân công, Vật tư | Lump sum contract - Labor and Material | SOL-CCM-FO-002.03.v01 | Hợp đồng trọn gói - Nhân công, Thiết bị | Lump sum contract - Labor and Equipment | SOL-CCM-FO-002.04.v01 | Hợp đồng trọn gói – Điều kiện chung | Lump sum contract – General conditions | SOL-CCM-FO-002.05.v01 | Hợp đồng giao khoán | Workmen contract | SOL-CCM-FO-002.06.v01 | Hợp đồng mua bán | Purchase contract | SOL-CCM-FO-002.07.v01 | Đơn đặt hàng | Purchase order | SOL-CCM-RG-001 | Quy định mã số hợp đồng và đơn hàng | Regulation on contract and purchasing order code || 07 ngày | 07 days
|
||||
|| || QS/NV.PB/ | TBP/TPB/ PD/PM || SOL-CCM-FO-002.01.v01 | Chấp thuận Hợp đồng Contract approval cover | SOL-CCM-FO-002.02.v01 | Hợp đồng trọn gói - Nhân công, Vật tư | Lump sum contract - Labor and Material | SOL-CCM-FO-002.03.v01 | Hợp đồng trọn gói - Nhân công, Thiết bị | Lump sum contract - Labor and Equipment | SOL-CCM-FO-002.04.v01 | Hợp đồng trọn gói – Điều kiện chung | Lump sum contract – General conditions | SOL-CCM-FO-002.05.v01 | Hợp đồng giao khoán | Workmen contract | SOL-CCM-FO-002.06.v01 | Hợp đồng mua bán | Purchase contract | SOL-CCM-FO-002.07.v01 | Đơn đặt hàng | Purchase order | SOL-CCM-RG-001 | Quy định mã số hợp đồng và đơn hàng | Regulation on contract and purchasing order code || 07 ngày | 07 days
|
||||
|| || NTP/NCC/TĐ || SOL-CCM-FO-002.01.v01 | Chấp thuận Hợp đồng Contract approval cover | SOL-CCM-FO-002.02.v01 | Hợp đồng trọn gói - Nhân công, Vật tư | Lump sum contract - Labor and Material | SOL-CCM-FO-002.03.v01 | Hợp đồng trọn gói - Nhân công, Thiết bị | Lump sum contract - Labor and Equipment | SOL-CCM-FO-002.04.v01 | Hợp đồng trọn gói – Điều kiện chung | Lump sum contract – General conditions | SOL-CCM-FO-002.05.v01 | Hợp đồng giao khoán | Workmen contract | SOL-CCM-FO-002.06.v01 | Hợp đồng mua bán | Purchase contract | SOL-CCM-FO-002.07.v01 | Đơn đặt hàng | Purchase order | SOL-CCM-RG-001 | Quy định mã số hợp đồng và đơn hàng | Regulation on contract and purchasing order code || 07 ngày | 07 days
|
||||
|| || CCM || SOL-CCM-FO-002.01.v01 | Chấp thuận Hợp đồng Contract approval cover | SOL-CCM-FO-002.02.v01 | Hợp đồng trọn gói - Nhân công, Vật tư | Lump sum contract - Labor and Material | SOL-CCM-FO-002.03.v01 | Hợp đồng trọn gói - Nhân công, Thiết bị | Lump sum contract - Labor and Equipment | SOL-CCM-FO-002.04.v01 | Hợp đồng trọn gói – Điều kiện chung | Lump sum contract – General conditions | SOL-CCM-FO-002.05.v01 | Hợp đồng giao khoán | Workmen contract | SOL-CCM-FO-002.06.v01 | Hợp đồng mua bán | Purchase contract | SOL-CCM-FO-002.07.v01 | Đơn đặt hàng | Purchase order | SOL-CCM-RG-001 | Quy định mã số hợp đồng và đơn hàng | Regulation on contract and purchasing order code || 03 ngày | 03 days
|
||||
|| || BOD/ | NĐUQ || SOL-CCM-FO-002.01.v01 | Chấp thuận Hợp đồng Contract approval cover | SOL-CCM-FO-002.02.v01 | Hợp đồng trọn gói - Nhân công, Vật tư | Lump sum contract - Labor and Material | SOL-CCM-FO-002.03.v01 | Hợp đồng trọn gói - Nhân công, Thiết bị | Lump sum contract - Labor and Equipment | SOL-CCM-FO-002.04.v01 | Hợp đồng trọn gói – Điều kiện chung | Lump sum contract – General conditions | SOL-CCM-FO-002.05.v01 | Hợp đồng giao khoán | Workmen contract | SOL-CCM-FO-002.06.v01 | Hợp đồng mua bán | Purchase contract | SOL-CCM-FO-002.07.v01 | Đơn đặt hàng | Purchase order | SOL-CCM-RG-001 | Quy định mã số hợp đồng và đơn hàng | Regulation on contract and purchasing order code || 01 ngày | 01 days
|
||||
|| || HRA || SOL-CCM-FO-002.01.v01 | Chấp thuận Hợp đồng Contract approval cover | SOL-CCM-FO-002.02.v01 | Hợp đồng trọn gói - Nhân công, Vật tư | Lump sum contract - Labor and Material | SOL-CCM-FO-002.03.v01 | Hợp đồng trọn gói - Nhân công, Thiết bị | Lump sum contract - Labor and Equipment | SOL-CCM-FO-002.04.v01 | Hợp đồng trọn gói – Điều kiện chung | Lump sum contract – General conditions | SOL-CCM-FO-002.05.v01 | Hợp đồng giao khoán | Workmen contract | SOL-CCM-FO-002.06.v01 | Hợp đồng mua bán | Purchase contract | SOL-CCM-FO-002.07.v01 | Đơn đặt hàng | Purchase order | SOL-CCM-RG-001 | Quy định mã số hợp đồng và đơn hàng | Regulation on contract and purchasing order code ||
|
||||
|| || PB/BCH CT/ CCM || SOL-CCM-FO-002.01.v01 | Chấp thuận Hợp đồng Contract approval cover | SOL-CCM-FO-002.02.v01 | Hợp đồng trọn gói - Nhân công, Vật tư | Lump sum contract - Labor and Material | SOL-CCM-FO-002.03.v01 | Hợp đồng trọn gói - Nhân công, Thiết bị | Lump sum contract - Labor and Equipment | SOL-CCM-FO-002.04.v01 | Hợp đồng trọn gói – Điều kiện chung | Lump sum contract – General conditions | SOL-CCM-FO-002.05.v01 | Hợp đồng giao khoán | Workmen contract | SOL-CCM-FO-002.06.v01 | Hợp đồng mua bán | Purchase contract | SOL-CCM-FO-002.07.v01 | Đơn đặt hàng | Purchase order | SOL-CCM-RG-001 | Quy định mã số hợp đồng và đơn hàng | Regulation on contract and purchasing order code ||
|
||||
|| || || SOL-CCM-FO-002.01.v01 | Chấp thuận Hợp đồng Contract approval cover | SOL-CCM-FO-002.02.v01 | Hợp đồng trọn gói - Nhân công, Vật tư | Lump sum contract - Labor and Material | SOL-CCM-FO-002.03.v01 | Hợp đồng trọn gói - Nhân công, Thiết bị | Lump sum contract - Labor and Equipment | SOL-CCM-FO-002.04.v01 | Hợp đồng trọn gói – Điều kiện chung | Lump sum contract – General conditions | SOL-CCM-FO-002.05.v01 | Hợp đồng giao khoán | Workmen contract | SOL-CCM-FO-002.06.v01 | Hợp đồng mua bán | Purchase contract | SOL-CCM-FO-002.07.v01 | Đơn đặt hàng | Purchase order | SOL-CCM-RG-001 | Quy định mã số hợp đồng và đơn hàng | Regulation on contract and purchasing order code ||
|
||||
|
||||
--- TABLE 2 (10r x 3c) ---
|
||||
BƯỚC | STEP || TRÁCH NHIỆM | RESPONSIBILITIES || NỘI DUNG CHI TIẾT | DETAIL
|
||||
Lựa chọn NTP/NCC | Select NTP/NCC || PB/BCH CT || Thống nhất chọn NTP/NCC/ĐTC theo quy định. | Select NTP/NCC/ĐTC in accordance with regulations. | Tham khảo Quy trình Cung ứng để so sánh và lựa chọn NTP/NCC/TĐC đúng quy định. | Refer to the Procurement Procedure to compare and select NTP/NCC/ĐTC in accordance with regulations.
|
||||
Soạn thảo hợp đồng | Draft the contract || QS/NV.PB/ | TBP/TPB || QS/NV.PB soạn dự thảo HĐ. | QS/NV.PB drafts Contract. | TBP/TPB kiểm tra các nội dung phù hợp với gói thầu và biểu mẫu HĐ. | TBP/TPB checks contents in compliance with the packages and Forms of Contract. | QS/NV.PB gởi dự thảo HĐ cho các Phòng ban để lấy ý kiến qua Email. | QS/NV.PB sends draft contract to relevant Departments for obtaining comments via email.
|
||||
Góp ý nội dung | Feedback content || PD/PM/PRO/ | CCM/FIN/ACT || PM/PD góp ý về phạm vi công việc, tiến độ, chất lượng, biện pháp thi công... (đối với HĐ của BCH CT). | PD/PM comments on scope of words, construction schedule, quality, construction methods,… (for BCH CT’s contract). | EQU góp ý (thuê/mua thiết bị). | EQU comments on hiring and purchasing equipment. | PRO góp ý (vật tư/thầu phụ trọn gói). | PRO comments on material and lumpsum Subcontractor. | CCM góp ý về các điều khoản liên quan đến chi phí. | CCM comments on provisions relating to cost. | ACT/FIN góp ý về điều kiện thanh toán, bảo lãnh, bảo hành. | ACT/FIN Comments on term of payments, guarantees, project warranty.
|
||||
Đàm phán, thương thảo hợp đồng | Negotiate the contract terms || QS/NV.PB/ | TBP/TPB/PD/PM || PB/BCH CT đàm phán các điều khoản về chất lượng, giao hàng, giá trị HĐ, điều kiện thanh toán, tạm ứng, ... | PB/BCH CT negotiates provisions on quality, product delivery, contract value, term of payment, advance payment, etc. | Các Phòng chức năng tham gia thương thảo nếu có yêu cầu. | Departments participates in negotiation if required.
|
||||
In hợp đồng | Print the contract || QS/NV.PB || NTP/NCC/TĐ in hợp đồng 2 mặt, ký, đóng dấu Hợp đồng và gửi về cho PB/BCH CT. | NTP/NCC/TĐ prints double-sided contract, signs and seals on the contract and sends to PB/BCH CT. | Cập nhật số Hợp đồng theo quy định. | Update the contract number. | PD/PM/TPB kiểm tra các điều khoản hợp đồng và ký nháy trên từng HĐ đúng theo các vị trí quy định. | PD/PM/TPB checks provisions contract and initializes on each contract. | PD/PM/TPB/TBP ký duyệt và cho ý kiến (nếu có) lên tờ cover chấp thuận HĐ (SOL-CCM-FO-002.01). | PD/PM/TPB signs and gives comments (if any) on the cover of Contract approval cover (SOL-CCM-FO-002.01). | QS/NV.PB chuyển HĐ cho CCM. | QS/NV.PB handover the contract to CCM Dept.
|
||||
Kiểm tra | Review and verify || CCM || CCM kiểm tra, TP.CCM ký nháy trên từng hợp đồng; ký duyệt và cho ý kiến (nếu có) lên Chấp thuận HĐ (SOL-CCM-FO-002.01.v01). | CCM checks and initializes on each contract; signs and gives comments (if any) on the Contract approval cover (SOL-CCM-FO-002.01.v01).
|
||||
Duyệt | Approve || BOD/ | NĐUQ || Xem xét và ký duyệt Hợp đồng. | Reviewing and signing the Contract.
|
||||
Đóng dấu hợp đồng | Seal the contract || HRA/ISO || PB/BCH CT chuyển hợp đồng đã được ký duyệt đến HRA/ISO để đóng dấu theo quy định. | PB/BCH CT transfrom the approved contract to HRA for seal the contract in accordance with regulations.
|
||||
Phát hành hợp đồng. Lưu trữ hồ sơ | Issue and archive the contract || PB/BCH CT/ | CCM || PB/BCH CT scan và gởi 01 bản gốc cho CCM. | PB/BCH CT scan and send the original contract to CCM | PB/BCH CT thông báo và chuyển bản gốc hợp đồng đã ký (theo đúng số lượng) đến NTP/NCC. | Notifying and sending originals of the signed contract (in accordance with quantity) to NTP/NCC. | Lưu trữ bản gốc hợp đồng. | Archive the original contract. | Lưu trữ trên Server theo Filing System (Hợp đồng bản cứng chính và bản scan). | Keep the contract on Filing System (hard copy and soft copy).
|
||||
24
fe-admin/.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
1
fe-admin/.nvmrc
Normal file
@ -0,0 +1 @@
|
||||
20
|
||||
73
fe-admin/README.md
Normal file
@ -0,0 +1,73 @@
|
||||
# React + TypeScript + Vite
|
||||
|
||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||
|
||||
Currently, two official plugins are available:
|
||||
|
||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
|
||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
|
||||
|
||||
## React Compiler
|
||||
|
||||
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
|
||||
|
||||
## Expanding the ESLint configuration
|
||||
|
||||
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
|
||||
|
||||
```js
|
||||
export default defineConfig([
|
||||
globalIgnores(['dist']),
|
||||
{
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
extends: [
|
||||
// Other configs...
|
||||
|
||||
// Remove tseslint.configs.recommended and replace with this
|
||||
tseslint.configs.recommendedTypeChecked,
|
||||
// Alternatively, use this for stricter rules
|
||||
tseslint.configs.strictTypeChecked,
|
||||
// Optionally, add this for stylistic rules
|
||||
tseslint.configs.stylisticTypeChecked,
|
||||
|
||||
// Other configs...
|
||||
],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
// other options...
|
||||
},
|
||||
},
|
||||
])
|
||||
```
|
||||
|
||||
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
||||
|
||||
```js
|
||||
// eslint.config.js
|
||||
import reactX from 'eslint-plugin-react-x'
|
||||
import reactDom from 'eslint-plugin-react-dom'
|
||||
|
||||
export default defineConfig([
|
||||
globalIgnores(['dist']),
|
||||
{
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
extends: [
|
||||
// Other configs...
|
||||
// Enable lint rules for React
|
||||
reactX.configs['recommended-typescript'],
|
||||
// Enable lint rules for React DOM
|
||||
reactDom.configs.recommended,
|
||||
],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
// other options...
|
||||
},
|
||||
},
|
||||
])
|
||||
```
|
||||
23
fe-admin/eslint.config.js
Normal file
@ -0,0 +1,23 @@
|
||||
import js from '@eslint/js'
|
||||
import globals from 'globals'
|
||||
import reactHooks from 'eslint-plugin-react-hooks'
|
||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||
import tseslint from 'typescript-eslint'
|
||||
import { defineConfig, globalIgnores } from 'eslint/config'
|
||||
|
||||
export default defineConfig([
|
||||
globalIgnores(['dist']),
|
||||
{
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
extends: [
|
||||
js.configs.recommended,
|
||||
tseslint.configs.recommended,
|
||||
reactHooks.configs.flat.recommended,
|
||||
reactRefresh.configs.vite,
|
||||
],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
},
|
||||
},
|
||||
])
|
||||
13
fe-admin/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>fe-admin</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
33
fe-admin/package.json
Normal file
@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "fe-admin",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^19.2.5",
|
||||
"react-dom": "^19.2.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.4",
|
||||
"@types/node": "^24.12.2",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^6.0.1",
|
||||
"eslint": "^9.39.4",
|
||||
"eslint-plugin-react-hooks": "^7.1.1",
|
||||
"eslint-plugin-react-refresh": "^0.5.2",
|
||||
"globals": "^17.5.0",
|
||||
"typescript": "~6.0.2",
|
||||
"typescript-eslint": "^8.58.2",
|
||||
"vite": "^8.0.9"
|
||||
}
|
||||
}
|
||||
1
fe-admin/public/favicon.svg
Normal file
|
After Width: | Height: | Size: 9.3 KiB |
24
fe-admin/public/icons.svg
Normal file
@ -0,0 +1,24 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<symbol id="bluesky-icon" viewBox="0 0 16 17">
|
||||
<g clip-path="url(#bluesky-clip)"><path fill="#08060d" d="M7.75 7.735c-.693-1.348-2.58-3.86-4.334-5.097-1.68-1.187-2.32-.981-2.74-.79C.188 2.065.1 2.812.1 3.251s.241 3.602.398 4.13c.52 1.744 2.367 2.333 4.07 2.145-2.495.37-4.71 1.278-1.805 4.512 3.196 3.309 4.38-.71 4.987-2.746.608 2.036 1.307 5.91 4.93 2.746 2.72-2.746.747-4.143-1.747-4.512 1.702.189 3.55-.4 4.07-2.145.156-.528.397-3.691.397-4.13s-.088-1.186-.575-1.406c-.42-.19-1.06-.395-2.741.79-1.755 1.24-3.64 3.752-4.334 5.099"/></g>
|
||||
<defs><clipPath id="bluesky-clip"><path fill="#fff" d="M.1.85h15.3v15.3H.1z"/></clipPath></defs>
|
||||
</symbol>
|
||||
<symbol id="discord-icon" viewBox="0 0 20 19">
|
||||
<path fill="#08060d" d="M16.224 3.768a14.5 14.5 0 0 0-3.67-1.153c-.158.286-.343.67-.47.976a13.5 13.5 0 0 0-4.067 0c-.128-.306-.317-.69-.476-.976A14.4 14.4 0 0 0 3.868 3.77C1.546 7.28.916 10.703 1.231 14.077a14.7 14.7 0 0 0 4.5 2.306q.545-.748.965-1.587a9.5 9.5 0 0 1-1.518-.74q.191-.14.372-.293c2.927 1.369 6.107 1.369 8.999 0q.183.152.372.294-.723.437-1.52.74.418.838.963 1.588a14.6 14.6 0 0 0 4.504-2.308c.37-3.911-.63-7.302-2.644-10.309m-9.13 8.234c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.894 0 1.614.82 1.599 1.82.001 1-.705 1.82-1.6 1.82m5.91 0c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.893 0 1.614.82 1.599 1.82 0 1-.706 1.82-1.6 1.82"/>
|
||||
</symbol>
|
||||
<symbol id="documentation-icon" viewBox="0 0 21 20">
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="m15.5 13.333 1.533 1.322c.645.555.967.833.967 1.178s-.322.623-.967 1.179L15.5 18.333m-3.333-5-1.534 1.322c-.644.555-.966.833-.966 1.178s.322.623.966 1.179l1.534 1.321"/>
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M17.167 10.836v-4.32c0-1.41 0-2.117-.224-2.68-.359-.906-1.118-1.621-2.08-1.96-.599-.21-1.349-.21-2.848-.21-2.623 0-3.935 0-4.983.369-1.684.591-3.013 1.842-3.641 3.428C3 6.449 3 7.684 3 10.154v2.122c0 2.558 0 3.838.706 4.726q.306.383.713.671c.76.536 1.79.64 3.581.66"/>
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M3 10a2.78 2.78 0 0 1 2.778-2.778c.555 0 1.209.097 1.748-.047.48-.129.854-.503.982-.982.145-.54.048-1.194.048-1.749a2.78 2.78 0 0 1 2.777-2.777"/>
|
||||
</symbol>
|
||||
<symbol id="github-icon" viewBox="0 0 19 19">
|
||||
<path fill="#08060d" fill-rule="evenodd" d="M9.356 1.85C5.05 1.85 1.57 5.356 1.57 9.694a7.84 7.84 0 0 0 5.324 7.44c.387.079.528-.168.528-.376 0-.182-.013-.805-.013-1.454-2.165.467-2.616-.935-2.616-.935-.349-.91-.864-1.143-.864-1.143-.71-.48.051-.48.051-.48.787.051 1.2.805 1.2.805.695 1.194 1.817.857 2.268.649.064-.507.27-.857.49-1.052-1.728-.182-3.545-.857-3.545-3.87 0-.857.31-1.558.8-2.104-.078-.195-.349-1 .077-2.078 0 0 .657-.208 2.14.805a7.5 7.5 0 0 1 1.946-.26c.657 0 1.328.092 1.946.26 1.483-1.013 2.14-.805 2.14-.805.426 1.078.155 1.883.078 2.078.502.546.799 1.247.799 2.104 0 3.013-1.818 3.675-3.558 3.87.284.247.528.714.528 1.454 0 1.052-.012 1.896-.012 2.156 0 .208.142.455.528.377a7.84 7.84 0 0 0 5.324-7.441c.013-4.338-3.48-7.844-7.773-7.844" clip-rule="evenodd"/>
|
||||
</symbol>
|
||||
<symbol id="social-icon" viewBox="0 0 20 20">
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M12.5 6.667a4.167 4.167 0 1 0-8.334 0 4.167 4.167 0 0 0 8.334 0"/>
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M2.5 16.667a5.833 5.833 0 0 1 8.75-5.053m3.837.474.513 1.035c.07.144.257.282.414.309l.93.155c.596.1.736.536.307.965l-.723.73a.64.64 0 0 0-.152.531l.207.903c.164.715-.213.991-.84.618l-.872-.52a.63.63 0 0 0-.577 0l-.872.52c-.624.373-1.003.094-.84-.618l.207-.903a.64.64 0 0 0-.152-.532l-.723-.729c-.426-.43-.289-.864.306-.964l.93-.156a.64.64 0 0 0 .412-.31l.513-1.034c.28-.562.735-.562 1.012 0"/>
|
||||
</symbol>
|
||||
<symbol id="x-icon" viewBox="0 0 19 19">
|
||||
<path fill="#08060d" fill-rule="evenodd" d="M1.893 1.98c.052.072 1.245 1.769 2.653 3.77l2.892 4.114c.183.261.333.48.333.486s-.068.089-.152.183l-.522.593-.765.867-3.597 4.087c-.375.426-.734.834-.798.905a1 1 0 0 0-.118.148c0 .01.236.017.664.017h.663l.729-.83c.4-.457.796-.906.879-.999a692 692 0 0 0 1.794-2.038c.034-.037.301-.34.594-.675l.551-.624.345-.392a7 7 0 0 1 .34-.374c.006 0 .93 1.306 2.052 2.903l2.084 2.965.045.063h2.275c1.87 0 2.273-.003 2.266-.021-.008-.02-1.098-1.572-3.894-5.547-2.013-2.862-2.28-3.246-2.273-3.266.008-.019.282-.332 2.085-2.38l2-2.274 1.567-1.782c.022-.028-.016-.03-.65-.03h-.674l-.3.342a871 871 0 0 1-1.782 2.025c-.067.075-.405.458-.75.852a100 100 0 0 1-.803.91c-.148.172-.299.344-.99 1.127-.304.343-.32.358-.345.327-.015-.019-.904-1.282-1.976-2.808L6.365 1.85H1.8zm1.782.91 8.078 11.294c.772 1.08 1.413 1.973 1.425 1.984.016.017.241.02 1.05.017l1.03-.004-2.694-3.766L7.796 5.75 5.722 2.852l-1.039-.004-1.039-.004z" clip-rule="evenodd"/>
|
||||
</symbol>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.9 KiB |
184
fe-admin/src/App.css
Normal file
@ -0,0 +1,184 @@
|
||||
.counter {
|
||||
font-size: 16px;
|
||||
padding: 5px 10px;
|
||||
border-radius: 5px;
|
||||
color: var(--accent);
|
||||
background: var(--accent-bg);
|
||||
border: 2px solid transparent;
|
||||
transition: border-color 0.3s;
|
||||
margin-bottom: 24px;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--accent-border);
|
||||
}
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.hero {
|
||||
position: relative;
|
||||
|
||||
.base,
|
||||
.framework,
|
||||
.vite {
|
||||
inset-inline: 0;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.base {
|
||||
width: 170px;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.framework,
|
||||
.vite {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.framework {
|
||||
z-index: 1;
|
||||
top: 34px;
|
||||
height: 28px;
|
||||
transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg)
|
||||
scale(1.4);
|
||||
}
|
||||
|
||||
.vite {
|
||||
z-index: 0;
|
||||
top: 107px;
|
||||
height: 26px;
|
||||
width: auto;
|
||||
transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg)
|
||||
scale(0.8);
|
||||
}
|
||||
}
|
||||
|
||||
#center {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 25px;
|
||||
place-content: center;
|
||||
place-items: center;
|
||||
flex-grow: 1;
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
padding: 32px 20px 24px;
|
||||
gap: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
#next-steps {
|
||||
display: flex;
|
||||
border-top: 1px solid var(--border);
|
||||
text-align: left;
|
||||
|
||||
& > div {
|
||||
flex: 1 1 0;
|
||||
padding: 32px;
|
||||
@media (max-width: 1024px) {
|
||||
padding: 24px 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-bottom: 16px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
#docs {
|
||||
border-right: 1px solid var(--border);
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
border-right: none;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
}
|
||||
|
||||
#next-steps ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin: 32px 0 0;
|
||||
|
||||
.logo {
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--text-h);
|
||||
font-size: 16px;
|
||||
border-radius: 6px;
|
||||
background: var(--social-bg);
|
||||
display: flex;
|
||||
padding: 6px 12px;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
text-decoration: none;
|
||||
transition: box-shadow 0.3s;
|
||||
|
||||
&:hover {
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
.button-icon {
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
margin-top: 20px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
|
||||
li {
|
||||
flex: 1 1 calc(50% - 8px);
|
||||
}
|
||||
|
||||
a {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#spacer {
|
||||
height: 88px;
|
||||
border-top: 1px solid var(--border);
|
||||
@media (max-width: 1024px) {
|
||||
height: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.ticks {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -4.5px;
|
||||
border: 5px solid transparent;
|
||||
}
|
||||
|
||||
&::before {
|
||||
left: 0;
|
||||
border-left-color: var(--border);
|
||||
}
|
||||
&::after {
|
||||
right: 0;
|
||||
border-right-color: var(--border);
|
||||
}
|
||||
}
|
||||
121
fe-admin/src/App.tsx
Normal file
@ -0,0 +1,121 @@
|
||||
import { useState } from 'react'
|
||||
import reactLogo from './assets/react.svg'
|
||||
import viteLogo from './assets/vite.svg'
|
||||
import heroImg from './assets/hero.png'
|
||||
import './App.css'
|
||||
|
||||
function App() {
|
||||
const [count, setCount] = useState(0)
|
||||
|
||||
return (
|
||||
<>
|
||||
<section id="center">
|
||||
<div className="hero">
|
||||
<img src={heroImg} className="base" width="170" height="179" alt="" />
|
||||
<img src={reactLogo} className="framework" alt="React logo" />
|
||||
<img src={viteLogo} className="vite" alt="Vite logo" />
|
||||
</div>
|
||||
<div>
|
||||
<h1>Get started</h1>
|
||||
<p>
|
||||
Edit <code>src/App.tsx</code> and save to test <code>HMR</code>
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
className="counter"
|
||||
onClick={() => setCount((count) => count + 1)}
|
||||
>
|
||||
Count is {count}
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<div className="ticks"></div>
|
||||
|
||||
<section id="next-steps">
|
||||
<div id="docs">
|
||||
<svg className="icon" role="presentation" aria-hidden="true">
|
||||
<use href="/icons.svg#documentation-icon"></use>
|
||||
</svg>
|
||||
<h2>Documentation</h2>
|
||||
<p>Your questions, answered</p>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://vite.dev/" target="_blank">
|
||||
<img className="logo" src={viteLogo} alt="" />
|
||||
Explore Vite
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://react.dev/" target="_blank">
|
||||
<img className="button-icon" src={reactLogo} alt="" />
|
||||
Learn more
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="social">
|
||||
<svg className="icon" role="presentation" aria-hidden="true">
|
||||
<use href="/icons.svg#social-icon"></use>
|
||||
</svg>
|
||||
<h2>Connect with us</h2>
|
||||
<p>Join the Vite community</p>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://github.com/vitejs/vite" target="_blank">
|
||||
<svg
|
||||
className="button-icon"
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<use href="/icons.svg#github-icon"></use>
|
||||
</svg>
|
||||
GitHub
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://chat.vite.dev/" target="_blank">
|
||||
<svg
|
||||
className="button-icon"
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<use href="/icons.svg#discord-icon"></use>
|
||||
</svg>
|
||||
Discord
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://x.com/vite_js" target="_blank">
|
||||
<svg
|
||||
className="button-icon"
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<use href="/icons.svg#x-icon"></use>
|
||||
</svg>
|
||||
X.com
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://bsky.app/profile/vite.dev" target="_blank">
|
||||
<svg
|
||||
className="button-icon"
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<use href="/icons.svg#bluesky-icon"></use>
|
||||
</svg>
|
||||
Bluesky
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="ticks"></div>
|
||||
<section id="spacer"></section>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
BIN
fe-admin/src/assets/hero.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
1
fe-admin/src/assets/react.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
1
fe-admin/src/assets/vite.svg
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
111
fe-admin/src/index.css
Normal file
@ -0,0 +1,111 @@
|
||||
:root {
|
||||
--text: #6b6375;
|
||||
--text-h: #08060d;
|
||||
--bg: #fff;
|
||||
--border: #e5e4e7;
|
||||
--code-bg: #f4f3ec;
|
||||
--accent: #aa3bff;
|
||||
--accent-bg: rgba(170, 59, 255, 0.1);
|
||||
--accent-border: rgba(170, 59, 255, 0.5);
|
||||
--social-bg: rgba(244, 243, 236, 0.5);
|
||||
--shadow:
|
||||
rgba(0, 0, 0, 0.1) 0 10px 15px -3px, rgba(0, 0, 0, 0.05) 0 4px 6px -2px;
|
||||
|
||||
--sans: system-ui, 'Segoe UI', Roboto, sans-serif;
|
||||
--heading: system-ui, 'Segoe UI', Roboto, sans-serif;
|
||||
--mono: ui-monospace, Consolas, monospace;
|
||||
|
||||
font: 18px/145% var(--sans);
|
||||
letter-spacing: 0.18px;
|
||||
color-scheme: light dark;
|
||||
color: var(--text);
|
||||
background: var(--bg);
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--text: #9ca3af;
|
||||
--text-h: #f3f4f6;
|
||||
--bg: #16171d;
|
||||
--border: #2e303a;
|
||||
--code-bg: #1f2028;
|
||||
--accent: #c084fc;
|
||||
--accent-bg: rgba(192, 132, 252, 0.15);
|
||||
--accent-border: rgba(192, 132, 252, 0.5);
|
||||
--social-bg: rgba(47, 48, 58, 0.5);
|
||||
--shadow:
|
||||
rgba(0, 0, 0, 0.4) 0 10px 15px -3px, rgba(0, 0, 0, 0.25) 0 4px 6px -2px;
|
||||
}
|
||||
|
||||
#social .button-icon {
|
||||
filter: invert(1) brightness(2);
|
||||
}
|
||||
}
|
||||
|
||||
#root {
|
||||
width: 1126px;
|
||||
max-width: 100%;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
border-inline: 1px solid var(--border);
|
||||
min-height: 100svh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2 {
|
||||
font-family: var(--heading);
|
||||
font-weight: 500;
|
||||
color: var(--text-h);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 56px;
|
||||
letter-spacing: -1.68px;
|
||||
margin: 32px 0;
|
||||
@media (max-width: 1024px) {
|
||||
font-size: 36px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
}
|
||||
h2 {
|
||||
font-size: 24px;
|
||||
line-height: 118%;
|
||||
letter-spacing: -0.24px;
|
||||
margin: 0 0 8px;
|
||||
@media (max-width: 1024px) {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
code,
|
||||
.counter {
|
||||
font-family: var(--mono);
|
||||
display: inline-flex;
|
||||
border-radius: 4px;
|
||||
color: var(--text-h);
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 15px;
|
||||
line-height: 135%;
|
||||
padding: 4px 8px;
|
||||
background: var(--code-bg);
|
||||
}
|
||||
10
fe-admin/src/main.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import './index.css'
|
||||
import App from './App.tsx'
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
)
|
||||
25
fe-admin/tsconfig.app.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"target": "es2023",
|
||||
"lib": ["ES2023", "DOM"],
|
||||
"module": "esnext",
|
||||
"types": ["vite/client"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
7
fe-admin/tsconfig.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
||||
24
fe-admin/tsconfig.node.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "es2023",
|
||||
"lib": ["ES2023"],
|
||||
"module": "esnext",
|
||||
"types": ["node"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
24
fe-admin/vite.config.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import path from 'node:path'
|
||||
|
||||
// Admin UI — port 8082, proxy /api → SolutionErp.Api (http://localhost:5443)
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src'),
|
||||
},
|
||||
},
|
||||
server: {
|
||||
port: 8082,
|
||||
strictPort: true,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:5443',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
24
fe-user/.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
1
fe-user/.nvmrc
Normal file
@ -0,0 +1 @@
|
||||
20
|
||||
73
fe-user/README.md
Normal file
@ -0,0 +1,73 @@
|
||||
# React + TypeScript + Vite
|
||||
|
||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||
|
||||
Currently, two official plugins are available:
|
||||
|
||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
|
||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
|
||||
|
||||
## React Compiler
|
||||
|
||||
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
|
||||
|
||||
## Expanding the ESLint configuration
|
||||
|
||||
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
|
||||
|
||||
```js
|
||||
export default defineConfig([
|
||||
globalIgnores(['dist']),
|
||||
{
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
extends: [
|
||||
// Other configs...
|
||||
|
||||
// Remove tseslint.configs.recommended and replace with this
|
||||
tseslint.configs.recommendedTypeChecked,
|
||||
// Alternatively, use this for stricter rules
|
||||
tseslint.configs.strictTypeChecked,
|
||||
// Optionally, add this for stylistic rules
|
||||
tseslint.configs.stylisticTypeChecked,
|
||||
|
||||
// Other configs...
|
||||
],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
// other options...
|
||||
},
|
||||
},
|
||||
])
|
||||
```
|
||||
|
||||
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
||||
|
||||
```js
|
||||
// eslint.config.js
|
||||
import reactX from 'eslint-plugin-react-x'
|
||||
import reactDom from 'eslint-plugin-react-dom'
|
||||
|
||||
export default defineConfig([
|
||||
globalIgnores(['dist']),
|
||||
{
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
extends: [
|
||||
// Other configs...
|
||||
// Enable lint rules for React
|
||||
reactX.configs['recommended-typescript'],
|
||||
// Enable lint rules for React DOM
|
||||
reactDom.configs.recommended,
|
||||
],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
// other options...
|
||||
},
|
||||
},
|
||||
])
|
||||
```
|
||||
23
fe-user/eslint.config.js
Normal file
@ -0,0 +1,23 @@
|
||||
import js from '@eslint/js'
|
||||
import globals from 'globals'
|
||||
import reactHooks from 'eslint-plugin-react-hooks'
|
||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||
import tseslint from 'typescript-eslint'
|
||||
import { defineConfig, globalIgnores } from 'eslint/config'
|
||||
|
||||
export default defineConfig([
|
||||
globalIgnores(['dist']),
|
||||
{
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
extends: [
|
||||
js.configs.recommended,
|
||||
tseslint.configs.recommended,
|
||||
reactHooks.configs.flat.recommended,
|
||||
reactRefresh.configs.vite,
|
||||
],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
},
|
||||
},
|
||||
])
|
||||
13
fe-user/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>fe-user</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
33
fe-user/package.json
Normal file
@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "fe-user",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^19.2.5",
|
||||
"react-dom": "^19.2.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.4",
|
||||
"@types/node": "^24.12.2",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^6.0.1",
|
||||
"eslint": "^9.39.4",
|
||||
"eslint-plugin-react-hooks": "^7.1.1",
|
||||
"eslint-plugin-react-refresh": "^0.5.2",
|
||||
"globals": "^17.5.0",
|
||||
"typescript": "~6.0.2",
|
||||
"typescript-eslint": "^8.58.2",
|
||||
"vite": "^8.0.9"
|
||||
}
|
||||
}
|
||||
1
fe-user/public/favicon.svg
Normal file
|
After Width: | Height: | Size: 9.3 KiB |
24
fe-user/public/icons.svg
Normal file
@ -0,0 +1,24 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<symbol id="bluesky-icon" viewBox="0 0 16 17">
|
||||
<g clip-path="url(#bluesky-clip)"><path fill="#08060d" d="M7.75 7.735c-.693-1.348-2.58-3.86-4.334-5.097-1.68-1.187-2.32-.981-2.74-.79C.188 2.065.1 2.812.1 3.251s.241 3.602.398 4.13c.52 1.744 2.367 2.333 4.07 2.145-2.495.37-4.71 1.278-1.805 4.512 3.196 3.309 4.38-.71 4.987-2.746.608 2.036 1.307 5.91 4.93 2.746 2.72-2.746.747-4.143-1.747-4.512 1.702.189 3.55-.4 4.07-2.145.156-.528.397-3.691.397-4.13s-.088-1.186-.575-1.406c-.42-.19-1.06-.395-2.741.79-1.755 1.24-3.64 3.752-4.334 5.099"/></g>
|
||||
<defs><clipPath id="bluesky-clip"><path fill="#fff" d="M.1.85h15.3v15.3H.1z"/></clipPath></defs>
|
||||
</symbol>
|
||||
<symbol id="discord-icon" viewBox="0 0 20 19">
|
||||
<path fill="#08060d" d="M16.224 3.768a14.5 14.5 0 0 0-3.67-1.153c-.158.286-.343.67-.47.976a13.5 13.5 0 0 0-4.067 0c-.128-.306-.317-.69-.476-.976A14.4 14.4 0 0 0 3.868 3.77C1.546 7.28.916 10.703 1.231 14.077a14.7 14.7 0 0 0 4.5 2.306q.545-.748.965-1.587a9.5 9.5 0 0 1-1.518-.74q.191-.14.372-.293c2.927 1.369 6.107 1.369 8.999 0q.183.152.372.294-.723.437-1.52.74.418.838.963 1.588a14.6 14.6 0 0 0 4.504-2.308c.37-3.911-.63-7.302-2.644-10.309m-9.13 8.234c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.894 0 1.614.82 1.599 1.82.001 1-.705 1.82-1.6 1.82m5.91 0c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.893 0 1.614.82 1.599 1.82 0 1-.706 1.82-1.6 1.82"/>
|
||||
</symbol>
|
||||
<symbol id="documentation-icon" viewBox="0 0 21 20">
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="m15.5 13.333 1.533 1.322c.645.555.967.833.967 1.178s-.322.623-.967 1.179L15.5 18.333m-3.333-5-1.534 1.322c-.644.555-.966.833-.966 1.178s.322.623.966 1.179l1.534 1.321"/>
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M17.167 10.836v-4.32c0-1.41 0-2.117-.224-2.68-.359-.906-1.118-1.621-2.08-1.96-.599-.21-1.349-.21-2.848-.21-2.623 0-3.935 0-4.983.369-1.684.591-3.013 1.842-3.641 3.428C3 6.449 3 7.684 3 10.154v2.122c0 2.558 0 3.838.706 4.726q.306.383.713.671c.76.536 1.79.64 3.581.66"/>
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M3 10a2.78 2.78 0 0 1 2.778-2.778c.555 0 1.209.097 1.748-.047.48-.129.854-.503.982-.982.145-.54.048-1.194.048-1.749a2.78 2.78 0 0 1 2.777-2.777"/>
|
||||
</symbol>
|
||||
<symbol id="github-icon" viewBox="0 0 19 19">
|
||||
<path fill="#08060d" fill-rule="evenodd" d="M9.356 1.85C5.05 1.85 1.57 5.356 1.57 9.694a7.84 7.84 0 0 0 5.324 7.44c.387.079.528-.168.528-.376 0-.182-.013-.805-.013-1.454-2.165.467-2.616-.935-2.616-.935-.349-.91-.864-1.143-.864-1.143-.71-.48.051-.48.051-.48.787.051 1.2.805 1.2.805.695 1.194 1.817.857 2.268.649.064-.507.27-.857.49-1.052-1.728-.182-3.545-.857-3.545-3.87 0-.857.31-1.558.8-2.104-.078-.195-.349-1 .077-2.078 0 0 .657-.208 2.14.805a7.5 7.5 0 0 1 1.946-.26c.657 0 1.328.092 1.946.26 1.483-1.013 2.14-.805 2.14-.805.426 1.078.155 1.883.078 2.078.502.546.799 1.247.799 2.104 0 3.013-1.818 3.675-3.558 3.87.284.247.528.714.528 1.454 0 1.052-.012 1.896-.012 2.156 0 .208.142.455.528.377a7.84 7.84 0 0 0 5.324-7.441c.013-4.338-3.48-7.844-7.773-7.844" clip-rule="evenodd"/>
|
||||
</symbol>
|
||||
<symbol id="social-icon" viewBox="0 0 20 20">
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M12.5 6.667a4.167 4.167 0 1 0-8.334 0 4.167 4.167 0 0 0 8.334 0"/>
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M2.5 16.667a5.833 5.833 0 0 1 8.75-5.053m3.837.474.513 1.035c.07.144.257.282.414.309l.93.155c.596.1.736.536.307.965l-.723.73a.64.64 0 0 0-.152.531l.207.903c.164.715-.213.991-.84.618l-.872-.52a.63.63 0 0 0-.577 0l-.872.52c-.624.373-1.003.094-.84-.618l.207-.903a.64.64 0 0 0-.152-.532l-.723-.729c-.426-.43-.289-.864.306-.964l.93-.156a.64.64 0 0 0 .412-.31l.513-1.034c.28-.562.735-.562 1.012 0"/>
|
||||
</symbol>
|
||||
<symbol id="x-icon" viewBox="0 0 19 19">
|
||||
<path fill="#08060d" fill-rule="evenodd" d="M1.893 1.98c.052.072 1.245 1.769 2.653 3.77l2.892 4.114c.183.261.333.48.333.486s-.068.089-.152.183l-.522.593-.765.867-3.597 4.087c-.375.426-.734.834-.798.905a1 1 0 0 0-.118.148c0 .01.236.017.664.017h.663l.729-.83c.4-.457.796-.906.879-.999a692 692 0 0 0 1.794-2.038c.034-.037.301-.34.594-.675l.551-.624.345-.392a7 7 0 0 1 .34-.374c.006 0 .93 1.306 2.052 2.903l2.084 2.965.045.063h2.275c1.87 0 2.273-.003 2.266-.021-.008-.02-1.098-1.572-3.894-5.547-2.013-2.862-2.28-3.246-2.273-3.266.008-.019.282-.332 2.085-2.38l2-2.274 1.567-1.782c.022-.028-.016-.03-.65-.03h-.674l-.3.342a871 871 0 0 1-1.782 2.025c-.067.075-.405.458-.75.852a100 100 0 0 1-.803.91c-.148.172-.299.344-.99 1.127-.304.343-.32.358-.345.327-.015-.019-.904-1.282-1.976-2.808L6.365 1.85H1.8zm1.782.91 8.078 11.294c.772 1.08 1.413 1.973 1.425 1.984.016.017.241.02 1.05.017l1.03-.004-2.694-3.766L7.796 5.75 5.722 2.852l-1.039-.004-1.039-.004z" clip-rule="evenodd"/>
|
||||
</symbol>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.9 KiB |
184
fe-user/src/App.css
Normal file
@ -0,0 +1,184 @@
|
||||
.counter {
|
||||
font-size: 16px;
|
||||
padding: 5px 10px;
|
||||
border-radius: 5px;
|
||||
color: var(--accent);
|
||||
background: var(--accent-bg);
|
||||
border: 2px solid transparent;
|
||||
transition: border-color 0.3s;
|
||||
margin-bottom: 24px;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--accent-border);
|
||||
}
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.hero {
|
||||
position: relative;
|
||||
|
||||
.base,
|
||||
.framework,
|
||||
.vite {
|
||||
inset-inline: 0;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.base {
|
||||
width: 170px;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.framework,
|
||||
.vite {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.framework {
|
||||
z-index: 1;
|
||||
top: 34px;
|
||||
height: 28px;
|
||||
transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg)
|
||||
scale(1.4);
|
||||
}
|
||||
|
||||
.vite {
|
||||
z-index: 0;
|
||||
top: 107px;
|
||||
height: 26px;
|
||||
width: auto;
|
||||
transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg)
|
||||
scale(0.8);
|
||||
}
|
||||
}
|
||||
|
||||
#center {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 25px;
|
||||
place-content: center;
|
||||
place-items: center;
|
||||
flex-grow: 1;
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
padding: 32px 20px 24px;
|
||||
gap: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
#next-steps {
|
||||
display: flex;
|
||||
border-top: 1px solid var(--border);
|
||||
text-align: left;
|
||||
|
||||
& > div {
|
||||
flex: 1 1 0;
|
||||
padding: 32px;
|
||||
@media (max-width: 1024px) {
|
||||
padding: 24px 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-bottom: 16px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
#docs {
|
||||
border-right: 1px solid var(--border);
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
border-right: none;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
}
|
||||
|
||||
#next-steps ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin: 32px 0 0;
|
||||
|
||||
.logo {
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--text-h);
|
||||
font-size: 16px;
|
||||
border-radius: 6px;
|
||||
background: var(--social-bg);
|
||||
display: flex;
|
||||
padding: 6px 12px;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
text-decoration: none;
|
||||
transition: box-shadow 0.3s;
|
||||
|
||||
&:hover {
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
.button-icon {
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
margin-top: 20px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
|
||||
li {
|
||||
flex: 1 1 calc(50% - 8px);
|
||||
}
|
||||
|
||||
a {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#spacer {
|
||||
height: 88px;
|
||||
border-top: 1px solid var(--border);
|
||||
@media (max-width: 1024px) {
|
||||
height: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.ticks {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -4.5px;
|
||||
border: 5px solid transparent;
|
||||
}
|
||||
|
||||
&::before {
|
||||
left: 0;
|
||||
border-left-color: var(--border);
|
||||
}
|
||||
&::after {
|
||||
right: 0;
|
||||
border-right-color: var(--border);
|
||||
}
|
||||
}
|
||||
121
fe-user/src/App.tsx
Normal file
@ -0,0 +1,121 @@
|
||||
import { useState } from 'react'
|
||||
import reactLogo from './assets/react.svg'
|
||||
import viteLogo from './assets/vite.svg'
|
||||
import heroImg from './assets/hero.png'
|
||||
import './App.css'
|
||||
|
||||
function App() {
|
||||
const [count, setCount] = useState(0)
|
||||
|
||||
return (
|
||||
<>
|
||||
<section id="center">
|
||||
<div className="hero">
|
||||
<img src={heroImg} className="base" width="170" height="179" alt="" />
|
||||
<img src={reactLogo} className="framework" alt="React logo" />
|
||||
<img src={viteLogo} className="vite" alt="Vite logo" />
|
||||
</div>
|
||||
<div>
|
||||
<h1>Get started</h1>
|
||||
<p>
|
||||
Edit <code>src/App.tsx</code> and save to test <code>HMR</code>
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
className="counter"
|
||||
onClick={() => setCount((count) => count + 1)}
|
||||
>
|
||||
Count is {count}
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<div className="ticks"></div>
|
||||
|
||||
<section id="next-steps">
|
||||
<div id="docs">
|
||||
<svg className="icon" role="presentation" aria-hidden="true">
|
||||
<use href="/icons.svg#documentation-icon"></use>
|
||||
</svg>
|
||||
<h2>Documentation</h2>
|
||||
<p>Your questions, answered</p>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://vite.dev/" target="_blank">
|
||||
<img className="logo" src={viteLogo} alt="" />
|
||||
Explore Vite
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://react.dev/" target="_blank">
|
||||
<img className="button-icon" src={reactLogo} alt="" />
|
||||
Learn more
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="social">
|
||||
<svg className="icon" role="presentation" aria-hidden="true">
|
||||
<use href="/icons.svg#social-icon"></use>
|
||||
</svg>
|
||||
<h2>Connect with us</h2>
|
||||
<p>Join the Vite community</p>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://github.com/vitejs/vite" target="_blank">
|
||||
<svg
|
||||
className="button-icon"
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<use href="/icons.svg#github-icon"></use>
|
||||
</svg>
|
||||
GitHub
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://chat.vite.dev/" target="_blank">
|
||||
<svg
|
||||
className="button-icon"
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<use href="/icons.svg#discord-icon"></use>
|
||||
</svg>
|
||||
Discord
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://x.com/vite_js" target="_blank">
|
||||
<svg
|
||||
className="button-icon"
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<use href="/icons.svg#x-icon"></use>
|
||||
</svg>
|
||||
X.com
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://bsky.app/profile/vite.dev" target="_blank">
|
||||
<svg
|
||||
className="button-icon"
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<use href="/icons.svg#bluesky-icon"></use>
|
||||
</svg>
|
||||
Bluesky
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="ticks"></div>
|
||||
<section id="spacer"></section>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
BIN
fe-user/src/assets/hero.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
1
fe-user/src/assets/react.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
1
fe-user/src/assets/vite.svg
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
111
fe-user/src/index.css
Normal file
@ -0,0 +1,111 @@
|
||||
:root {
|
||||
--text: #6b6375;
|
||||
--text-h: #08060d;
|
||||
--bg: #fff;
|
||||
--border: #e5e4e7;
|
||||
--code-bg: #f4f3ec;
|
||||
--accent: #aa3bff;
|
||||
--accent-bg: rgba(170, 59, 255, 0.1);
|
||||
--accent-border: rgba(170, 59, 255, 0.5);
|
||||
--social-bg: rgba(244, 243, 236, 0.5);
|
||||
--shadow:
|
||||
rgba(0, 0, 0, 0.1) 0 10px 15px -3px, rgba(0, 0, 0, 0.05) 0 4px 6px -2px;
|
||||
|
||||
--sans: system-ui, 'Segoe UI', Roboto, sans-serif;
|
||||
--heading: system-ui, 'Segoe UI', Roboto, sans-serif;
|
||||
--mono: ui-monospace, Consolas, monospace;
|
||||
|
||||
font: 18px/145% var(--sans);
|
||||
letter-spacing: 0.18px;
|
||||
color-scheme: light dark;
|
||||
color: var(--text);
|
||||
background: var(--bg);
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--text: #9ca3af;
|
||||
--text-h: #f3f4f6;
|
||||
--bg: #16171d;
|
||||
--border: #2e303a;
|
||||
--code-bg: #1f2028;
|
||||
--accent: #c084fc;
|
||||
--accent-bg: rgba(192, 132, 252, 0.15);
|
||||
--accent-border: rgba(192, 132, 252, 0.5);
|
||||
--social-bg: rgba(47, 48, 58, 0.5);
|
||||
--shadow:
|
||||
rgba(0, 0, 0, 0.4) 0 10px 15px -3px, rgba(0, 0, 0, 0.25) 0 4px 6px -2px;
|
||||
}
|
||||
|
||||
#social .button-icon {
|
||||
filter: invert(1) brightness(2);
|
||||
}
|
||||
}
|
||||
|
||||
#root {
|
||||
width: 1126px;
|
||||
max-width: 100%;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
border-inline: 1px solid var(--border);
|
||||
min-height: 100svh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2 {
|
||||
font-family: var(--heading);
|
||||
font-weight: 500;
|
||||
color: var(--text-h);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 56px;
|
||||
letter-spacing: -1.68px;
|
||||
margin: 32px 0;
|
||||
@media (max-width: 1024px) {
|
||||
font-size: 36px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
}
|
||||
h2 {
|
||||
font-size: 24px;
|
||||
line-height: 118%;
|
||||
letter-spacing: -0.24px;
|
||||
margin: 0 0 8px;
|
||||
@media (max-width: 1024px) {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
code,
|
||||
.counter {
|
||||
font-family: var(--mono);
|
||||
display: inline-flex;
|
||||
border-radius: 4px;
|
||||
color: var(--text-h);
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 15px;
|
||||
line-height: 135%;
|
||||
padding: 4px 8px;
|
||||
background: var(--code-bg);
|
||||
}
|
||||
10
fe-user/src/main.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import './index.css'
|
||||
import App from './App.tsx'
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
)
|
||||
25
fe-user/tsconfig.app.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"target": "es2023",
|
||||
"lib": ["ES2023", "DOM"],
|
||||
"module": "esnext",
|
||||
"types": ["vite/client"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
7
fe-user/tsconfig.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
||||
24
fe-user/tsconfig.node.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "es2023",
|
||||
"lib": ["ES2023"],
|
||||
"module": "esnext",
|
||||
"types": ["node"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
24
fe-user/vite.config.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import path from 'node:path'
|
||||
|
||||
// User UI — port 8080, proxy /api → SolutionErp.Api (http://localhost:5443)
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src'),
|
||||
},
|
||||
},
|
||||
server: {
|
||||
port: 8080,
|
||||
strictPort: true,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:5443',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
6
global.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "10.0.104",
|
||||
"rollForward": "latestFeature"
|
||||
}
|
||||
}
|
||||
100
scripts/parse_forms.py
Normal file
@ -0,0 +1,100 @@
|
||||
"""Extract text + structure from 8 FORM files (.docx, .doc, .xlsx).
|
||||
|
||||
Outputs: docs/forms-spec-raw.md — dump text + tables for manual field spec extraction.
|
||||
|
||||
Usage: python scripts/parse_forms.py
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
from xml.etree import ElementTree as ET
|
||||
|
||||
try:
|
||||
import docx
|
||||
from openpyxl import load_workbook
|
||||
except ImportError:
|
||||
sys.stderr.write("pip install python-docx openpyxl\n")
|
||||
sys.exit(1)
|
||||
|
||||
FORM_DIR = Path("D:/Dropbox/CONG_VIEC/SOLUTION/FORM")
|
||||
OUT = Path(__file__).parent.parent / "docs" / "forms-spec-raw.md"
|
||||
|
||||
W_NS = {"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"}
|
||||
|
||||
|
||||
def extract_doc_via_zip(path: Path) -> str:
|
||||
"""Fallback for .doc (binary) or .docx — unzip and read word/document.xml."""
|
||||
try:
|
||||
with zipfile.ZipFile(path) as z:
|
||||
with z.open("word/document.xml") as f:
|
||||
tree = ET.parse(f)
|
||||
root = tree.getroot()
|
||||
texts: list[str] = []
|
||||
for para in root.iter(f"{{{W_NS['w']}}}p"):
|
||||
line = "".join(t.text or "" for t in para.iter(f"{{{W_NS['w']}}}t"))
|
||||
if line.strip():
|
||||
texts.append(line)
|
||||
return "\n".join(texts)
|
||||
except Exception as e:
|
||||
return f"[ERROR unzip: {e}]"
|
||||
|
||||
|
||||
def extract_docx(path: Path) -> str:
|
||||
try:
|
||||
d = docx.Document(str(path))
|
||||
parts: list[str] = []
|
||||
for p in d.paragraphs:
|
||||
if p.text.strip():
|
||||
parts.append(p.text)
|
||||
# Tables
|
||||
for i, tbl in enumerate(d.tables):
|
||||
parts.append(f"\n--- TABLE {i+1} ({len(tbl.rows)} rows x {len(tbl.columns)} cols) ---")
|
||||
for row in tbl.rows:
|
||||
cells = [c.text.strip().replace("\n", " | ") for c in row.cells]
|
||||
parts.append(" || ".join(cells))
|
||||
return "\n".join(parts)
|
||||
except Exception:
|
||||
return extract_doc_via_zip(path)
|
||||
|
||||
|
||||
def extract_xlsx(path: Path) -> str:
|
||||
wb = load_workbook(str(path), data_only=True)
|
||||
parts: list[str] = []
|
||||
for sh_name in wb.sheetnames:
|
||||
ws = wb[sh_name]
|
||||
parts.append(f"\n--- SHEET: {sh_name} ({ws.max_row} rows x {ws.max_column} cols) ---")
|
||||
for row in ws.iter_rows(values_only=True):
|
||||
vals = [str(v).strip() if v is not None else "" for v in row]
|
||||
if any(v for v in vals):
|
||||
parts.append(" | ".join(vals))
|
||||
return "\n".join(parts)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
files = sorted(FORM_DIR.iterdir())
|
||||
OUT.parent.mkdir(parents=True, exist_ok=True)
|
||||
with OUT.open("w", encoding="utf-8") as out:
|
||||
out.write("# Forms — Raw Text Dump\n\n")
|
||||
out.write(f"Source: `{FORM_DIR}` — {len(files)} files\n\n")
|
||||
for f in files:
|
||||
out.write(f"\n---\n\n## {f.name}\n\n")
|
||||
ext = f.suffix.lower()
|
||||
if ext == ".xlsx":
|
||||
text = extract_xlsx(f)
|
||||
elif ext in (".docx", ".doc"):
|
||||
text = extract_docx(f)
|
||||
else:
|
||||
text = "[unsupported]"
|
||||
out.write("```\n")
|
||||
out.write(text[:20000])
|
||||
if len(text) > 20000:
|
||||
out.write(f"\n\n[... truncated {len(text)-20000} chars]")
|
||||
out.write("\n```\n")
|
||||
print(f"OK -> {OUT}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
21
scripts/parse_workflow.py
Normal file
@ -0,0 +1,21 @@
|
||||
"""Extract QUY_TRINH docx text for manual workflow modeling."""
|
||||
from pathlib import Path
|
||||
import docx
|
||||
|
||||
SRC = Path("D:/Dropbox/CONG_VIEC/SOLUTION/QUY_TRINH/QT TRINH KY HOP DONG TP-NCC.docx")
|
||||
OUT = Path(__file__).parent.parent / "docs" / "workflow-raw.md"
|
||||
|
||||
d = docx.Document(str(SRC))
|
||||
parts = []
|
||||
for p in d.paragraphs:
|
||||
if p.text.strip():
|
||||
parts.append(p.text)
|
||||
for i, tbl in enumerate(d.tables):
|
||||
parts.append(f"\n--- TABLE {i+1} ({len(tbl.rows)}r x {len(tbl.columns)}c) ---")
|
||||
for row in tbl.rows:
|
||||
cells = [c.text.strip().replace("\n", " | ") for c in row.cells]
|
||||
parts.append(" || ".join(cells))
|
||||
|
||||
OUT.parent.mkdir(parents=True, exist_ok=True)
|
||||
OUT.write_text("\n".join(parts), encoding="utf-8")
|
||||
print(f"OK -> {OUT} ({OUT.stat().st_size} bytes)")
|
||||
@ -0,0 +1,25 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace SolutionErp.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("[controller]")]
|
||||
public class WeatherForecastController : ControllerBase
|
||||
{
|
||||
private static readonly string[] Summaries =
|
||||
[
|
||||
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
|
||||
];
|
||||
|
||||
[HttpGet(Name = "GetWeatherForecast")]
|
||||
public IEnumerable<WeatherForecast> Get()
|
||||
{
|
||||
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
|
||||
{
|
||||
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
|
||||
TemperatureC = Random.Shared.Next(-20, 55),
|
||||
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
23
src/Backend/SolutionErp.Api/Program.cs
Normal file
@ -0,0 +1,23 @@
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Add services to the container.
|
||||
|
||||
builder.Services.AddControllers();
|
||||
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
|
||||
builder.Services.AddOpenApi();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.MapOpenApi();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapControllers();
|
||||
|
||||
app.Run();
|
||||
23
src/Backend/SolutionErp.Api/Properties/launchSettings.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"applicationUrl": "http://localhost:5235",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"applicationUrl": "https://localhost:7241;http://localhost:5235",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
21
src/Backend/SolutionErp.Api/SolutionErp.Api.csproj
Normal file
@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.4" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.7" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SolutionErp.Application\SolutionErp.Application.csproj" />
|
||||
<ProjectReference Include="..\SolutionErp.Infrastructure\SolutionErp.Infrastructure.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
6
src/Backend/SolutionErp.Api/SolutionErp.Api.http
Normal file
@ -0,0 +1,6 @@
|
||||
@SolutionErp.Api_HostAddress = http://localhost:5235
|
||||
|
||||
GET {{SolutionErp.Api_HostAddress}}/weatherforecast/
|
||||
Accept: application/json
|
||||
|
||||
###
|
||||
12
src/Backend/SolutionErp.Api/WeatherForecast.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace SolutionErp.Api;
|
||||
|
||||
public class WeatherForecast
|
||||
{
|
||||
public DateOnly Date { get; set; }
|
||||
|
||||
public int TemperatureC { get; set; }
|
||||
|
||||
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
|
||||
|
||||
public string? Summary { get; set; }
|
||||
}
|
||||
8
src/Backend/SolutionErp.Api/appsettings.Development.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
9
src/Backend/SolutionErp.Api/appsettings.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SolutionErp.Domain\SolutionErp.Domain.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AutoMapper" Version="16.1.1" />
|
||||
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="12.1.1" />
|
||||
<PackageReference Include="MediatR" Version="14.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
9
src/Backend/SolutionErp.Domain/SolutionErp.Domain.csproj
Normal file
@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@ -0,0 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SolutionErp.Application\SolutionErp.Application.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.6">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||