This commit is contained in:
rgrgogu
2026-05-07 23:35:31 +08:00
parent 42b842c480
commit ca6eae1d56
35 changed files with 1637 additions and 207 deletions
+310
View File
@@ -0,0 +1,310 @@
/***********************************************************************************************************************************************************************
* File Name: AdminUserGroupContext.jsx
* Type of Program: Context
* Description: Admin-level user group management context.
* Covers: list groups, get single group + members, create, update,
* deactivate, restore, add/remove members.
*
* Author: rgrgogu
***********************************************************************************************************************************************************************/
import { createContext, useCallback, useContext, useState } from "react";
import api from "@/utils/api.util";
import { toast } from "sonner";
const BASE = "/admin";
// ─── Context ──────────────────────────────────────────────────────────────────
const UserGroupContext = createContext(null);
export function useUserGroups() {
const ctx = useContext(UserGroupContext);
if (!ctx) throw new Error("useUserGroups must be used within a UserGroupProvider");
return ctx;
}
// ─── Provider ─────────────────────────────────────────────────────────────────
export function UserGroupProvider({ children }) {
const [groups, setGroups] = useState([]);
const [group, setGroup] = useState(null); // single group + its members
const [members, setMembers] = useState([]); // members inside current group
const [usersIn, setUsersIn] = useState([]); // users already in group (for remove)
const [usersNotIn, setUsersNotIn] = useState([]); // users not in group (for add)
const [attributes, setAttributes] = useState([]);
const [pagination, setPagination] = useState({ page: 1, limit: 10, total: 0, totalPages: 1 });
const [loading, setLoading] = useState(false);
// ─── Generic request wrapper ────────────────────────────────────────────────
const request = useCallback(async (fn) => {
setLoading(true);
try {
return await fn();
} catch (err) {
const message = err?.response?.data?.message ?? "Something went wrong.";
toast.error(message);
return null;
} finally {
setLoading(false);
}
}, []);
// ─── GET /api/admin/groups ─────────────────────────────────────────────────
const fetchGroups = useCallback(
({ page = 1, limit = 10, filters = [], sort = [] } = {}) =>
request(async () => {
const res = await api.get(`${BASE}/groups`, {
params: { page, limit, filters: JSON.stringify(filters), sort: JSON.stringify(sort) },
});
const { data, attributes: attrs, pagination: pg } = res.data?.data ?? {};
setGroups(data ?? []);
setAttributes(attrs ?? []);
setPagination(pg ?? { page, limit, total: 0, totalPages: 1 });
return res.data;
}),
[request]
);
// ─── GET /api/admin/groups/:gid ────────────────────────────────────────────
const fetchGroup = useCallback(
(gid, paginationParams = {}) =>
request(async () => {
const { page = 1, limit = 10, filters = [], sort = [] } = paginationParams;
const res = await api.get(`${BASE}/groups/${gid}`, {
params: { page, limit, filters: JSON.stringify(filters), sort: JSON.stringify(sort) },
});
const { group: g, members: m } = res.data?.data ?? {};
setGroup(g ?? null);
setMembers(m?.data ?? []);
setPagination(m?.pagination ?? { page, limit, total: 0, totalPages: 1 });
return res.data;
}),
[request]
);
// ─── GET /api/admin/groups/:gid/users ──────────────────────────────────────
const fetchUsersInGroup = useCallback(
(gid) =>
request(async () => {
const res = await api.get(`${BASE}/groups/${gid}/users`);
setUsersIn(res.data?.data ?? []);
return res.data;
}),
[request]
);
// ─── GET /api/admin/groups/:gid/users/add ──────────────────────────────────
const fetchUsersNotInGroup = useCallback(
(gid) =>
request(async () => {
const res = await api.get(`${BASE}/groups/${gid}/users/add`);
setUsersNotIn(res.data?.data ?? []);
return res.data;
}),
[request]
);
// ─── POST /api/admin/groups ────────────────────────────────────────────────
const createGroup = useCallback(
({ name, description }) =>
request(async () => {
const res = await api.post(`${BASE}/groups`, { name, description });
setGroups((prev) => [res.data?.data, ...prev]);
toast.success("Group created successfully.");
return res.data;
}),
[request]
);
// ─── PUT /api/admin/groups/:gid ────────────────────────────────────────────
const updateGroup = useCallback(
(gid, { name, description }) =>
request(async () => {
const res = await api.put(`${BASE}/groups/${gid}`, { name, description });
setGroups((prev) =>
prev.map((g) => (g.group_id === gid ? { ...g, ...res.data?.data } : g))
);
// Update single group view if open
setGroup((prev) => (prev?.group_id === gid ? { ...prev, ...res.data?.data } : prev));
toast.success("Group updated successfully.");
return res.data;
}),
[request]
);
// ─── PATCH /api/admin/groups/:gid/deactivate ───────────────────────────────
const deactivateGroup = useCallback(
(gid) =>
request(async () => {
const res = await api.patch(`${BASE}/groups/${gid}/deactivate`);
setGroups((prev) =>
prev.map((g) => (g.group_id === gid ? { ...g, is_active: false } : g))
);
toast.success("Group deactivated.");
return res.data;
}),
[request]
);
// ─── PATCH /api/admin/groups/:gid/restore ─────────────────────────────────
const restoreGroup = useCallback(
(gid) =>
request(async () => {
const res = await api.patch(`${BASE}/groups/${gid}/restore`);
setGroups((prev) =>
prev.map((g) => (g.group_id === gid ? { ...g, is_active: true } : g))
);
toast.success("Group restored.");
return res.data;
}),
[request]
);
// ─── POST /api/admin/groups/:gid/users ────────────────────────────────────
const addUsersToGroup = useCallback(
(gid, user_ids) =>
request(async () => {
const res = await api.post(`${BASE}/groups/${gid}/users`, { user_ids });
// Remove added users from usersNotIn list
setUsersNotIn((prev) => prev.filter((u) => !user_ids.includes(u.user_id)));
toast.success("Users added to group.");
return res.data;
}),
[request]
);
// ─── DELETE /api/admin/groups/:gid/users ──────────────────────────────────
const removeUsersFromGroup = useCallback(
(gid, user_ids) =>
request(async () => {
const res = await api.delete(`${BASE}/groups/${gid}/users`, { data: { user_ids } });
// Remove from members list
setMembers((prev) => prev.filter((u) => !user_ids.includes(u.user_id)));
setUsersIn((prev) => prev.filter((u) => !user_ids.includes(u.user_id)));
toast.success("Users removed from group.");
return res.data;
}),
[request]
);
// ─── DELETE /api/admin/groups/bulk ────────────────────────────────────────────
const deactivateGroups = useCallback(
({ ids }) =>
request(async () => {
const res = await api.delete(`${BASE}/groups/bulk`, { data: { ids } });
const { deactivated_ids } = res.data?.data ?? {};
if (deactivated_ids?.length) {
setGroups((prev) =>
prev.map((g) =>
deactivated_ids.includes(g.group_id) ? { ...g, is_active: false } : g
)
);
}
return res.data;
}),
[request]
);
// ─── POST /api/admin/groups/bulk/restore ──────────────────────────────────────
const restoreGroups = useCallback(
({ ids }) =>
request(async () => {
const res = await api.post(`${BASE}/groups/bulk/restore`, { ids });
const { restored_ids } = res.data?.data ?? {};
if (restored_ids?.length) {
setGroups((prev) =>
prev.map((g) =>
restored_ids.includes(g.group_id) ? { ...g, is_active: true } : g
)
);
}
return res.data;
}),
[request]
);
// ─── GET /api/admin/groups/field-values ───────────────────────────────────────
const fetchGroupFieldValues = useCallback(
(field) =>
request(async () => {
const res = await api.get(`${BASE}/groups/field-values`, { params: { field } });
return res.data;
}),
[request]
);
const fetchArchivedGroups = useCallback(
({ page = 1, limit = 10, filters = [], sort = [] } = {}) =>
request(async () => {
const res = await api.get(`${BASE}/groups/archived`, {
params: { page, limit, filters: JSON.stringify(filters), sort: JSON.stringify(sort) },
});
const { data, attributes: attrs, pagination: pg } = res.data?.data ?? {};
setGroups(data ?? []);
setAttributes(attrs ?? []);
setPagination(pg ?? { page, limit, total: 0, totalPages: 1 });
return res.data;
}),
[request]
);
return (
<UserGroupContext.Provider
value={{
// state
groups,
group,
members,
usersIn,
usersNotIn,
attributes,
pagination,
setPagination,
loading,
// actions
fetchGroups,
fetchGroup,
fetchUsersInGroup,
fetchUsersNotInGroup,
fetchGroupFieldValues,
fetchArchivedGroups,
createGroup,
updateGroup,
deactivateGroup,
deactivateGroups,
restoreGroup,
restoreGroups,
addUsersToGroup,
removeUsersFromGroup,
}}
>
{children}
</UserGroupContext.Provider>
);
}