From 6e98ff0b5de3192beea10f64b4564cd36ccf9287 Mon Sep 17 00:00:00 2001 From: rgrgogu Date: Tue, 12 May 2026 12:47:13 +0800 Subject: [PATCH] Adjusted --- src/components/generic/Table/DataTable.jsx | 3 +- src/contexts/AdminAssetsContext.jsx | 175 ++++++++++++++++++ src/data/adminTiles.data.js | 8 + .../user_groups/ArchiveGroupTable.jsx | 2 + .../components/user_groups/GroupTable.jsx | 2 + .../components/users/ArchiveUserTable.jsx | 2 + .../admin/components/users/UserTable.jsx | 2 + .../user_groups/archive/selection.config.jsx | 4 +- .../user_groups/archive/toolbar.config.jsx | 4 +- .../config/user_groups/selection.config.jsx | 4 +- .../config/user_groups/toolbar.config.jsx | 4 +- .../config/users/archive/selection.config.jsx | 4 +- .../config/users/archive/toolbar.config.jsx | 4 +- .../admin/config/users/selection.config.jsx | 4 +- .../admin/config/users/toolbar.config.jsx | 4 +- src/utils/excel.util.js | 5 +- 16 files changed, 211 insertions(+), 20 deletions(-) create mode 100644 src/contexts/AdminAssetsContext.jsx 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 }); }