diff --git a/src/components/generic/Table/DataTable.jsx b/src/components/generic/Table/DataTable.jsx
index 24758f8..a642e58 100644
--- a/src/components/generic/Table/DataTable.jsx
+++ b/src/components/generic/Table/DataTable.jsx
@@ -85,7 +85,8 @@ export default function DataTable({
getFilters: () => filtersRef.current,
getSort: () => sortRef.current,
resetSelection: () => table.resetRowSelection(),
- setFilters, // ← new
+ setFilters,
+ tableInstance: table,
});
onFetch({ page: 1, limit: pagination.limit, filters: [], sort: [] });
diff --git a/src/contexts/AdminAssetsContext.jsx b/src/contexts/AdminAssetsContext.jsx
new file mode 100644
index 0000000..c626b96
--- /dev/null
+++ b/src/contexts/AdminAssetsContext.jsx
@@ -0,0 +1,175 @@
+import { createContext, useCallback, useContext, useState } from "react";
+import api from "@/utils/api.util";
+import { toast } from "sonner";
+
+const AssetsContext = createContext(null);
+
+export function useAssets() {
+ const ctx = useContext(AssetsContext);
+ if (!ctx) throw new Error("useAssets must be used within an AssetsProvider");
+ return ctx;
+}
+
+export function AssetsProvider({ children }) {
+ const [assets, setAssets] = useState([]);
+ const [pagination, setPagination] = useState(null);
+ const [selectedAsset, setSelectedAsset] = useState(null);
+ const [loading, setLoading] = useState(false);
+
+ 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/assets ────────────────────────────────────────────────
+ const fetchAssets = useCallback(
+ (params = {}) =>
+ request(async () => {
+ const res = await api.get("/admin/assets", { params });
+ setAssets(res.data?.result?.data ?? []);
+ setPagination(res.data?.result?.pagination ?? null);
+ return res.data;
+ }),
+ [request]
+ );
+
+ // ─── GET /api/admin/assets/:assetId ───────────────────────────────────────
+ const fetchAsset = useCallback(
+ (assetId) =>
+ request(async () => {
+ const res = await api.get(`/admin/assets/${assetId}`);
+ setSelectedAsset(res.data?.result?.data ?? null);
+ return res.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 () => {
+ const form = new FormData();
+ form.append("file", file);
+ if (thumbnail) form.append("thumbnail", thumbnail);
+ Object.entries(rest).forEach(([k, v]) => {
+ 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;
+ if (asset) {
+ setAssets((prev) => prev.map((a) => (a.asset_id === assetId ? asset : a)));
+ setSelectedAsset((prev) => (prev?.asset_id === assetId ? asset : prev));
+ }
+ 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
+ const updateAsset = useCallback(
+ (assetId, fields) =>
+ request(async () => {
+ const res = await api.put(`/admin/assets/${assetId}`, fields);
+ const asset = res.data?.result?.data ?? null;
+ if (asset) {
+ setAssets((prev) => prev.map((a) => (a.asset_id === assetId ? asset : a)));
+ setSelectedAsset((prev) => (prev?.asset_id === assetId ? asset : prev));
+ }
+ return res.data;
+ }),
+ [request]
+ );
+
+ // ─── DELETE /api/admin/assets/:assetId ────────────────────────────────────
+ const deleteAsset = useCallback(
+ (assetId, { deletedBy } = {}) =>
+ request(async () => {
+ const res = await api.delete(`/admin/assets/${assetId}`, {
+ data: { deletedBy },
+ });
+ setAssets((prev) => prev.filter((a) => a.asset_id !== assetId));
+ setSelectedAsset((prev) => (prev?.asset_id === assetId ? null : prev));
+ return res.data;
+ }),
+ [request]
+ );
+
+ // ─── DELETE /api/admin/assets (bulk) ──────────────────────────────────────
+ const deleteAssets = useCallback(
+ (ids = [], { deletedBy } = {}) =>
+ request(async () => {
+ const res = await api.delete("/admin/assets", {
+ data: { ids, deletedBy },
+ });
+ setAssets((prev) => prev.filter((a) => !ids.includes(a.asset_id)));
+ return res.data;
+ }),
+ [request]
+ );
+
+ // ─── 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)));
+ return res.data;
+ }),
+ [request]
+ );
+
+ return (
+
+ {children}
+
+ );
+}
\ No newline at end of file
diff --git a/src/data/adminTiles.data.js b/src/data/adminTiles.data.js
index 36a7f58..f0c0ed1 100644
--- a/src/data/adminTiles.data.js
+++ b/src/data/adminTiles.data.js
@@ -9,6 +9,14 @@ export const USER_MANAGEMENT = [
{ key: "user-groups", label: "User Groups", icon: GitFork, link: "groups" },
],
},
+ {
+ title: "Asset Management",
+ description: "Manage people across different systems",
+ tiles: [
+ { key: "users", label: "Assets", icon: Users, link: "all" },
+ { key: "user-groups", label: "User Groups", icon: GitFork, link: "groups" },
+ ],
+ },
]
export const CONTENT_MANAGEMENT = [
diff --git a/src/modules/admin/components/user_groups/ArchiveGroupTable.jsx b/src/modules/admin/components/user_groups/ArchiveGroupTable.jsx
index f898740..ea35d5f 100644
--- a/src/modules/admin/components/user_groups/ArchiveGroupTable.jsx
+++ b/src/modules/admin/components/user_groups/ArchiveGroupTable.jsx
@@ -47,12 +47,14 @@ export default function ArchiveGroupTable() {
fetchArchivedGroups, pagination, exportConfig, navigate,
getFilters: () => tableRefsRef.current.getFilters(),
getSort: () => tableRefsRef.current.getSort(),
+ getTableInstance: () => tableRefsRef.current.tableInstance,
});
const selectionActions = buildSelectionActions({
exportConfig,
restoreGroup: (row) => setRestoreTarget(row), // single
restoreGroups: (ids) => setRestoreIds(ids), // bulk
+ getTableInstance: () => tableRefsRef.current.tableInstance,
});
const columns = useMemo(() => buildDataColumns(attributes, rowActions), [attributes]);
diff --git a/src/modules/admin/components/user_groups/GroupTable.jsx b/src/modules/admin/components/user_groups/GroupTable.jsx
index 5315a19..7b6a3b1 100644
--- a/src/modules/admin/components/user_groups/GroupTable.jsx
+++ b/src/modules/admin/components/user_groups/GroupTable.jsx
@@ -71,12 +71,14 @@ export default function GroupTable() {
onAddGroup: () => setCreateOpen(true),
getFilters: () => tableRefsRef.current.getFilters(),
getSort: () => tableRefsRef.current.getSort(),
+ getTableInstance: () => tableRefsRef.current.tableInstance,
});
const selectionActions = buildSelectionActions({
exportConfig,
archiveGroup: (row) => setArchiveTarget(row),
archiveGroups: (ids) => setArchiveIds(ids),
+ getTableInstance: () => tableRefsRef.current.tableInstance,
});
const columns = useMemo(
diff --git a/src/modules/admin/components/users/ArchiveUserTable.jsx b/src/modules/admin/components/users/ArchiveUserTable.jsx
index 24a28c6..fe48236 100644
--- a/src/modules/admin/components/users/ArchiveUserTable.jsx
+++ b/src/modules/admin/components/users/ArchiveUserTable.jsx
@@ -47,11 +47,13 @@ export default function ArchiveGroupTable() {
fetchArchivedUsers, pagination, exportConfig, navigate,
getFilters: () => tableRefsRef.current.getFilters(),
getSort: () => tableRefsRef.current.getSort(),
+ getTableInstance: () => tableRefsRef.current.tableInstance,
});
const selectionActions = buildSelectionActions({
exportConfig,
restoreUser: (row) => setRestoreTarget(row),
restoreUsers: (ids) => setRestoreIds(ids),
+ getTableInstance: () => tableRefsRef.current.tableInstance,
});
const columns = useMemo(() => buildUserColumns(attributes, rowActions), [attributes]);
diff --git a/src/modules/admin/components/users/UserTable.jsx b/src/modules/admin/components/users/UserTable.jsx
index 2dcf9e6..6f21647 100644
--- a/src/modules/admin/components/users/UserTable.jsx
+++ b/src/modules/admin/components/users/UserTable.jsx
@@ -60,11 +60,13 @@ export default function UsersTable() {
fetchUsers, pagination, exportConfig, navigate,
getFilters: () => tableRefsRef.current.getFilters(),
getSort: () => tableRefsRef.current.getSort(),
+ getTableInstance: () => tableRefsRef.current.tableInstance,
});
const selectionActions = buildSelectionActions({
exportConfig,
archiveUser: (row) => setArchiveTarget(row),
archiveUsers: (ids) => setArchiveIds(ids),
+ getTableInstance: () => tableRefsRef.current.tableInstance,
});
const columns = useMemo(() => buildDataColumns(attributes, rowActions), [attributes]);
diff --git a/src/modules/admin/config/user_groups/archive/selection.config.jsx b/src/modules/admin/config/user_groups/archive/selection.config.jsx
index 06336b6..1a7d2d2 100644
--- a/src/modules/admin/config/user_groups/archive/selection.config.jsx
+++ b/src/modules/admin/config/user_groups/archive/selection.config.jsx
@@ -8,14 +8,14 @@ import { exportTableToExcel } from "@/utils/excel.util";
* @param {Function} deps.archiveUser Archive handler from useManagement
* @param {Function} deps.deleteUser Delete handler from useManagement
*/
-export function buildSelectionActions({ exportConfig, restoreGroup, restoreGroups }) {
+export function buildSelectionActions({ exportConfig, restoreGroup, restoreGroups, getTableInstance }) {
return [
{
key: "export-selected",
label: "Export",
icon: ,
onClick: (rows, table) =>
- exportTableToExcel({ ...exportConfig, selectedRows: rows, tableInstance: table }),
+ exportTableToExcel({ ...exportConfig, selectedRows: rows, tableInstance: table ?? getTableInstance(), }),
},
{
key: "restore-selected",
diff --git a/src/modules/admin/config/user_groups/archive/toolbar.config.jsx b/src/modules/admin/config/user_groups/archive/toolbar.config.jsx
index 4801aba..c56096b 100644
--- a/src/modules/admin/config/user_groups/archive/toolbar.config.jsx
+++ b/src/modules/admin/config/user_groups/archive/toolbar.config.jsx
@@ -9,7 +9,7 @@ import { exportTableToExcel } from "@/utils/excel.util";
* @param {Object} deps.exportConfig { allData, attributes, filename, sheetName }
* @param {Function} deps.navigate React Router navigate
*/
-export function buildToolbarActions({ fetchArchivedGroups, pagination, exportConfig, navigate, getFilters, getSort }) {
+export function buildToolbarActions({ fetchArchivedGroups, pagination, exportConfig, navigate, getFilters, getSort, getTableInstance }) {
return [
{
key: "refresh",
@@ -23,7 +23,7 @@ export function buildToolbarActions({ fetchArchivedGroups, pagination, exportCon
type: "button",
icon: ,
label: "Export",
- onClick: (table) => exportTableToExcel({ ...exportConfig, tableInstance: table }),
+ onClick: (table) => exportTableToExcel({ ...exportConfig, tableInstance: table ?? getTableInstance(), }),
},
];
}
\ No newline at end of file
diff --git a/src/modules/admin/config/user_groups/selection.config.jsx b/src/modules/admin/config/user_groups/selection.config.jsx
index 81a5e7e..b03deda 100644
--- a/src/modules/admin/config/user_groups/selection.config.jsx
+++ b/src/modules/admin/config/user_groups/selection.config.jsx
@@ -8,14 +8,14 @@ import { exportTableToExcel } from "@/utils/excel.util";
* @param {Function} deps.archiveUser Archive handler from useManagement
* @param {Function} deps.deleteUser Delete handler from useManagement
*/
-export function buildSelectionActions({ exportConfig, archiveGroup, archiveGroups }) {
+export function buildSelectionActions({ exportConfig, archiveGroup, archiveGroups, getTableInstance }) {
return [
{
key: "export-selected",
label: "Export",
icon: ,
onClick: (rows, table) =>
- exportTableToExcel({ ...exportConfig, selectedRows: rows, tableInstance: table }),
+ exportTableToExcel({ ...exportConfig, selectedRows: rows, tableInstance: table ?? getTableInstance(), }),
},
{
key: "archive-selected",
diff --git a/src/modules/admin/config/user_groups/toolbar.config.jsx b/src/modules/admin/config/user_groups/toolbar.config.jsx
index 8b35150..202e896 100644
--- a/src/modules/admin/config/user_groups/toolbar.config.jsx
+++ b/src/modules/admin/config/user_groups/toolbar.config.jsx
@@ -9,7 +9,7 @@ import { exportTableToExcel } from "@/utils/excel.util";
* @param {Object} deps.exportConfig { allData, attributes, filename, sheetName }
* @param {Function} deps.navigate React Router navigate
*/
-export function buildToolbarActions({ fetchGroups, pagination, exportConfig, onAddGroup, navigate, getFilters, getSort }) {
+export function buildToolbarActions({ fetchGroups, pagination, exportConfig, onAddGroup, navigate, getFilters, getSort, getTableInstance }) {
return [
{
key: "refresh",
@@ -23,7 +23,7 @@ export function buildToolbarActions({ fetchGroups, pagination, exportConfig, onA
type: "button",
icon: ,
label: "Export",
- onClick: (table) => exportTableToExcel({ ...exportConfig, tableInstance: table }),
+ onClick: (table) => exportTableToExcel({ ...exportConfig, tableInstance: table ?? getTableInstance() }),
},
{
key: "add-group",
diff --git a/src/modules/admin/config/users/archive/selection.config.jsx b/src/modules/admin/config/users/archive/selection.config.jsx
index cfbe652..ee38739 100644
--- a/src/modules/admin/config/users/archive/selection.config.jsx
+++ b/src/modules/admin/config/users/archive/selection.config.jsx
@@ -8,14 +8,14 @@ import { exportTableToExcel } from "@/utils/excel.util";
* @param {Function} deps.archiveUser Archive handler from useManagement
* @param {Function} deps.deleteUser Delete handler from useManagement
*/
-export function buildSelectionActions({ exportConfig, restoreUser, restoreUsers }) {
+export function buildSelectionActions({ exportConfig, restoreUser, restoreUsers, getTableInstance }) {
return [
{
key: "export-selected",
label: "Export",
icon: ,
onClick: (rows, table) =>
- exportTableToExcel({ ...exportConfig, selectedRows: rows, tableInstance: table }),
+ exportTableToExcel({ ...exportConfig, selectedRows: rows, tableInstance: table ?? getTableInstance() }),
},
{
key: "restore-selected",
diff --git a/src/modules/admin/config/users/archive/toolbar.config.jsx b/src/modules/admin/config/users/archive/toolbar.config.jsx
index 934c7d6..b08a2c5 100644
--- a/src/modules/admin/config/users/archive/toolbar.config.jsx
+++ b/src/modules/admin/config/users/archive/toolbar.config.jsx
@@ -9,7 +9,7 @@ import { exportTableToExcel } from "@/utils/excel.util";
* @param {Object} deps.exportConfig { allData, attributes, filename, sheetName }
* @param {Function} deps.navigate React Router navigate
*/
-export function buildToolbarActions({ fetchArchivedUsers, pagination, exportConfig, navigate, getFilters, getSort }) {
+export function buildToolbarActions({ fetchArchivedUsers, pagination, exportConfig, navigate, getFilters, getSort, getTableInstance }) {
return [
{
key: "refresh",
@@ -23,7 +23,7 @@ export function buildToolbarActions({ fetchArchivedUsers, pagination, exportConf
type: "button",
icon: ,
label: "Export",
- onClick: (table) => exportTableToExcel({ ...exportConfig, tableInstance: table }),
+ onClick: (table) => exportTableToExcel({ ...exportConfig, tableInstance: table ?? getTableInstance(), }),
},
];
}
\ No newline at end of file
diff --git a/src/modules/admin/config/users/selection.config.jsx b/src/modules/admin/config/users/selection.config.jsx
index 3b7f7ce..af85009 100644
--- a/src/modules/admin/config/users/selection.config.jsx
+++ b/src/modules/admin/config/users/selection.config.jsx
@@ -8,14 +8,14 @@ import { exportTableToExcel } from "@/utils/excel.util";
* @param {Function} deps.archiveUser Archive handler from useManagement
* @param {Function} deps.deleteUser Delete handler from useManagement
*/
-export function buildSelectionActions({ exportConfig, archiveUser, archiveUsers }) {
+export function buildSelectionActions({ exportConfig, archiveUser, archiveUsers, getTableInstance }) {
return [
{
key: "export-selected",
label: "Export",
icon: ,
onClick: (rows, table) =>
- exportTableToExcel({ ...exportConfig, selectedRows: rows, tableInstance: table }),
+ exportTableToExcel({ ...exportConfig, selectedRows: rows, tableInstance: table ?? getTableInstance() }),
},
{
key: "archive-selected",
diff --git a/src/modules/admin/config/users/toolbar.config.jsx b/src/modules/admin/config/users/toolbar.config.jsx
index b8eb8da..975f183 100644
--- a/src/modules/admin/config/users/toolbar.config.jsx
+++ b/src/modules/admin/config/users/toolbar.config.jsx
@@ -9,7 +9,7 @@ import { exportTableToExcel } from "@/utils/excel.util";
* @param {Object} deps.exportConfig { allData, attributes, filename, sheetName }
* @param {Function} deps.navigate React Router navigate
*/
-export function buildToolbarActions({ fetchUsers, pagination, exportConfig, navigate, getFilters, getSort }) {
+export function buildToolbarActions({ fetchUsers, pagination, exportConfig, navigate, getFilters, getSort, getTableInstance }) {
return [
{
key: "refresh",
@@ -23,7 +23,7 @@ export function buildToolbarActions({ fetchUsers, pagination, exportConfig, navi
type: "button",
icon: ,
label: "Export",
- onClick: (table) => exportTableToExcel({ ...exportConfig, tableInstance: table }),
+ onClick: (table) => exportTableToExcel({ ...exportConfig, tableInstance: table ?? getTableInstance(), }),
},
{
key: "add-user",
diff --git a/src/utils/excel.util.js b/src/utils/excel.util.js
index 2ee9f6c..7d52516 100644
--- a/src/utils/excel.util.js
+++ b/src/utils/excel.util.js
@@ -89,12 +89,11 @@ export function exportTableToExcel({
sheetName = "Sheet1",
} = {}) {
const columns = resolveColumns(tableInstance, attributes);
- const rawData = Array.isArray(selectedRows) && selectedRows.length > 0
+ const data = Array.isArray(selectedRows) && selectedRows.length > 0
? selectedRows
: allData;
- // ─── Flatten rows so dot-notation keys resolve correctly ──────────────────
- const data = rawData.map((row) => flattenRow(row, attributes));
+ console.log(columns, data)
exportToExcel({ data, columns, filename, sheetName });
}