@@ -93,6 +93,15 @@ File này lưu trữ các quyết định thiết kế dài hạn và trạng th
- Admin Control Center tại `/admin` dùng TanStack Query và permission động, gồm dashboard KPI/hoạt động gần đây, danh sách người dùng có tìm kiếm-lọc-phân trang, tạo/đổi trạng thái/xóa mềm, chi tiết người dùng có đổi vai trò/khôi phục (lịch sử kiểm toán được quản lý tập trung tại `/admin/audit-logs`), cùng audit viewer lọc theo khoảng ngày Việt Nam/action/target và so sánh JSON trước-sau. Trang chủ chỉ hiện lối vào khi có `USER_READ`.
- Mọi lối vào module trên trang chủ phải được ẩn khi người dùng thiếu quyền đang bảo vệ route tương ứng; tiêu đề nhóm Tính năng Nâng cao cũng ẩn khi không có bất kỳ module con nào khả dụng. Lối vào Hồ sơ vẫn hiển thị vì route này không có permission riêng; lối vào Style Guide mặc định ẩn với vai trò hệ thống `USER` và hiển thị cho các vai trò còn lại.
- Contract Backend hiện chưa cho list hoặc đọc user đã soft-delete (`GET /users` và `GET /users/:id` đều loại `deletedAt != null`), nên nút restore trên detail đã sẵn sàng theo type/permission nhưng chỉ khả dụng khi Backend trả được bản ghi đã xóa.
-**Xuất sao kê giao dịch bất đồng bộ (Statement Exporter)**:
- Tuân thủ nghiêm ngặt quy chuẩn cấu trúc trang Mini App: Bọc toàn bộ trong `<Page className="page">`, `<Header title={t("statement.pageTitle")} showBackIcon onBackClick={() => navigate("/")} />`, `<IconGradients />`, và `<main className="mx-auto mt-4 flex w-full max-w-lg flex-col gap-5 pb-12">`. Tuyệt đối không dùng `<div>` trần để tránh mất header, che khuất thanh điều khiển và vỡ bố cục Zalo Mini App.
- Thẻ Hero Overview Claymorphic: Nền tím đậm (`!bg-clay-primary-dark !text-clay-on-primary`), bong bóng trang trí đa chiều, icon `ReportIcon` trong khối nổi 3D, số đếm tổng số sao kê và huy hiệu số lượng tác vụ đang xử lý nền.
- Lưới thông số giao dịch dạng ô gạch Claymorphism (`rounded-clay-sm bg-clay-bg p-2.5 shadow-clay-pressed`): Kỳ sao kê, Tên ví tài khoản thực tế (ánh xạ qua `walletMap`), Số lượng giao dịch và Dung lượng tệp. Không dùng raw emoji dạng văn bản trên giao diện.
- Trạng thái phản hồi (Feedback states): Sử dụng `StatementSkeleton` chuyên biệt mô phỏng trực quan cấu trúc thẻ đất sét thay cho skeleton khối trơn. Card trạng thái rỗng (Empty state) và lỗi (Error state) theo chuẩn chung với nút thử lại hoặc tạo mới.
- Modal tạo yêu cầu xuất sao kê `ExportStatementModal`: Sử dụng prop `footer` của base `Modal` cố định đáy, kiểm soát dữ liệu bằng Zod (`createExportSchema`) + React Hook Form, dùng `LocalizedDateInput` cho ngày bắt đầu/kết thúc chuẩn định dạng Việt Nam (`DD/MM/YYYY`), phím tắt khoảng ngày bo tròn (Pill presets), bộ chọn định dạng tệp 3D (Excel, PDF, CSV), và tùy chọn bảo vệ mật khẩu (PDF mã hóa, Excel khóa bảo vệ sheet).
- Tích hợp hook `useStatementJob` và `useStatementHistory` tự động polling trạng thái (mỗi 2.5s - 3s) khi có job ở trạng thái PENDING hoặc PROCESSING. Tải file an toàn qua blob request có đính kèm Bearer token tự động hoặc mở trực tiếp presigned Cloudflare R2 URL, có thông báo toast phản hồi thành công/thất bại qua `useSnackbar`.
- Route `/statements` được bảo vệ bởi `PermissionGate` với quyền `PERMISSIONS.STATEMENT_READ`. Nút điều hướng trên Trang chủ xuất hiện trong nhóm Mô-đun Tài chính Cốt lõi khi người dùng có quyền đọc. Tiêu đề tài liệu tự động cập nhật qua `DocumentTitle`.
- Đầy đủ hỗ trợ đa ngôn ngữ Việt - Anh qua `src/i18n/`.
"sameCurrencyNote":"Exchange rate for identical currencies"
},
"statement":{
"pageTitle":"Export Financial Statements",
"pageSubtitle":"Asynchronous export to Excel (4 sheets), Bank-standard PDF, and CSV",
"heroTitle":"Statement Overview",
"newExportBtn":"New Statement Export",
"bannerTitle":"Secure & Unlimited Export",
"bannerDesc":"Background asynchronous processing ensures zero server freeze or memory overflow. Completed files will notify via In-App and Zalo with download links valid for 48 hours.",
"historyTitle":"Export Request History",
"emptyTitle":"No statements yet",
"emptyDesc":"You haven't requested any statements yet. Click the button above to start.",
"createFirstExport":"Create First Export",
"exportModalTitle":"Export Financial Statement",
"selectWallet":"Wallet",
"allWallets":"All wallets",
"selectedWallet":"Selected wallet",
"dateRange":"Statement Period [From, To)",
"dateFrom":"From date",
"dateTo":"To date",
"presetThisMonth":"This month",
"presetLastMonth":"Last month",
"presetThisYear":"This year",
"presetLast365":"Last 365 days",
"format":"File Format",
"protectWithPassword":"Password Protect File",
"password":"Password protection",
"passwordHint":"Password hint",
"showPassword":"Show password",
"hidePassword":"Hide password",
"passwordPlaceholder":"Enter password (birthday / ID / PIN)",
"passwordHintPlaceholder":"Password hint (e.g. Birthday DDMMYYYY)",
"startExport":"Start Export",
"errorDateRange":"Start date must be before end date",
"errorPasswordLength":"Password must be at least 4 characters",
"simulations":"Mô phỏng Kịch bản Tài chính (What-if)",
"anomalies":"Phát hiện Chi tiêu Bất thường",
...
...
@@ -235,31 +236,32 @@
},
"home":{
"header":"FinWise Mini App",
"greeting":"Chào, {{name}}!",
"subtitle":"Sổ tay Chi tiêu & Báo cáo Tài chính",
"greeting":"Xin chào, {{name}}!",
"subtitle":"Quản lý Tài chính Thông minh & Dự báo Dòng tiền",
"account":"Tài khoản:",
"authTitle":"Xác thực hệ thống",
"authDescription":"Bạn đã đăng nhập thành công. Các API tiếp theo sẽ tự động được xác thực nhờ cookie an toàn và cơ chế JWT.",
"aiAssistant":"Trợ lý tài chính AI",
"smartQuery":"Truy vấn thông minh DSL",
"advancedFeatures":"Tính năng Phân tích Nâng cao",
"forecast":"Dự báo dòng tiền",
"simulations":"Mô phỏng kịch bản",
"anomalies":"Cảnh báo bất thường",
"subscriptions":"Hóa đơn định kỳ",
"coreModules":"Các mô-đun tài chính cốt lõi",
"wallets":"Quản lý ví",
"categories":"Quản lý danh mục thu chi",
"transactions":"Quản lý giao dịch thu chi",
"recurringTransactions":"Giao dịch tự động định kỳ",
"transfers":"Chuyển tiền giữa các ví",
"budgets":"Quản lý ngân sách chi tiêu",
"savingGoals":"Quản lý mục tiêu tiết kiệm",
"reports":"Báo cáo & phân tích tài chính",
"notifications":"Thông báo & nhắc nhở",
"authTitle":"Trạng thái Hoạt động",
"authDescription":"Hệ thống bảo mật xác thực với đa tài khoản và phân quyền dữ liệu độc lập.",
"aiAssistant":"Trợ lý FinWise AI",
"smartQuery":"Truy vấn Thông minh DSL",
"advancedFeatures":"Tính năng Nâng cao",
"forecast":"Dự báo Dòng tiền",
"simulations":"Mô phỏng What-if",
"anomalies":"Cảnh báo Bất thường",
"subscriptions":"Hóa đơn Định kỳ",
"recurringTransactions":"Giao dịch Tự động",
"coreModules":"Danh mục Nghiệp vụ",
"wallets":"Ví của tôi",
"transactions":"Giao dịch",
"transfers":"Chuyển tiền",
"budgets":"Ngân sách",
"savingGoals":"Mục tiêu Tiết kiệm",
"categories":"Danh mục Chi tiêu",
"reports":"Báo cáo",
"statements":"Xuất sao kê",
"notifications":"Thông báo",
"rolesManagement":"Phân quyền & Vai trò",
"profile":"Trang cá nhân của tôi",
"styleGuide":"Khám phá Style Guide"
"profile":"Cá nhân",
"styleGuide":"Hướng dẫn Giao diện"
},
"profile":{
"header":"Tài Khoản",
...
...
@@ -1589,33 +1591,6 @@
"extractFailed":"Không thể phân tích hóa đơn. Vui lòng kiểm tra ảnh và thử lại."
}
},
"home":{
"header":"FinWise Mini App",
"greeting":"Xin chào, {{name}}!",
"subtitle":"Quản lý Tài chính Thông minh & Dự báo Dòng tiền",
"account":"Tài khoản:",
"authTitle":"Trạng thái Hoạt động",
"authDescription":"Hệ thống bảo mật xác thực với đa tài khoản và phân quyền dữ liệu độc lập.",
"aiAssistant":"Trợ lý FinWise AI",
"smartQuery":"Truy vấn Thông minh DSL",
"advancedFeatures":"Tính năng Nâng cao",
"forecast":"Dự báo Dòng tiền",
"simulations":"Mô phỏng What-if",
"anomalies":"Cảnh báo Bất thường",
"subscriptions":"Hóa đơn Định kỳ",
"recurringTransactions":"Giao dịch Tự động",
"coreModules":"Danh mục Nghiệp vụ",
"wallets":"Ví của tôi",
"transactions":"Giao dịch",
"transfers":"Chuyển tiền",
"budgets":"Ngân sách",
"savingGoals":"Mục tiêu Tiết kiệm",
"categories":"Danh mục Chi tiêu",
"reports":"Báo cáo",
"notifications":"Thông báo",
"profile":"Cá nhân",
"styleGuide":"Hướng dẫn Giao diện"
},
"forecast":{
"pageTitle":"Dự báo Dòng tiền & Ngân sách",
"selectHorizon":"Khoảng thời gian dự báo",
...
...
@@ -2414,6 +2389,59 @@
"estimated":"Cơ sở",
"refreshRate":"Cập nhật tỷ giá thị trường",
"sameCurrencyNote":"Tỷ giá giữa cùng một loại tiền tệ"
},
"statement":{
"pageTitle":"Xuất sao kê giao dịch",
"pageSubtitle":"Kết xuất bất đồng bộ Excel (4 sheet), PDF chuẩn ngân hàng và CSV",
"heroTitle":"Tổng quan sao kê",
"newExportBtn":"Xuất sao kê mới",
"bannerTitle":"Kết xuất an toàn & Không giới hạn số lượng",
"bannerDesc":"Hệ thống xử lý bất đồng bộ trong nền, không gây nghẽn mạng hay tràn bộ nhớ. Tệp hoàn tất sẽ có thông báo In-app và tin nhắn Zalo kèm đường dẫn tải về trong 48 giờ.",
"historyTitle":"Lịch sử yêu cầu xuất sao kê",
"emptyTitle":"Chưa có sao kê nào",
"emptyDesc":"Bạn chưa tạo yêu cầu xuất sao kê nào. Hãy nhấn nút phía trên để bắt đầu.",
"createFirstExport":"Tạo sao kê đầu tiên",
"exportModalTitle":"Xuất sao kê giao dịch",
"selectWallet":"Ví tài khoản",
"allWallets":"Tất cả ví tài khoản",
"selectedWallet":"Ví được chọn",
"dateRange":"Kỳ sao kê [Từ ngày, Đến ngày)",
"dateFrom":"Từ ngày",
"dateTo":"Đến ngày",
"presetThisMonth":"Tháng này",
"presetLastMonth":"Tháng trước",
"presetThisYear":"Năm nay",
"presetLast365":"365 ngày qua",
"format":"Định dạng tệp",
"protectWithPassword":"Bảo vệ tệp bằng mật khẩu",
"password":"Mật khẩu bảo vệ",
"passwordHint":"Gợi ý nhớ mật khẩu",
"showPassword":"Hiện mật khẩu",
"hidePassword":"Ẩn mật khẩu",
"passwordPlaceholder":"Nhập mật khẩu (ngày sinh / CCCD / số bí mật)",
"passwordHintPlaceholder":"Gợi ý nhớ mật khẩu (VD: Ngày sinh DDMMYYYY)",
"startExport":"Bắt đầu xuất",
"errorDateRange":"Ngày bắt đầu phải trước ngày kết thúc",
"errorPasswordLength":"Mật khẩu bảo vệ phải từ 4 ký tự trở lên",
"createSuccess":"Đã khởi tạo yêu cầu xuất sao kê thành công",
"createFailed":"Không thể khởi tạo yêu cầu xuất sao kê",
"downloadSuccess":"Đã tải sao kê thành công",
"downloadFailed":"Không thể tải tệp sao kê",
"loadFailed":"Không thể tải danh sách sao kê",
"activeCount":"{{count}} yêu cầu đang xử lý",
"statusPending":"Đang chờ",
"statusProcessing":"Đang tạo file...",
"statusCompleted":"Đã sẵn sàng",
"statusFailed":"Thất bại",
"period":"Kỳ sao kê",
"wallet":"Ví",
"recordCount":"Số giao dịch",
"fileSize":"Dung lượng",
"createdAt":"Thời gian tạo",
"expiresIn48h":"Hết hạn sau 48h",
"downloadFile":"Tải xuống",
"processingNotice":"Hệ thống đang trích xuất dữ liệu, vui lòng đợi trong giây lát...",
"failedNotice":"Xử lý thất bại. Vui lòng thử lại sau."