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
+114 -55
View File
@@ -10,11 +10,22 @@ export function useAssets() {
return ctx;
}
// ─── Initial States ────────────────────────────────────────────────────────────
const PAGINATION_INIT = {
page: 1,
limit: 10,
totalRecords: 0,
totalPages: 0,
hasPrevPage: false,
hasNextPage: false,
};
export function AssetsProvider({ children }) {
const [assets, setAssets] = useState([]);
const [pagination, setPagination] = useState(null);
const [assets, setAssets] = useState([]);
const [attributes, setAttributes] = useState([]);
const [pagination, setPagination] = useState(PAGINATION_INIT);
const [selectedAsset, setSelectedAsset] = useState(null);
const [loading, setLoading] = useState(false);
const [loading, setLoading] = useState(false);
const request = useCallback(async (fn) => {
setLoading(true);
@@ -31,12 +42,23 @@ export function AssetsProvider({ children }) {
// ─── GET /api/admin/assets ────────────────────────────────────────────────
const fetchAssets = useCallback(
(params = {}) =>
({ page = 1, limit = 10, filters = [], sort = [] } = {}) =>
request(async () => {
const res = await api.get("/admin/assets", { params });
setAssets(res.data?.result?.data ?? []);
setPagination(res.data?.result?.pagination ?? null);
return res.data;
const { data } = await api.get("/admin/assets", {
params: {
page, limit,
filters: filters.length ? JSON.stringify(filters) : undefined,
sort: sort.length ? JSON.stringify(sort) : undefined,
},
});
const result = data?.data;
setAssets(result?.data ?? []);
setPagination(result?.pagination ?? PAGINATION_INIT);
setAttributes(result.attributes);
return data.data;
}),
[request]
);
@@ -46,16 +68,34 @@ export function AssetsProvider({ children }) {
(assetId) =>
request(async () => {
const res = await api.get(`/admin/assets/${assetId}`);
setSelectedAsset(res.data?.result?.data ?? null);
setSelectedAsset(res.data?.data?.data ?? null);
return res.data;
}),
[request]
);
// ─── GET /api/admin/assets/archived ───────────────────────────────────────
const fetchArchivedAssets = useCallback(
({ page = 1, limit = 10, filters = [], sort = [] } = {}) =>
request(async () => {
const { data } = await api.get("/admin/assets/archived", {
params: {
page, limit,
filters: filters.length ? JSON.stringify(filters) : undefined,
sort: sort.length ? JSON.stringify(sort) : undefined,
},
});
const final_data = data?.data;
setAssets(final_data?.data ?? []);
setPagination(final_data?.pagination ?? PAGINATION_INIT);
setAttributes(final_data.attributes);
return data;
}),
[request]
);
// ─── POST /api/admin/assets ───────────────────────────────────────────────
// fields: { file (File), thumbnail? (File), uploadedBy, display_name,
// description, owner_type, owner_id, is_public, access_level,
// storage_provider, storage_bucket, storage_key }
const uploadAsset = useCallback(
({ file, thumbnail, ...rest }) =>
request(async () => {
@@ -66,56 +106,44 @@ export function AssetsProvider({ children }) {
if (v !== undefined && v !== null) form.append(k, v);
});
const res = await api.post("/admin/assets", form, {
headers: { "Content-Type": "multipart/form-data" },
});
const asset = res.data?.result?.data ?? null;
if (asset) setAssets((prev) => [asset, ...prev]);
return res.data;
}),
[request]
);
// ─── PATCH /api/admin/assets/:assetId/thumbnail ───────────────────────────
const updateThumbnail = useCallback(
(assetId, thumbnailFile) =>
request(async () => {
const form = new FormData();
form.append("thumbnail", thumbnailFile);
const res = await api.patch(`/admin/assets/${assetId}/thumbnail`, form, {
headers: { "Content-Type": "multipart/form-data" },
});
const asset = res.data?.result?.data ?? null;
const res = await api.post("/admin/assets", form);
const asset = res.data?.data?.data ?? null;
if (asset) {
setAssets((prev) => prev.map((a) => (a.asset_id === assetId ? asset : a)));
setSelectedAsset((prev) => (prev?.asset_id === assetId ? asset : prev));
setAssets((prev) => [asset, ...prev]);
toast.success("Asset uploaded successfully.");
}
return res.data;
}),
[request]
);
// ─── PUT /api/admin/assets/:assetId ───────────────────────────────────────
// Allowed: display_name, description, owner_type, owner_id, is_public,
// access_level, thumbnail_url, width, height, duration,
// frame_rate, bitrate, video_codec, audio_codec
// ─── PATCH /api/admin/assets/:assetId ────────────────────────────────────
const updateAsset = useCallback(
(assetId, fields) =>
(assetId, fields, file = null) =>
request(async () => {
const res = await api.put(`/admin/assets/${assetId}`, fields);
const asset = res.data?.result?.data ?? null;
const formData = new FormData();
Object.entries(fields).forEach(([key, value]) => {
if (value !== undefined && value !== null) formData.append(key, value);
});
if (file) formData.append("file", file);
const res = await api.patch(`/admin/assets/${assetId}`, formData, {
headers: { "Content-Type": "multipart/form-data" },
});
const asset = res.data?.data?.data ?? null;
if (asset) {
setAssets((prev) => prev.map((a) => (a.asset_id === assetId ? asset : a)));
setSelectedAsset((prev) => (prev?.asset_id === assetId ? asset : prev));
setSelectedAsset(asset);
toast.success("Asset updated successfully.");
}
return res.data;
}),
[request]
);
// ─── DELETE /api/admin/assets/:assetId ────────────────────────────────────
const deleteAsset = useCallback(
// ─── DELETE /api/admin/assets/:assetId ───────────────────────────────────
const archiveAsset = useCallback(
(assetId, { deletedBy } = {}) =>
request(async () => {
const res = await api.delete(`/admin/assets/${assetId}`, {
@@ -123,51 +151,82 @@ export function AssetsProvider({ children }) {
});
setAssets((prev) => prev.filter((a) => a.asset_id !== assetId));
setSelectedAsset((prev) => (prev?.asset_id === assetId ? null : prev));
toast.success("Asset archived.");
return res.data;
}),
[request]
);
// ─── DELETE /api/admin/assets (bulk) ──────────────────────────────────────
const deleteAssets = useCallback(
(ids = [], { deletedBy } = {}) =>
// ─── DELETE /api/admin/assets/bulk ───────────────────────────────────────
const archiveAssets = useCallback(
({ ids }, { deletedBy } = {}) =>
request(async () => {
const res = await api.delete("/admin/assets", {
const res = await api.delete("/admin/assets/bulk", {
data: { ids, deletedBy },
});
setAssets((prev) => prev.filter((a) => !ids.includes(a.asset_id)));
toast.success(`${ids.length} asset(s) archived.`);
return res.data;
}),
[request]
);
// ─── PATCH /api/admin/assets/:assetId/restore ─────────────────────────────
// ─── PATCH /api/admin/assets/:assetId/restore ────────────────────────────
const restoreAsset = useCallback(
(assetId) =>
request(async () => {
const res = await api.patch(`/admin/assets/${assetId}/restore`);
const asset = res.data?.result?.data ?? null;
if (asset) setAssets((prev) => prev.map((a) => (a.asset_id === assetId ? asset : a)));
const asset = res.data?.data?.data ?? null;
if (asset) {
setAssets((prev) => prev.filter((a) => a.asset_id !== assetId));
toast.success("Asset restored.");
}
return res.data;
}),
[request]
);
// ─── PATCH /api/admin/assets/bulk-restore ────────────────────────────────
const restoreAssets = useCallback(
({ ids }) =>
request(async () => {
const res = await api.patch("/admin/assets/bulk-restore", { ids });
setAssets((prev) => prev.filter((a) => !ids.includes(a.asset_id)));
toast.success(`${ids.length} asset(s) restored.`);
return res.data;
}),
[request]
);
// ─── GET /api/admin/assets/field-values ──────────────────────────────────────
const fetchAssetFieldValues = useCallback(
(field) =>
request(async () => {
const res = await api.get("/admin/assets/field-values", { params: { field } });
return res.data?.data;
}),
[request]
);
return (
<AssetsContext.Provider value={{
assets,
attributes,
pagination,
selectedAsset,
loading,
setPagination,
setSelectedAsset,
fetchAssets,
fetchAsset,
fetchArchivedAssets,
uploadAsset,
updateThumbnail,
updateAsset,
deleteAsset,
deleteAssets,
archiveAsset,
archiveAssets,
restoreAsset,
restoreAssets,
fetchAssetFieldValues
}}>
{children}
</AssetsContext.Provider>
+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>
);
+83 -143
View File
@@ -1,19 +1,8 @@
/***********************************************************************************************************************************************************************
* 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() {
@@ -22,18 +11,16 @@ export function useUserGroups() {
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 [group, setGroup] = useState(null);
const [members, setMembers] = useState([]);
const [usersIn, setUsersIn] = useState([]);
const [usersNotIn, setUsersNotIn] = useState([]);
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 {
@@ -54,40 +41,49 @@ export function UserGroupProvider({ children }) {
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 ────────────────────────────────────────────
// ─── GET /api/admin/groups/archived ───────────────────────────────────────
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]
);
// ─── 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 ──────────────────────────────────────
// ─── GET /api/admin/groups/:gid/users ─────────────────────────────────────
const fetchUsersInGroup = useCallback(
(gid) =>
request(async () => {
@@ -98,7 +94,7 @@ export function UserGroupProvider({ children }) {
[request]
);
// ─── GET /api/admin/groups/:gid/users/add ──────────────────────────────────
// ─── GET /api/admin/groups/:gid/users/add ─────────────────────────────────
const fetchUsersNotInGroup = useCallback(
(gid) =>
request(async () => {
@@ -109,80 +105,115 @@ export function UserGroupProvider({ children }) {
[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]
);
// ─── 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 ────────────────────────────────────────────
// ─── 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 ───────────────────────────────
// ─── 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]
);
// ─── 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
)
);
toast.success(`${deactivated_ids.length} group(s) 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/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
)
);
toast.success(`${restored_ids.length} group(s) 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;
}),
@@ -194,116 +225,25 @@ export function UserGroupProvider({ children }) {
(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,
}}
>
<UserGroupContext.Provider value={{
groups, group, members, usersIn, usersNotIn, attributes, pagination, loading,
setPagination,
fetchGroups, fetchArchivedGroups, fetchGroup,
fetchUsersInGroup, fetchUsersNotInGroup, fetchGroupFieldValues,
createGroup, updateGroup,
deactivateGroup, deactivateGroups,
restoreGroup, restoreGroups,
addUsersToGroup, removeUsersFromGroup,
}}>
{children}
</UserGroupContext.Provider>
);
+8 -5
View File
@@ -1,4 +1,5 @@
// ─── AdminProvider.jsx ─────────────────────────────────────────────────────────
import { AssetsProvider } from "../AdminAssetsContext";
import { AdminDashboardProvider } from "../AdminDashboardContext"
import { UserProvider } from "../AdminUserContext";
import { UserGroupProvider } from "../AdminUserGroupContext";
@@ -6,11 +7,13 @@ import { UserGroupProvider } from "../AdminUserGroupContext";
export const AdminProvider = ({ children }) => {
return (
<AdminDashboardProvider>
<UserProvider>
<UserGroupProvider>
{children}
</UserGroupProvider>
</UserProvider>
<AssetsProvider>
<UserProvider>
<UserGroupProvider>
{children}
</UserGroupProvider>
</UserProvider>
</AssetsProvider>
</AdminDashboardProvider>
);
};