This commit is contained in:
rgrgogu
2026-05-14 12:59:31 +08:00
parent 6e98ff0b5d
commit dcf5f43fd5
38 changed files with 2174 additions and 382 deletions
+53 -92
View File
@@ -1,6 +1,6 @@
// ─── UserContext.jsx ───────────────────────────────────────────────────────────
import { createContext, useContext, useState, useCallback } from "react";
import api from "@/utils/api.util";
import { toast } from "sonner";
const UserContext = createContext(null);
@@ -10,7 +10,6 @@ export const useUsers = () => {
return ctx;
};
// ─── Initial States ────────────────────────────────────────────────────────────
const PAGINATION_INIT = {
page: 1,
limit: 10,
@@ -23,7 +22,6 @@ const PAGINATION_INIT = {
const BASE = "/admin";
export const UserProvider = ({ children }) => {
// ─── State ─────────────────────────────────────────────────────────────────
const [users, setUsers] = useState([]);
const [user, setUser] = useState(null);
const [sessions, setSessions] = useState([]);
@@ -32,7 +30,6 @@ export const UserProvider = ({ children }) => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
// ─── Helpers ───────────────────────────────────────────────────────────────
const request = useCallback(async (fn) => {
setLoading(true);
setError(null);
@@ -41,6 +38,7 @@ export const UserProvider = ({ children }) => {
} catch (err) {
const message = err?.response?.data?.message || err.message || "Something went wrong.";
setError(message);
toast.error(message);
return null;
} finally {
setLoading(false);
@@ -53,24 +51,41 @@ export const UserProvider = ({ children }) => {
request(async () => {
const { data } = await api.get(`${BASE}/users`, {
params: {
page,
limit,
page, limit,
filters: filters.length ? JSON.stringify(filters) : undefined,
sort: sort.length ? JSON.stringify(sort) : undefined,
sort: sort.length ? JSON.stringify(sort) : undefined,
},
});
const final_data = data?.data
const final_data = data?.data;
setUsers(final_data?.data ?? []);
setPagination(final_data?.pagination ?? PAGINATION_INIT);
setAttributes(final_data?.attributes ?? []);
return data?.data;
}),
[request]
);
// ─── GET /api/admin/users/:id ──────────────────────────────────────────────
// ─── GET /api/admin/users/archived ────────────────────────────────────────
const fetchArchivedUsers = useCallback(
({ page = 1, limit = 10, filters = [], sort = [] } = {}) =>
request(async () => {
const { data } = await api.get(`${BASE}/users/archived`, {
params: {
page, limit,
filters: filters.length ? JSON.stringify(filters) : undefined,
sort: sort.length ? JSON.stringify(sort) : undefined,
},
});
const final_data = data?.data;
setUsers(final_data?.data ?? []);
setPagination(final_data?.pagination ?? PAGINATION_INIT);
setAttributes(final_data?.attributes ?? []);
return data?.data;
}),
[request]
);
// ─── GET /api/admin/users/:id ─────────────────────────────────────────────
const fetchUser = useCallback(
(userId) =>
request(async () => {
@@ -81,88 +96,59 @@ export const UserProvider = ({ children }) => {
[request]
);
// ─── POST /api/admin/users/staff ──────────────────────────────────────────────
// ─── POST /api/admin/users/staff ──────────────────────────────────────────
const addStaffUser = useCallback(
(payload) =>
request(async () => {
const res = await api.post(`${BASE}/users/staff`, payload);
toast.success("Staff user added successfully.");
return res.data;
}),
[request]
);
// ─── PUT /api/admin/users/:id ──────────────────────────────────────────────
// ─── PUT /api/admin/users/:id ─────────────────────────────────────────────
const updateUser = useCallback(
(userId, payload) =>
request(async () => {
const res = await api.put(`${BASE}/users/${userId}`, payload);
// Sync local list if user exists in it
setUsers((prev) =>
prev.map((u) => (u.user_id === userId ? { ...u, ...res.data?.data } : u))
);
toast.success("User updated successfully.");
return res.data;
}),
[request]
);
// ─── GET /api/admin/users/archived ────────────────────────────────────────────
const fetchArchivedUsers = useCallback(
({ page = 1, limit = 10, filters = [], sort = [] } = {}) =>
request(async () => {
const { data } = await api.get(`${BASE}/users/archived`, {
params: {
page,
limit,
filters: filters.length ? JSON.stringify(filters) : undefined,
sort: sort.length ? JSON.stringify(sort) : undefined,
},
});
const final_data = data?.data
setUsers(final_data?.data ?? []);
setPagination(final_data?.pagination ?? PAGINATION_INIT);
setAttributes(final_data?.attributes ?? []);
return res.data;
}),
[request]
);
// ─── DELETE /api/admin/users/:id (soft delete) ────────────────────────────
// ─── DELETE /api/admin/users/:id ──────────────────────────────────────────
const deactivateUser = useCallback(
(userId) =>
request(async () => {
const res = await api.delete(`${BASE}/users/${userId}`);
setUsers((prev) =>
prev.map((u) => (u.user_id === userId ? { ...u, is_active: false } : u))
);
toast.success("User deactivated.");
return res.data;
}),
[request]
);
// ─── DELETE /api/admin/users/bulk ─────────────────────────────────────────────
// ─── DELETE /api/admin/users/bulk ─────────────────────────────────────────
const deactivateUsers = useCallback(
({ ids }) =>
request(async () => {
const res = await api.delete(`${BASE}/users/bulk`, { data: { ids } });
const { deactivated_ids } = res.data?.data ?? {}; // ← unwrap nested data
const { deactivated_ids } = res.data?.data ?? {};
if (deactivated_ids?.length) {
setUsers((prev) =>
prev.map((u) =>
deactivated_ids.includes(u.user_id)
? { ...u, is_active: false }
: u
deactivated_ids.includes(u.user_id) ? { ...u, is_active: false } : u
)
);
toast.success(`${deactivated_ids.length} user(s) deactivated.`);
}
return res.data;
}),
[request]
@@ -173,33 +159,29 @@ export const UserProvider = ({ children }) => {
(userId) =>
request(async () => {
const res = await api.post(`${BASE}/users/${userId}/restore`);
setUsers((prev) =>
prev.map((u) => (u.user_id === userId ? { ...u, is_active: true } : u))
);
toast.success("User restored.");
return res.data;
}),
[request]
);
// ─── POST /api/admin/users/bulk/restore ───────────────────────────────────
const restoreUsers = useCallback(
({ ids }) =>
request(async () => {
const res = await api.post(`${BASE}/users/bulk/restore`, { ids });
const { restored_ids } = res.data?.data ?? {}; // ← same unwrap
const { restored_ids } = res.data?.data ?? {};
if (restored_ids?.length) {
setUsers((prev) =>
prev.map((u) =>
restored_ids.includes(u.user_id)
? { ...u, is_active: true }
: u
restored_ids.includes(u.user_id) ? { ...u, is_active: true } : u
)
);
toast.success(`${restored_ids.length} user(s) restored.`);
}
return res.data;
}),
[request]
@@ -221,17 +203,16 @@ export const UserProvider = ({ children }) => {
(userId, sessionId) =>
request(async () => {
const res = await api.delete(`${BASE}/users/${userId}/sessions/${sessionId}`);
setSessions((prev) =>
prev.map((s) => (s.session_id === sessionId ? { ...s, is_active: false } : s))
);
toast.success("Session terminated.");
return res.data;
}),
[request]
);
// ─── GET /api/admin/users/field-values?field=acc_type ─────────────────────────
// ─── GET /api/admin/users/field-values ────────────────────────────────────
const fetchUserFieldValues = useCallback(
(field) =>
request(async () => {
@@ -241,37 +222,17 @@ export const UserProvider = ({ children }) => {
[request]
);
// ─── Provider ──────────────────────────────────────────────────────────────
return (
<UserContext.Provider
value={{
// state
users,
user,
sessions,
pagination,
attributes,
loading,
error,
// setters
setPagination,
// actions
fetchUserFieldValues,
fetchUsers,
fetchArchivedUsers,
fetchUser,
addStaffUser,
updateUser,
deactivateUser,
deactivateUsers,
restoreUser,
restoreUsers,
fetchUserSessions,
terminateSession,
}}
>
<UserContext.Provider value={{
users, user, sessions, pagination, attributes, loading, error,
setPagination,
fetchUsers, fetchArchivedUsers, fetchUser,
addStaffUser, updateUser,
deactivateUser, deactivateUsers,
restoreUser, restoreUsers,
fetchUserSessions, terminateSession,
fetchUserFieldValues,
}}>
{children}
</UserContext.Provider>
);