From 0286e6e217b5ca602b708d02550f5e7af336d2c6 Mon Sep 17 00:00:00 2001 From: phuongdm Date: Fri, 3 Apr 2026 17:01:33 +0700 Subject: [PATCH] fix bug in delete file and add user crud --- Users-API.md | 74 ++++ src/components/cards/room-management-card.tsx | 4 +- src/components/dialogs/select-dialog.tsx | 10 +- src/config/api.ts | 6 +- src/hooks/queries/useAppVersionQueries.ts | 2 +- src/hooks/queries/useUserQueries.ts | 52 ++- src/routeTree.gen.ts | 22 ++ src/routes/_auth/apps/index.tsx | 13 +- .../user/change-password/$userName/index.tsx | 3 + src/routes/_auth/user/create/index.tsx | 38 +- .../_auth/user/edit/$userName/index.tsx | 361 ++++++++++++++++++ src/routes/_auth/user/index.tsx | 128 ++++--- src/routes/_auth/user/role/$roleId/index.tsx | 2 +- src/services/app-version.service.ts | 9 +- src/services/user.service.ts | 47 ++- src/template/dashboard-template.tsx | 4 +- src/types/app-sidebar.ts | 21 +- src/types/permission.ts | 5 + src/types/user-profile.ts | 30 ++ 19 files changed, 730 insertions(+), 101 deletions(-) create mode 100644 Users-API.md create mode 100644 src/routes/_auth/user/edit/$userName/index.tsx diff --git a/Users-API.md b/Users-API.md new file mode 100644 index 0000000..bc37164 --- /dev/null +++ b/Users-API.md @@ -0,0 +1,74 @@ +# User API + +Tai lieu mo ta cac endpoint cap nhat role va thong tin nguoi dung. + +---------------------------------------- + +## 1) Cap nhat thong tin nguoi dung +- PUT /api/User/{id} +- Permission: EDIT_USER_ROLE + +### Request +```json +{ + "name": "Nguyen Van A", + "userName": "nguyenvana", + "accessRooms": [1, 2, 3] +} +``` + +### Response (200) +```json +{ + "success": true, + "message": "User updated successfully", + "data": { + "userId": 12, + "userName": "nguyenvana", + "name": "Nguyen Van A", + "roleId": 3, + "accessRooms": [1, 2, 3], + "updatedAt": "2026-04-03T10:20:30Z", + "updatedBy": "admin" + } +} +``` + +### Ghi chu +- Neu khong truyen `accessRooms` thi giu nguyen danh sach phong. +- Neu truyen `accessRooms` = [] thi xoa tat ca phong. +- Neu `userName` bi trung hoac khong hop le thi tra ve 400. + +---------------------------------------- + +## 2) Cap nhat role nguoi dung +- PUT /api/User/{id}/role +- Permission: EDIT_USER_ROLE + +### Request +```json +{ + "roleId": 2 +} +``` + +### Response (200) +```json +{ + "success": true, + "message": "User role updated", + "data": { + "userId": 12, + "userName": "nguyenvana", + "roleId": 2, + "roleName": "Manager", + "updatedAt": "2026-04-03T10:20:30Z", + "updatedBy": "admin" + } +} +``` + +### Ghi chu +- Chi System Admin moi duoc phep cap nhat role System Admin. + +---------------------------------------- diff --git a/src/components/cards/room-management-card.tsx b/src/components/cards/room-management-card.tsx index e881449..f1e0502 100644 --- a/src/components/cards/room-management-card.tsx +++ b/src/components/cards/room-management-card.tsx @@ -22,7 +22,7 @@ export function RoomManagementCard({ Quản lý phòng - Thông tin tổng quan và phòng cần chú ý + Thông tin tổng quan và các phòng đang không sử dụng @@ -47,7 +47,7 @@ export function RoomManagementCard({
-
Phòng cần chú ý
+
Phòng không dùng
{data?.roomsNeedAttention && data.roomsNeedAttention.length > 0 ? ( data.roomsNeedAttention.map((r: RoomHealthStatus) => ( diff --git a/src/components/dialogs/select-dialog.tsx b/src/components/dialogs/select-dialog.tsx index 3da79c1..18b4876 100644 --- a/src/components/dialogs/select-dialog.tsx +++ b/src/components/dialogs/select-dialog.tsx @@ -2,7 +2,7 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/u import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Checkbox } from "@/components/ui/checkbox"; -import { useState, useMemo } from "react"; +import { useEffect, useMemo, useState } from "react"; export interface SelectItem { label: string; @@ -16,6 +16,7 @@ interface SelectDialogProps { description?: string; icon?: React.ReactNode; items: SelectItem[]; + selectedValues?: string[]; onConfirm: (values: string[]) => Promise | void; } @@ -26,11 +27,18 @@ export function SelectDialog({ description, icon, items, + selectedValues, onConfirm, }: SelectDialogProps) { const [selected, setSelected] = useState([]); const [search, setSearch] = useState(""); + useEffect(() => { + if (!open) return; + if (!selectedValues) return; + setSelected(selectedValues); + }, [open, selectedValues]); + const filteredItems = useMemo(() => { return items.filter((item) => item.label.toLowerCase().includes(search.toLowerCase()) diff --git a/src/config/api.ts b/src/config/api.ts index 3cb5d24..0d8dc78 100644 --- a/src/config/api.ts +++ b/src/config/api.ts @@ -21,6 +21,10 @@ export const API_ENDPOINTS = { CREATE_ACCOUNT: `${BASE_URL}/auth/create-account`, GET_USERS_LIST: `${BASE_URL}/users-info`, }, + USER: { + UPDATE_INFO: (id: number) => `${BASE_URL}/User/${id}`, + UPDATE_ROLE: (id: number) => `${BASE_URL}/User/${id}/role`, + }, APP_VERSION: { //agent and app api GET_VERSION: `${BASE_URL}/AppVersion/version`, @@ -38,7 +42,7 @@ export const API_ENDPOINTS = { GET_REQUIRED_FILES: `${BASE_URL}/AppVersion/requirefiles`, ADD_REQUIRED_FILE: `${BASE_URL}/AppVersion/requirefile/add`, DELETE_REQUIRED_FILE: `${BASE_URL}/AppVersion/requirefile/delete`, - DELETE_FILES: (fileId: number) => `${BASE_URL}/AppVersion/delete/${fileId}`, + DELETE_FILES: `${BASE_URL}/AppVersion/delete`, }, DEVICE_COMM: { DOWNLOAD_FILES: (roomName: string) => `${BASE_URL}/DeviceComm/downloadfile/${roomName}`, diff --git a/src/hooks/queries/useAppVersionQueries.ts b/src/hooks/queries/useAppVersionQueries.ts index f74346f..cda27f7 100644 --- a/src/hooks/queries/useAppVersionQueries.ts +++ b/src/hooks/queries/useAppVersionQueries.ts @@ -176,7 +176,7 @@ export function useDeleteFile() { const queryClient = useQueryClient(); return useMutation({ - mutationFn: (fileId: number) => appVersionService.deleteFile(fileId), + mutationFn: (data: { MsiFileIds: number[] }) => appVersionService.deleteFile(data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: APP_VERSION_QUERY_KEYS.softwareList(), diff --git a/src/hooks/queries/useUserQueries.ts b/src/hooks/queries/useUserQueries.ts index 4592649..95d6e2d 100644 --- a/src/hooks/queries/useUserQueries.ts +++ b/src/hooks/queries/useUserQueries.ts @@ -1,6 +1,10 @@ -import { useQuery } from "@tanstack/react-query"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import * as userService from "@/services/user.service"; -import type { UserProfile } from "@/types/user-profile"; +import type { + UserProfile, + UpdateUserInfoRequest, + UpdateUserRoleRequest, +} from "@/types/user-profile"; const USER_QUERY_KEYS = { all: ["users"] as const, @@ -18,3 +22,47 @@ export function useGetUsersInfo(enabled = true) { staleTime: 5 * 60 * 1000, // 5 minutes }); } + +/** + * Hook để cập nhật thông tin người dùng + */ +export function useUpdateUserInfo() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: ({ + id, + data, + }: { + id: number; + data: UpdateUserInfoRequest; + }) => userService.updateUserInfo(id, data), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: USER_QUERY_KEYS.list(), + }); + }, + }); +} + +/** + * Hook để cập nhật role người dùng + */ +export function useUpdateUserRole() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: ({ + id, + data, + }: { + id: number; + data: UpdateUserRoleRequest; + }) => userService.updateUserRole(id, data), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: USER_QUERY_KEYS.list(), + }); + }, + }); +} diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts index d0acffe..8c9d499 100644 --- a/src/routeTree.gen.ts +++ b/src/routeTree.gen.ts @@ -30,6 +30,7 @@ import { Route as AuthProfileChangePasswordIndexRouteImport } from './routes/_au import { Route as AuthProfileUserNameIndexRouteImport } from './routes/_auth/profile/$userName/index' import { Route as authSsoCallbackIndexRouteImport } from './routes/(auth)/sso/callback/index' import { Route as AuthUserRoleRoleIdIndexRouteImport } from './routes/_auth/user/role/$roleId/index' +import { Route as AuthUserEditUserNameIndexRouteImport } from './routes/_auth/user/edit/$userName/index' import { Route as AuthUserChangePasswordUserNameIndexRouteImport } from './routes/_auth/user/change-password/$userName/index' import { Route as AuthRoomsRoomNameFolderStatusIndexRouteImport } from './routes/_auth/rooms/$roomName/folder-status/index' import { Route as AuthRoomsRoomNameConnectIndexRouteImport } from './routes/_auth/rooms/$roomName/connect/index' @@ -141,6 +142,12 @@ const AuthUserRoleRoleIdIndexRoute = AuthUserRoleRoleIdIndexRouteImport.update({ path: '/user/role/$roleId/', getParentRoute: () => AuthRoute, } as any) +const AuthUserEditUserNameIndexRoute = + AuthUserEditUserNameIndexRouteImport.update({ + id: '/user/edit/$userName/', + path: '/user/edit/$userName/', + getParentRoute: () => AuthRoute, + } as any) const AuthUserChangePasswordUserNameIndexRoute = AuthUserChangePasswordUserNameIndexRouteImport.update({ id: '/user/change-password/$userName/', @@ -189,6 +196,7 @@ export interface FileRoutesByFullPath { '/rooms/$roomName/connect': typeof AuthRoomsRoomNameConnectIndexRoute '/rooms/$roomName/folder-status': typeof AuthRoomsRoomNameFolderStatusIndexRoute '/user/change-password/$userName': typeof AuthUserChangePasswordUserNameIndexRoute + '/user/edit/$userName': typeof AuthUserEditUserNameIndexRoute '/user/role/$roleId': typeof AuthUserRoleRoleIdIndexRoute } export interface FileRoutesByTo { @@ -215,6 +223,7 @@ export interface FileRoutesByTo { '/rooms/$roomName/connect': typeof AuthRoomsRoomNameConnectIndexRoute '/rooms/$roomName/folder-status': typeof AuthRoomsRoomNameFolderStatusIndexRoute '/user/change-password/$userName': typeof AuthUserChangePasswordUserNameIndexRoute + '/user/edit/$userName': typeof AuthUserEditUserNameIndexRoute '/user/role/$roleId': typeof AuthUserRoleRoleIdIndexRoute } export interface FileRoutesById { @@ -243,6 +252,7 @@ export interface FileRoutesById { '/_auth/rooms/$roomName/connect/': typeof AuthRoomsRoomNameConnectIndexRoute '/_auth/rooms/$roomName/folder-status/': typeof AuthRoomsRoomNameFolderStatusIndexRoute '/_auth/user/change-password/$userName/': typeof AuthUserChangePasswordUserNameIndexRoute + '/_auth/user/edit/$userName/': typeof AuthUserEditUserNameIndexRoute '/_auth/user/role/$roleId/': typeof AuthUserRoleRoleIdIndexRoute } export interface FileRouteTypes { @@ -271,6 +281,7 @@ export interface FileRouteTypes { | '/rooms/$roomName/connect' | '/rooms/$roomName/folder-status' | '/user/change-password/$userName' + | '/user/edit/$userName' | '/user/role/$roleId' fileRoutesByTo: FileRoutesByTo to: @@ -297,6 +308,7 @@ export interface FileRouteTypes { | '/rooms/$roomName/connect' | '/rooms/$roomName/folder-status' | '/user/change-password/$userName' + | '/user/edit/$userName' | '/user/role/$roleId' id: | '__root__' @@ -324,6 +336,7 @@ export interface FileRouteTypes { | '/_auth/rooms/$roomName/connect/' | '/_auth/rooms/$roomName/folder-status/' | '/_auth/user/change-password/$userName/' + | '/_auth/user/edit/$userName/' | '/_auth/user/role/$roleId/' fileRoutesById: FileRoutesById } @@ -483,6 +496,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthUserRoleRoleIdIndexRouteImport parentRoute: typeof AuthRoute } + '/_auth/user/edit/$userName/': { + id: '/_auth/user/edit/$userName/' + path: '/user/edit/$userName' + fullPath: '/user/edit/$userName' + preLoaderRoute: typeof AuthUserEditUserNameIndexRouteImport + parentRoute: typeof AuthRoute + } '/_auth/user/change-password/$userName/': { id: '/_auth/user/change-password/$userName/' path: '/user/change-password/$userName' @@ -535,6 +555,7 @@ interface AuthRouteChildren { AuthRoomsRoomNameConnectIndexRoute: typeof AuthRoomsRoomNameConnectIndexRoute AuthRoomsRoomNameFolderStatusIndexRoute: typeof AuthRoomsRoomNameFolderStatusIndexRoute AuthUserChangePasswordUserNameIndexRoute: typeof AuthUserChangePasswordUserNameIndexRoute + AuthUserEditUserNameIndexRoute: typeof AuthUserEditUserNameIndexRoute AuthUserRoleRoleIdIndexRoute: typeof AuthUserRoleRoleIdIndexRoute } @@ -561,6 +582,7 @@ const AuthRouteChildren: AuthRouteChildren = { AuthRoomsRoomNameFolderStatusIndexRoute, AuthUserChangePasswordUserNameIndexRoute: AuthUserChangePasswordUserNameIndexRoute, + AuthUserEditUserNameIndexRoute: AuthUserEditUserNameIndexRoute, AuthUserRoleRoleIdIndexRoute: AuthUserRoleRoleIdIndexRoute, } diff --git a/src/routes/_auth/apps/index.tsx b/src/routes/_auth/apps/index.tsx index 084e8f8..355c810 100644 --- a/src/routes/_auth/apps/index.tsx +++ b/src/routes/_auth/apps/index.tsx @@ -137,11 +137,10 @@ function AppsComponent() { return; } + const MsiFileIds = selectedRows.map((row: any) => row.original.id); + try { - for (const row of selectedRows) { - const { id } = row.original; - await deleteMutation.mutateAsync(id); - } + await deleteMutation.mutateAsync({ MsiFileIds }); toast.success("Xóa phần mềm thành công!"); } catch (e) { toast.error("Xóa phần mềm thất bại!"); @@ -175,12 +174,10 @@ function AppsComponent() { if (!table) return; const selectedRows = table.getSelectedRowModel().rows; + const MsiFileIds = selectedRows.map((row: any) => row.original.id); try { - for (const row of selectedRows) { - const { id } = row.original; - await deleteMutation.mutateAsync(id); - } + await deleteMutation.mutateAsync({ MsiFileIds }); toast.success("Xóa phần mềm từ server thành công!"); if (table) { table.setRowSelection({}); diff --git a/src/routes/_auth/user/change-password/$userName/index.tsx b/src/routes/_auth/user/change-password/$userName/index.tsx index fbb9e31..3e1b24d 100644 --- a/src/routes/_auth/user/change-password/$userName/index.tsx +++ b/src/routes/_auth/user/change-password/$userName/index.tsx @@ -9,6 +9,9 @@ import { LoaderCircle } from "lucide-react"; import { useState } from "react"; export const Route = createFileRoute("/_auth/user/change-password/$userName/")({ + head: () => ({ + meta: [{ title: "Thay đổi mật khẩu" }], + }), component: AdminChangePasswordComponent, loader: async ({ context, params }) => { context.breadcrumbs = [ diff --git a/src/routes/_auth/user/create/index.tsx b/src/routes/_auth/user/create/index.tsx index 2470107..33af235 100644 --- a/src/routes/_auth/user/create/index.tsx +++ b/src/routes/_auth/user/create/index.tsx @@ -22,6 +22,9 @@ import { UserPlus, ArrowLeft, Save, Loader2 } from "lucide-react"; import { toast } from "sonner"; export const Route = createFileRoute("/_auth/user/create/")({ + head: () => ({ + meta: [{ title: "Tạo người dùng mới" }], + }), component: CreateUserComponent, loader: async ({ context }) => { context.breadcrumbs = [ @@ -59,7 +62,8 @@ function CreateUserComponent() { if (!formData.userName) { newErrors.userName = "Tên đăng nhập không được để trống"; } else if (!validateUserName(formData.userName)) { - newErrors.userName = "Tên đăng nhập chỉ cho phép chữ cái, số, dấu chấm và gạch dưới (3-20 ký tự)"; + newErrors.userName = + "Tên đăng nhập chỉ cho phép chữ cái, số, dấu chấm và gạch dưới (3-20 ký tự)"; } // Validate name @@ -106,7 +110,8 @@ function CreateUserComponent() { toast.success("Tạo tài khoản thành công!"); navigate({ to: "/dashboard" }); // TODO: Navigate to user list page when it exists } catch (error: any) { - const errorMessage = error.response?.data?.message || "Tạo tài khoản thất bại!"; + const errorMessage = + error.response?.data?.message || "Tạo tài khoản thất bại!"; toast.error(errorMessage); } }; @@ -128,15 +133,14 @@ function CreateUserComponent() { {/* Header */}
-

Tạo người dùng mới

+

+ Tạo người dùng mới +

Thêm tài khoản người dùng mới vào hệ thống

- @@ -164,7 +168,9 @@ function CreateUserComponent() { handleInputChange("userName", e.target.value)} + onChange={(e) => + handleInputChange("userName", e.target.value) + } placeholder="Nhập tên đăng nhập (3-20 ký tự, chỉ chữ, số, . và _)" disabled={createMutation.isPending} className="h-10" @@ -202,7 +208,9 @@ function CreateUserComponent() { id="password" type="password" value={formData.password} - onChange={(e) => handleInputChange("password", e.target.value)} + onChange={(e) => + handleInputChange("password", e.target.value) + } placeholder="Nhập mật khẩu (tối thiểu 6 ký tự)" disabled={createMutation.isPending} className="h-10" @@ -220,13 +228,17 @@ function CreateUserComponent() { id="confirmPassword" type="password" value={formData.confirmPassword} - onChange={(e) => handleInputChange("confirmPassword", e.target.value)} + onChange={(e) => + handleInputChange("confirmPassword", e.target.value) + } placeholder="Nhập lại mật khẩu" disabled={createMutation.isPending} className="h-10" /> {errors.confirmPassword && ( -

{errors.confirmPassword}

+

+ {errors.confirmPassword} +

)}
@@ -288,8 +300,8 @@ function CreateUserComponent() { > Hủy - +
+ Không tìm thấy người dùng cần chỉnh sửa. +
+
+ ); + } + + return ( +
+
+
+

+ Chỉnh sửa người dùng +

+

+ Tài khoản: {user.userName} +

+
+ +
+ + + + Thông tin người dùng + + Cập nhật họ tên, username và danh sách phòng truy cập. + + + +
+
+ + + setEditForm((prev) => ({ ...prev, userName: e.target.value })) + } + disabled={updateUserInfoMutation.isPending} + /> +
+
+ + + setEditForm((prev) => ({ ...prev, name: e.target.value })) + } + disabled={updateUserInfoMutation.isPending} + /> +
+
+ +
+ +
+ {selectedRoomValues.length > 0 ? ( + selectedRoomValues.map((value) => ( + + {roomLabelMap.get(value) ?? value} + + )) + ) : ( + + Chưa chọn phòng. + + )} +
+
+ + {roomsLoading && ( + + Đang tải danh sách phòng... + + )} +
+
+ +
+ +
+
+
+ + + + Vai trò + Cập nhật vai trò của người dùng. + + +
+ + +
+ +
+ +
+
+
+ + setIsRoomDialogOpen(false)} + title="Chọn phòng phụ trách" + description="Chọn một hoặc nhiều phòng để gán quyền truy cập." + items={roomOptions} + selectedValues={selectedRoomValues} + onConfirm={(values) => setSelectedRoomValues(values)} + /> +
+ ); +} diff --git a/src/routes/_auth/user/index.tsx b/src/routes/_auth/user/index.tsx index 22d4856..e09a53f 100644 --- a/src/routes/_auth/user/index.tsx +++ b/src/routes/_auth/user/index.tsx @@ -10,10 +10,13 @@ import { } from "@/components/ui/tooltip"; import type { ColumnDef } from "@tanstack/react-table"; import { VersionTable } from "@/components/tables/version-table"; -import { Edit2, Trash2, Shield } from "lucide-react"; +import { Edit2, Settings, Shield, Trash2 } from "lucide-react"; import { toast } from "sonner"; export const Route = createFileRoute("/_auth/user/")({ + head: () => ({ + meta: [{ title: "Danh sách người dùng" }], + }), component: RouteComponent, loader: async ({ context }) => { context.breadcrumbs = [ @@ -65,21 +68,6 @@ function RouteComponent() {
{Array.isArray(getValue()) ? (getValue() as number[]).join(", ") : "-"}
), }, - { - id: "select", - header: () =>
Chọn
, - cell: ({ row }) => ( -
- -
- ), - enableSorting: false, - enableHiding: false, - }, { id: "actions", header: () => ( @@ -87,42 +75,78 @@ function RouteComponent() { ), cell: ({ row }) => (
- - - + + + + + Đổi thông tin + + + + + + Đổi mật khẩu + + + + + + Xem quyền + + + + + + Xóa người dùng +
), enableSorting: false, diff --git a/src/routes/_auth/user/role/$roleId/index.tsx b/src/routes/_auth/user/role/$roleId/index.tsx index b7521e6..a7831e5 100644 --- a/src/routes/_auth/user/role/$roleId/index.tsx +++ b/src/routes/_auth/user/role/$roleId/index.tsx @@ -14,7 +14,7 @@ import type { PermissionOnRole } from "@/types/permission"; export const Route = createFileRoute("/_auth/user/role/$roleId/")({ head: () => ({ - meta: [{ title: "Quyền của người dùng | AccessControl" }] + meta: [{ title: "Quyền của người dùng" }] }), component: ViewRolePermissionsComponent, loader: async ({ context, params }) => { diff --git a/src/services/app-version.service.ts b/src/services/app-version.service.ts index 1e95e57..ebbab7a 100644 --- a/src/services/app-version.service.ts +++ b/src/services/app-version.service.ts @@ -120,9 +120,12 @@ export async function deleteRequiredFile(data: { MsiFileIds: number[] }): Promis /** * Xóa file từ server - * @param fileId - ID file + * @param data - DownloadMsiRequest { MsiFileIds: number[] } */ -export async function deleteFile(fileId: number): Promise<{ message: string }> { - const response = await axios.delete(API_ENDPOINTS.APP_VERSION.DELETE_FILES(fileId)); +export async function deleteFile(data: { MsiFileIds: number[] }): Promise<{ message: string }> { + const response = await axios.delete( + API_ENDPOINTS.APP_VERSION.DELETE_FILES, + { data } + ); return response.data; } diff --git a/src/services/user.service.ts b/src/services/user.service.ts index e196400..49af545 100644 --- a/src/services/user.service.ts +++ b/src/services/user.service.ts @@ -1,6 +1,20 @@ import axios from "@/config/axios"; import { API_ENDPOINTS } from "@/config/api"; -import type { UserProfile } from "@/types/user-profile"; +import type { + UserProfile, + UpdateUserInfoRequest, + UpdateUserRoleRequest, + UpdateUserInfoResponse, + UpdateUserRoleResponse, +} from "@/types/user-profile"; + +// Helper to extract data from wrapped or unwrapped response +function extractData(responseData: any): T { + if (responseData && typeof responseData === "object" && "success" in responseData && "data" in responseData) { + return responseData.data as T; + } + return responseData as T; +} /** * Lấy danh sách thông tin người dùng và chuyển sang camelCase keys @@ -11,6 +25,7 @@ export async function getUsersInfo(): Promise { const list = Array.isArray(response.data) ? response.data : []; return list.map((u: any) => ({ + userId: u.id ?? u.Id ?? u.userId ?? u.UserId ?? undefined, userName: u.userName ?? u.UserName ?? "", name: u.name ?? u.Name ?? "", role: u.role ?? u.Role ?? "", @@ -31,4 +46,32 @@ export async function getUsersInfo(): Promise { } } -export default { getUsersInfo }; +/** + * Cập nhật thông tin người dùng + */ +export async function updateUserInfo( + userId: number, + data: UpdateUserInfoRequest +): Promise { + const response = await axios.put( + API_ENDPOINTS.USER.UPDATE_INFO(userId), + data + ); + return extractData(response.data); +} + +/** + * Cập nhật role người dùng + */ +export async function updateUserRole( + userId: number, + data: UpdateUserRoleRequest +): Promise { + const response = await axios.put( + API_ENDPOINTS.USER.UPDATE_ROLE(userId), + data + ); + return extractData(response.data); +} + +export default { getUsersInfo, updateUserInfo, updateUserRole }; diff --git a/src/template/dashboard-template.tsx b/src/template/dashboard-template.tsx index 56b1287..92089a2 100644 --- a/src/template/dashboard-template.tsx +++ b/src/template/dashboard-template.tsx @@ -168,7 +168,7 @@ export function DashboardTemplate({ variant={usageRange === "weekly" ? "default" : "outline"} onClick={() => setUsageRange("weekly")} > - 7 ngay + 7 ngày diff --git a/src/types/app-sidebar.ts b/src/types/app-sidebar.ts index 6e643ae..117db16 100644 --- a/src/types/app-sidebar.ts +++ b/src/types/app-sidebar.ts @@ -27,7 +27,7 @@ export const appSidebarSection = { code: AppSidebarSectionCode.DASHBOARD, icon: Home, permissions: [PermissionEnum.ALLOW_ALL], - }, + } ], }, { @@ -40,6 +40,13 @@ export const appSidebarSection = { icon: Building, permissions: [PermissionEnum.VIEW_ROOM], }, + { + title: "Điều khiển trực tiếp", + url: "/remote-control", + code: AppSidebarSectionCode.REMOTE_LIVE_CONTROL, + icon: Monitor, + permissions: [PermissionEnum.VIEW_REMOTE_CONTROL], + } ], }, { @@ -95,18 +102,6 @@ export const appSidebarSection = { } ] }, - { - title: "Điều khiển từ xa", - items: [ - { - title: "Điều khiển trực tiếp", - url: "/remote-control", - code: AppSidebarSectionCode.REMOTE_LIVE_CONTROL, - icon: Monitor, - permissions: [PermissionEnum.ALLOW_ALL], - } - ] - }, { title: "Audits", items: [ diff --git a/src/types/permission.ts b/src/types/permission.ts index c20306b..82ab174 100644 --- a/src/types/permission.ts +++ b/src/types/permission.ts @@ -101,6 +101,11 @@ export enum PermissionEnum { AUDIT_OPERATION = 190, VIEW_AUDIT_LOGS = 191, + //REMOTE CONTROL + REMOTE_CONTROL_OPERATION = 200, + VIEW_REMOTE_CONTROL = 201, + CONTROL_REMOTE = 202, + //Undefined UNDEFINED = 9999, diff --git a/src/types/user-profile.ts b/src/types/user-profile.ts index e561b28..229efc4 100644 --- a/src/types/user-profile.ts +++ b/src/types/user-profile.ts @@ -1,4 +1,5 @@ export type UserProfile = { + userId?: number; userName: string; name: string; role: string; @@ -8,4 +9,33 @@ export type UserProfile = { createdBy?: string | null; updatedAt?: string | null; updatedBy?: string | null; +}; + +export type UpdateUserInfoRequest = { + name: string; + userName: string; + accessRooms?: number[]; +}; + +export type UpdateUserRoleRequest = { + roleId: number; +}; + +export type UpdateUserInfoResponse = { + userId: number; + userName: string; + name: string; + roleId: number; + accessRooms: number[]; + updatedAt?: string | null; + updatedBy?: string | null; +}; + +export type UpdateUserRoleResponse = { + userId: number; + userName: string; + roleId: number; + roleName?: string | null; + updatedAt?: string | null; + updatedBy?: string | null; }; \ No newline at end of file