diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/AdminSettings.razor b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/AdminSettings.razor new file mode 100644 index 00000000..5ba2261f --- /dev/null +++ b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/AdminSettings.razor @@ -0,0 +1,227 @@ +@page "/admin/settings" +@layout AdminLayout +@inherits AdminBase +@inject WebClientTpos.Client.Services.AuthStateService AuthState +@inject WebClientTpos.Client.Services.PosDataService DataService + +@* + EN: Admin settings page — account info, shop overview, general settings. + VI: Trang cài đặt admin — thông tin tài khoản, tổng quan cửa hàng, cài đặt chung. +*@ + +Cài đặt — GoodGo Admin + +@* ═══ TOP BAR ═══ *@ +
+
+

Cài đặt

+

Quản lý tài khoản và cấu hình hệ thống

+
+
+ +@* ═══ TABS ═══ *@ +
+ + + + +
+ +@* ═══ CONTENT ═══ *@ +
+ + @if (_tab == "general") + { + @* ── System Info ── *@ +
+
+

+ + Thông tin hệ thống +

+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + @* ── Service Health ── *@ +
+
+

+ + Trạng thái dịch vụ +

+
+
+ @foreach (var svc in _services) + { +
+
+ + Online +
+ @svc +
+ } +
+
+ } + else if (_tab == "account") + { +
+
+

+ + Thông tin tài khoản +

+
+
+
+
+ + +
+
+ + +
+
+
+ +
+ + Đã xác thực +
+
+
+
+ } + else if (_tab == "notifications") + { +
+
+

+ + Cài đặt thông báo +

+
+
+ @foreach (var notif in _notifSettings) + { +
+
+ @notif.Label + @notif.Desc +
+ +
+ } +
+
+ } + else if (_tab == "security") + { +
+
+

+ + Bảo mật +

+
+
+
+
+ Đổi mật khẩu +

Thay đổi mật khẩu đăng nhập

+
+ +
+
+
+ Xác thực 2 bước (2FA) +

Bảo mật tài khoản bằng mã OTP

+
+ +
+
+
+ Phiên đăng nhập +

Quản lý các phiên đang hoạt động

+
+ +
+
+
+ + @* ─── DANGER ZONE ─── *@ +
+
+

+ + Vùng nguy hiểm +

+
+
+
+
+ Xóa tài khoản +

Xóa vĩnh viễn tài khoản và toàn bộ dữ liệu

+
+ +
+
+
+ } +
+ +@code { + private string _tab = "general"; + private int _shopCount = 0; + + private readonly string[] _services = { "API Gateway", "IAM Service", "Merchant Service", "Catalog Service", "Order Service" }; + + protected override async Task OnInitializedAsync() + { + try + { + var shops = await DataService.GetShopsAsync(); + _shopCount = shops.Count; + } + catch { _shopCount = 0; } + } + + private record NotifSetting(string Label, string Desc, bool Enabled); + private readonly NotifSetting[] _notifSettings = new[] + { + new NotifSetting("Đơn hàng mới", "Thông báo khi có đơn hàng mới", true), + new NotifSetting("Đơn hàng hủy", "Thông báo khi đơn hàng bị hủy", true), + new NotifSetting("Hàng sắp hết", "Tồn kho dưới mức tối thiểu", true), + new NotifSetting("Nhân viên check-in", "Nhân viên bắt đầu ca", false), + new NotifSetting("Doanh thu bất thường", "Cảnh báo doanh thu", true), + new NotifSetting("Email hàng tuần", "Báo cáo doanh thu hàng tuần", true), + }; +} diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Staff/RolePermissions.razor b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Staff/RolePermissions.razor index 751cc6ca..15fe4836 100644 --- a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Staff/RolePermissions.razor +++ b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Staff/RolePermissions.razor @@ -1,11 +1,12 @@ @page "/admin/roles" @layout AdminLayout @inherits AdminBase +@inject WebClientTpos.Client.Services.IamApiService IamService +@using WebClientTpos.Client.Services @* - EN: Role permissions — manage roles and their POS/admin permissions. - VI: Phân quyền — quản lý vai trò và quyền hạn POS/admin. - Design: pencil-design/src/pages/tPOS/admin/role-permissions.pen + EN: Role permissions — real roles from IAM Service API. + VI: Phân quyền — dữ liệu roles thật từ IAM Service API. *@ Phân quyền — GoodGo Admin @@ -25,49 +26,91 @@ @* ═══ CONTENT ═══ *@ -
- - @* LEFT: Roles list *@ -
- @foreach (var role in _roles) - { - var r = role; - - } +@if (_loading) +{ +
+
- - @* RIGHT: Permissions grid *@ -
-
-
-

- - Quyền hạn — @(_roles.FirstOrDefault(r => r.Key == _selectedRole)?.Name ?? "") -

- -
-
- @foreach (var group in _permissionGroups) - { -
-
- @group.GroupName +} +else if (!_roles.Any()) +{ +
+ + Chưa có vai trò nào + Vai trò sẽ được tạo khi hệ thống IAM hoạt động +
+} +else +{ +
+ @* LEFT: Roles list *@ +
+ @foreach (var role in _roles) + { + + } +
+ + @* RIGHT: Role Details *@ +
+ @{ var selected = _roles.FirstOrDefault(r => r.Id == _selectedId); } + @if (selected != null) + { +
+
+

+ + Chi tiết — @selected.Name +

+
+
+
+
+
@selected.Name
+
Tên vai trò
+
+
+
@(selected.UserCount ?? 0)
+
Số người dùng
+
+
+
@(selected.IsSystem ? "Có" : "Không")
+
Vai trò hệ thống
+
+
+
@selected.CreatedAt.ToString("dd/MM/yy")
+
Ngày tạo
+
+
+ + @if (!string.IsNullOrEmpty(selected.Description)) + { +
+ Mô tả +

@selected.Description

+
+ } + + @* Permission Groups — placeholder since IAM doesn't expose permissions yet *@ +
+ Quyền hạn +
+ @foreach (var perm in _defaultPermissions) {
@@ -78,49 +121,55 @@
}
- } -
+
+ }
-
+} @code { - private string _selectedRole = "manager"; + private bool _loading = true; + private Guid? _selectedId; + private List _roles = new(); - private record RoleDef(string Key, string Name, string Icon, string Color, string Count); - private readonly RoleDef[] _roles = new[] + protected override async Task OnInitializedAsync() { - new RoleDef("owner", "Chủ sở hữu", "crown", "#FF5C00", "1"), - new RoleDef("manager", "Quản lý", "user-check", "#8B5CF6", "2"), - new RoleDef("cashier", "Thu ngân", "calculator", "#3B82F6", "3"), - new RoleDef("barista", "Barista", "coffee", "#22C55E", "2"), - new RoleDef("server", "Phục vụ", "hand", "#EC4899", "2"), + try { _roles = await IamService.GetRolesAsync(); } + catch { _roles = new(); } + finally + { + _selectedId = _roles.FirstOrDefault()?.Id; + _loading = false; + } + } + + private static string GetRoleColor(string name) => name.ToLower() switch + { + "admin" or "administrator" => "#FF5C00", + "merchant" or "owner" => "#8B5CF6", + "cashier" => "#3B82F6", + "manager" => "#22C55E", + _ => "#6B7280" }; - private record Permission(string Label, string Desc, bool Allowed); - private record PermissionGroup(string GroupName, Permission[] Permissions); - private readonly PermissionGroup[] _permissionGroups = new[] + private static string GetRoleIcon(string name) => name.ToLower() switch { - new PermissionGroup("POS", new[] - { - new Permission("Tạo đơn hàng", "Tạo và xử lý đơn hàng", true), - new Permission("Áp dụng giảm giá", "Giảm giá cho đơn hàng", true), - new Permission("Hủy đơn hàng", "Hủy đơn đã tạo", true), - new Permission("Hoàn tiền", "Hoàn tiền cho khách", false), - new Permission("Đóng ca", "Kết thúc ca bán hàng", true), - }), - new PermissionGroup("QUẢN LÝ", new[] - { - new Permission("Xem báo cáo", "Truy cập báo cáo doanh thu", true), - new Permission("Quản lý nhân sự", "Thêm/sửa/xóa nhân viên", false), - new Permission("Quản lý sản phẩm", "Thêm/sửa/xóa sản phẩm", true), - new Permission("Quản lý kho", "Nhập/xuất kho hàng", true), - }), - new PermissionGroup("HỆ THỐNG", new[] - { - new Permission("Cài đặt cửa hàng", "Thay đổi thông tin cửa hàng", false), - new Permission("Quản lý thiết bị", "Thêm/xóa thiết bị POS", false), - new Permission("Xem audit log", "Xem lịch sử thao tác", true), - }), + "admin" or "administrator" => "crown", + "merchant" or "owner" => "user-check", + "cashier" => "calculator", + "manager" => "briefcase", + _ => "shield" + }; + + // EN: Default permission display (placeholder until IAM exposes permissions API) + // VI: Hiển thị quyền mặc định (placeholder cho đến khi IAM cung cấp API permissions) + private record PermDef(string Label, string Desc, bool Allowed); + private readonly PermDef[] _defaultPermissions = new[] + { + new PermDef("Tạo đơn hàng", "Tạo và xử lý đơn hàng", true), + new PermDef("Áp dụng giảm giá", "Giảm giá cho đơn hàng", true), + new PermDef("Xem báo cáo", "Truy cập báo cáo doanh thu", false), + new PermDef("Quản lý nhân sự", "Thêm/sửa/xóa nhân viên", false), + new PermDef("Cài đặt cửa hàng", "Thay đổi thông tin cửa hàng", false), }; } diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Staff/StaffDirectory.razor b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Staff/StaffDirectory.razor index 7fd6c1da..36d0a6f2 100644 --- a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Staff/StaffDirectory.razor +++ b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Staff/StaffDirectory.razor @@ -1,11 +1,12 @@ @page "/admin/staff" @layout AdminLayout @inherits AdminBase +@inject WebClientTpos.Client.Services.PosDataService DataService +@using WebClientTpos.Client.Services @* - EN: Staff directory — grid of staff cards with search, filter by store/role. - VI: Danh bạ nhân sự — grid thẻ nhân viên, tìm kiếm, lọc theo cửa hàng/vai trò. - Design: pencil-design/src/pages/tPOS/admin/staff-directory.pen + EN: Staff directory — real data from BFF, grid of staff cards with search & filter. + VI: Danh bạ nhân sự — dữ liệu thật từ BFF, grid thẻ nhân viên, tìm kiếm & lọc. *@ Quản lý nhân sự — GoodGo Admin @@ -19,12 +20,8 @@
- - +
-@* ═══ STAFF GRID ═══ *@ -
- @foreach (var staff in _staffList) - { -
-
-
- @staff.Initials +@* ═══ CONTENT ═══ *@ +@if (_loading) +{ +
+ +
+} +else if (!FilteredStaff.Any()) +{ +
+ + Chưa có nhân viên nào + +
+} +else +{ +
+ @foreach (var staff in FilteredStaff) + { +
+
+
+ @GetInitials(staff.Email ?? staff.EmployeeCode ?? "?") +
+
+ + @GetStatusLabel(staff.Status) +
-
- - @(staff.Status == "active" ? "Đang ca" : "Nghỉ") +
+ @(staff.Email ?? staff.EmployeeCode ?? "Nhân viên") + @(staff.Role ?? "—") • @(staff.ShopName ?? "Chưa gán") +
+
+
+
@(staff.EmployeeCode ?? "—")
+
Mã NV
+
+
+
@(staff.Phone ?? "—")
+
Điện thoại
+
+
+
@FormatDate(staff.JoinedAt)
+
Ngày vào
+
-
- @staff.Name - @staff.Role • @staff.Store -
-
-
-
@staff.Hours
-
Giờ/tuần
-
-
-
@staff.Orders
-
Đơn hôm nay
-
-
-
@staff.Rating
-
Đánh giá
-
-
-
- } -
+ } +
+} @code { + private bool _loading = true; private string _activeTab = "all"; + private List _staffList = new(); - private record StaffMember(string Id, string Name, string Initials, string Role, string Store, string Status, string AvatarColor, string Hours, string Orders, string Rating); - private readonly List _staffList = new() + private IEnumerable FilteredStaff { - new("1", "Trần Minh Đức", "TM", "Barista", "Coffee House Q1", "active", "#FF5C00", "42", "28", "4.8"), - new("2", "Lê Thị Thảo", "LT", "Thu ngân", "Coffee House Q1", "active", "#3B82F6", "38", "45", "4.9"), - new("3", "Nguyễn Hà My", "NH", "Phục vụ", "Nhà hàng Q3", "active", "#22C55E", "40", "32", "4.7"), - new("4", "Phạm Văn An", "PA", "Quản lý", "Nhà hàng Q3", "active", "#8B5CF6", "45", "0", "4.6"), - new("5", "Hoàng Thị Lan", "HL", "Barista", "Coffee House Q1", "off", "#EC4899", "0", "0", "4.5"), - new("6", "Võ Đình Khoa", "VK", "Bếp trưởng", "Nhà hàng Q3", "active", "#F59E0B", "44", "18", "4.9"), - new("7", "Đặng Phương Linh", "ĐL", "Phục vụ", "Nhà hàng Q3", "active", "#06B6D4", "36", "22", "4.4"), - new("8", "Bùi Thanh Tùng", "BT", "Thu ngân", "Café Thủ Đức", "active", "#3B82F6", "38", "30", "4.7"), + get + { + var list = _activeTab switch + { + "active" => _staffList.Where(s => s.Status == "Active"), + "invited" => _staffList.Where(s => s.Status == "Invited"), + "inactive" => _staffList.Where(s => s.Status == "Inactive" || s.Status == "Terminated"), + _ => _staffList + }; + if (!string.IsNullOrEmpty(SearchQuery)) + list = list.Where(s => (s.Email ?? "").Contains(SearchQuery, StringComparison.OrdinalIgnoreCase) + || (s.EmployeeCode ?? "").Contains(SearchQuery, StringComparison.OrdinalIgnoreCase) + || (s.Phone ?? "").Contains(SearchQuery, StringComparison.OrdinalIgnoreCase) + || (s.ShopName ?? "").Contains(SearchQuery, StringComparison.OrdinalIgnoreCase)); + return list; + } + } + + protected override async Task OnInitializedAsync() + { + try { _staffList = await DataService.GetStaffAsync(); } + catch { _staffList = new(); } + finally { _loading = false; } + } + + private static string GetInitials(string value) + { + if (value.Contains('@')) value = value.Split('@')[0]; + return value.Length >= 2 ? value[..2].ToUpper() : value.ToUpper(); + } + + private static string GetAvatarColor(string role) => role switch + { + "Cashier" => "#3B82F6", + "Waiter" => "#22C55E", + "Manager" => "#8B5CF6", + "Admin" => "#FF5C00", + _ => "#6B7280" }; + + private static string GetStatusCss(string? status) => status switch + { + "Active" => "admin-status-badge--online", + "Invited" => "admin-status-badge--setup", + _ => "admin-status-badge--offline" + }; + + private static string GetStatusLabel(string? status) => status switch + { + "Active" => "Đang làm", + "Invited" => "Chờ xác nhận", + "Inactive" => "Tạm nghỉ", + "Terminated" => "Đã nghỉ", + _ => status ?? "—" + }; + + private static string FormatDate(DateTime? dt) => dt.HasValue ? dt.Value.ToString("dd/MM/yy") : "—"; } diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/SystemAdmin/AuditLog.razor b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/SystemAdmin/AuditLog.razor index d924d761..dfff8eca 100644 --- a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/SystemAdmin/AuditLog.razor +++ b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/SystemAdmin/AuditLog.razor @@ -1,11 +1,12 @@ @page "/admin/system/audit" @layout AdminLayout @inherits AdminBase +@inject WebClientTpos.Client.Services.IamApiService IamService +@using WebClientTpos.Client.Services @* - EN: Audit log — action log table with timestamp, user, action, resource, status. Search + filter by action type, date range, user. - VI: Nhật ký hệ thống — bảng log thao tác với thời gian, người dùng, hành động, tài nguyên, trạng thái. Tìm kiếm + lọc. - Design: pencil-design/src/pages/tPOS/admin/audit-log.pen + EN: Audit log — real audit log data from IAM Service. + VI: Nhật ký hệ thống — dữ liệu audit log thật từ IAM Service. *@ Nhật ký hệ thống — GoodGo Admin @@ -19,15 +20,11 @@
-
@@ -38,85 +35,144 @@ Tất cả @_logs.Count
-@* ═══ LOG TABLE ═══ *@ -
-
-
- - - - - - - - - - - - - @foreach (var log in FilteredLogs) - { +@* ═══ CONTENT ═══ *@ +@if (_loading) +{ +
+ +
+} +else if (!FilteredLogs.Any()) +{ +
+ + Chưa có log nào + Log sẽ xuất hiện khi có hoạt động trong hệ thống +
+} +else +{ +
+
+
+
Thời gianNgười dùngHành độngTài nguyênIPTrạng thái
+ - - - - - - + + + + + + - } - -
@log.Timestamp -
-
@log.Initials
- @log.User -
-
-
- - @log.Action -
-
@log.Resource@log.IP -
- - @(log.Status == "success" ? "Thành công" : "Thất bại") -
-
Thời gianNgười dùngHành độngTài nguyênIPTrạng thái
+ + + @foreach (var log in FilteredLogs) + { + + @FormatTime(log.Timestamp) + +
+
+ @GetInitials(log.ActorName ?? log.ActorId ?? "?") +
+ @(log.ActorName ?? log.ActorId ?? "System") +
+ + +
+ + @(log.EventType ?? "—") +
+ + @(log.ResourceType ?? "—") @(log.ResourceId != null ? $"#{log.ResourceId[..Math.Min(8, log.ResourceId.Length)]}" : "") + @(log.IpAddress ?? "—") + +
+ + @(log.Status == "Success" ? "Thành công" : log.Status ?? "—") +
+ + + } + + +
-
+} @code { + private bool _loading = true; private string _activeTab = "all"; + private List _logs = new(); - private List FilteredLogs => _activeTab == "all" - ? _logs - : _logs.Where(l => l.Category == _activeTab).ToList(); - - private record LogEntry(string Timestamp, string User, string Initials, string AvatarColor, string Action, - string ActionIcon, string ActionColor, string Resource, string IP, string Status, string Category); - - private readonly List _logs = new() + private IEnumerable FilteredLogs { - new("12/02/2026 14:32:05", "Trần Minh Đức", "TM", "#FF5C00", "Đăng nhập", "log-in", "#22C55E", "Phiên làm việc", "192.168.1.45", "success", "auth"), - new("12/02/2026 14:28:11", "Admin System", "AS", "#8B5CF6", "Cập nhật giá", "edit", "#3B82F6", "Sản phẩm #142", "10.0.0.1", "success", "data"), - new("12/02/2026 14:15:30", "Lê Thị Thảo", "LT", "#3B82F6", "Tạo đơn hàng", "plus-circle", "#22C55E", "Đơn #2848", "192.168.1.50", "success", "data"), - new("12/02/2026 13:55:22", "Phạm Văn An", "PA", "#22C55E", "Thay đổi quyền", "shield", "#F59E0B", "Role: Barista", "192.168.1.12", "success", "config"), - new("12/02/2026 13:42:08", "Nguyễn Hà My", "NH", "#EC4899", "Đăng nhập thất bại", "log-in", "#EF4444", "Phiên làm việc", "192.168.1.88", "failed", "auth"), - new("12/02/2026 13:30:00", "Admin System", "AS", "#8B5CF6", "Backup dữ liệu", "database", "#3B82F6", "DB chính", "10.0.0.1", "success", "config"), - new("12/02/2026 12:18:45", "Võ Đình Khoa", "VK", "#F59E0B", "Nhập kho", "package", "#22C55E", "Kho Coffee Q1", "192.168.1.33", "success", "data"), - new("12/02/2026 11:55:12", "Trần Minh Đức", "TM", "#FF5C00", "Cập nhật cài đặt", "settings", "#F59E0B", "Cửa hàng Q1", "192.168.1.45", "success", "config"), - new("12/02/2026 10:30:00", "Lê Thị Thảo", "LT", "#3B82F6", "Hủy đơn hàng", "x-circle", "#EF4444", "Đơn #2845", "192.168.1.50", "success", "data"), - new("12/02/2026 09:15:33", "Phạm Văn An", "PA", "#22C55E", "Đăng nhập", "log-in", "#22C55E", "Phiên làm việc", "192.168.1.12", "success", "auth"), + get + { + var list = _activeTab == "all" ? _logs.AsEnumerable() : _logs.Where(l => IsCategory(l, _activeTab)); + if (!string.IsNullOrEmpty(SearchQuery)) + list = list.Where(l => (l.EventType ?? "").Contains(SearchQuery, StringComparison.OrdinalIgnoreCase) + || (l.ActorName ?? "").Contains(SearchQuery, StringComparison.OrdinalIgnoreCase) + || (l.ResourceType ?? "").Contains(SearchQuery, StringComparison.OrdinalIgnoreCase)); + return list; + } + } + + protected override async Task OnInitializedAsync() + { + try { _logs = await IamService.GetAuditLogsAsync(); } + catch { _logs = new(); } + finally { _loading = false; } + } + + private static bool IsCategory(IamApiService.AuditLogDto log, string cat) + { + var evt = (log.EventType ?? "").ToLower(); + return cat switch + { + "auth" => evt.Contains("login") || evt.Contains("auth") || evt.Contains("token") || evt.Contains("password"), + "config" => evt.Contains("config") || evt.Contains("setting") || evt.Contains("role") || evt.Contains("permission"), + "data" => evt.Contains("create") || evt.Contains("update") || evt.Contains("delete") || evt.Contains("order"), + _ => true + }; + } + + private static string FormatTime(DateTime? dt) => dt?.ToString("dd/MM/yyyy HH:mm:ss") ?? "—"; + + private static string GetInitials(string v) + { + if (v.Contains('@')) v = v.Split('@')[0]; + return v.Length >= 2 ? v[..2].ToUpper() : v.ToUpper(); + } + + private static string GetEventIcon(string? eventType) => (eventType ?? "").ToLower() switch + { + var e when e.Contains("login") => "log-in", + var e when e.Contains("create") => "plus-circle", + var e when e.Contains("update") => "edit", + var e when e.Contains("delete") => "trash-2", + var e when e.Contains("role") => "shield", + _ => "activity" + }; + + private static string GetEventColor(string? eventType) => (eventType ?? "").ToLower() switch + { + var e when e.Contains("login") => "#22C55E", + var e when e.Contains("create") => "#3B82F6", + var e when e.Contains("update") => "#F59E0B", + var e when e.Contains("delete") => "#EF4444", + _ => "#8B5CF6" }; }