mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
integrate users
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
import { useMemo, useRef } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { useUsers } from "@/contexts/AdminUserContext";
|
||||
import DataTable from "@/components/generic/Table/DataTable";
|
||||
import { FilterSheet } from "@/components/generic/Sheet/FilterSheet";
|
||||
|
||||
import { buildUserColumns, columnPinning } from "../config/columns.config";
|
||||
import { buildToolbarActions } from "../config/toolbar.config";
|
||||
import { buildSelectionActions } from "../config/selection.config";
|
||||
import { buildRowActions } from "../config/rowActions.config";
|
||||
|
||||
import { getTimestamp } from "@/utils/timestamp.util";
|
||||
|
||||
export default function UsersTable() {
|
||||
const navigate = useNavigate();
|
||||
const tableRefsRef = useRef({ getFilters: () => [], getSort: () => [] });
|
||||
|
||||
const {
|
||||
users,
|
||||
attributes,
|
||||
pagination,
|
||||
setPagination,
|
||||
loading,
|
||||
fetchUsers,
|
||||
fetchUserFieldValues,
|
||||
deactivateUser,
|
||||
} = useUsers();
|
||||
|
||||
// Shared export config — passed into toolbar + selection configs
|
||||
const exportConfig = {
|
||||
allData: users,
|
||||
attributes,
|
||||
filename: `${getTimestamp()}_Users`,
|
||||
sheetName: "Users",
|
||||
};
|
||||
|
||||
const rowActions = buildRowActions({ navigate, deactivateUser });
|
||||
const toolbarActions = buildToolbarActions({
|
||||
fetchUsers, pagination, exportConfig, navigate,
|
||||
getFilters: () => tableRefsRef.current.getFilters(),
|
||||
getSort: () => tableRefsRef.current.getSort(),
|
||||
});
|
||||
const selectionActions = buildSelectionActions({ exportConfig, deactivateUser });
|
||||
const columns = useMemo(() => buildUserColumns(attributes, rowActions), [attributes]);
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
title="Users"
|
||||
data={users}
|
||||
columns={columns}
|
||||
attributes={attributes}
|
||||
pagination={pagination}
|
||||
setPagination={setPagination}
|
||||
loading={loading}
|
||||
onFetch={fetchUsers}
|
||||
onFetchFilterData={fetchUserFieldValues}
|
||||
onRefsReady={(refs) => tableRefsRef.current = refs}
|
||||
renderFilterSheet={({ open, onOpenChange, column, attr, data, loading }) => (
|
||||
<FilterSheet
|
||||
open={open}
|
||||
onOpenChange={onOpenChange}
|
||||
column={column}
|
||||
attr={attr}
|
||||
data={data}
|
||||
loading={loading}
|
||||
/>
|
||||
)}
|
||||
columnPinning={columnPinning}
|
||||
toolbarActions={toolbarActions}
|
||||
selectionActions={selectionActions}
|
||||
recordLabel="user"
|
||||
emptyMessage="No users match the current filters."
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// config/columns.config.jsx
|
||||
// Column definitions and pinning config for the Users table.
|
||||
|
||||
import { buildColumns } from "@/utils/table.util";
|
||||
import { buildSelectionColumn } from "@/components/generic/Table/buildSelectionColumn";
|
||||
import { buildRowActionsColumn } from "@/components/generic/Table/buildRowActionsColumn";
|
||||
|
||||
export const columnPinning = {
|
||||
right: ["actions"],
|
||||
left: [],
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds the full column array for the Users table.
|
||||
*
|
||||
* @param {Array} attributes Field definitions from the server (drives data columns)
|
||||
* @param {Array} rowActions Row-level kebab action definitions
|
||||
* @returns {Array} TanStack column definitions
|
||||
*/
|
||||
export function buildUserColumns(attributes, rowActions) {
|
||||
const visibleAttributes = attributes.filter((a) => !a.hidden);
|
||||
|
||||
return [
|
||||
buildSelectionColumn(),
|
||||
...buildColumns(visibleAttributes),
|
||||
buildRowActionsColumn(rowActions, { dropdownLabel: "User Actions" }),
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// config/rowActions.config.jsx
|
||||
// Per-row kebab menu action definitions for the Users table.
|
||||
//
|
||||
// Each onClick receives the row's data object from buildRowActionsColumn.
|
||||
|
||||
import { Eye, Pencil, Archive } from "lucide-react";
|
||||
|
||||
/**
|
||||
* @param {Object} deps
|
||||
* @param {Function} deps.navigate React Router navigate
|
||||
* @param {Function} deps.archiveUser Archive handler from useManagement
|
||||
* @returns {Array} rowActions
|
||||
*/
|
||||
export function buildRowActions({ navigate, archiveUser }) {
|
||||
return [
|
||||
{
|
||||
key: "view",
|
||||
label: "View details",
|
||||
icon: <Eye className="h-3.5 w-3.5" />,
|
||||
onClick: (row) => navigate(`view/${row.user_id}`),
|
||||
},
|
||||
{
|
||||
key: "edit",
|
||||
label: "Edit",
|
||||
icon: <Pencil className="h-3.5 w-3.5" />,
|
||||
onClick: (row) => navigate(`/users/${row.id}/edit`),
|
||||
disabled: (row) => row.role === "super_admin",
|
||||
},
|
||||
{
|
||||
key: "archive",
|
||||
label: "Archive",
|
||||
className: "text-destructive focus:text-destructive",
|
||||
icon: <Archive className="h-3.5 w-3.5" />,
|
||||
onClick: (row) => archiveUser(row.id),
|
||||
hidden: (row) => row.status === "archived",
|
||||
separator: true,
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// config/selection.config.jsx
|
||||
import { Download, Archive, Trash2 } from "lucide-react";
|
||||
import { exportTableToExcel } from "@/utils/excel.util";
|
||||
|
||||
/**
|
||||
* @param {Object} deps
|
||||
* @param {Object} deps.exportConfig { allData, attributes, filename, sheetName }
|
||||
* @param {Function} deps.archiveUser Archive handler from useManagement
|
||||
* @param {Function} deps.deleteUser Delete handler from useManagement
|
||||
*/
|
||||
export function buildSelectionActions({ exportConfig, archiveUser, deleteUser }) {
|
||||
return [
|
||||
{
|
||||
key: "export-selected",
|
||||
label: "Export",
|
||||
icon: <Download className="h-3.5 w-3.5" />,
|
||||
onClick: (rows, table) =>
|
||||
exportTableToExcel({ ...exportConfig, selectedRows: rows, tableInstance: table }),
|
||||
},
|
||||
{
|
||||
key: "archive-selected",
|
||||
label: "Archive",
|
||||
icon: <Archive className="h-3.5 w-3.5" />,
|
||||
onClick: (rows) => archiveUser({ ids: rows.map((r) => r.id) }),
|
||||
hidden: (rows) => rows.every((r) => r.status === "archived"),
|
||||
},
|
||||
{
|
||||
key: "delete-selected",
|
||||
label: "Delete",
|
||||
icon: <Trash2 className="h-3.5 w-3.5" />,
|
||||
className: "text-destructive border-destructive/40 hover:bg-destructive/10 hover:text-destructive",
|
||||
onClick: (rows) => deleteUser({ ids: rows.map((r) => r.id) }),
|
||||
disabled: (rows) => rows.some((r) => r.role === "admin" || r.role === "super_admin"),
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// ─── config/toolbar.config.jsx ────────────────────────────────────────────────
|
||||
import { RefreshCw, Download, UserPlus } from "lucide-react";
|
||||
import { exportTableToExcel } from "@/utils/excel.util";
|
||||
|
||||
/**
|
||||
* @param {Object} deps
|
||||
* @param {Function} deps.fetchUsers Refetch handler from useUsers
|
||||
* @param {Object} deps.pagination Current pagination state
|
||||
* @param {Object} deps.exportConfig { allData, attributes, filename, sheetName }
|
||||
* @param {Function} deps.navigate React Router navigate
|
||||
*/
|
||||
export function buildToolbarActions({ fetchUsers, refetchUsers, pagination, exportConfig, navigate, getFilters, getSort }) {
|
||||
return [
|
||||
{
|
||||
key: "refresh",
|
||||
type: "button",
|
||||
icon: <RefreshCw className="h-3.5 w-3.5" />,
|
||||
label: "Refresh",
|
||||
onClick: () => fetchUsers({ page: 1, limit: pagination.limit, filters: getFilters(), sort: getSort()}),
|
||||
},
|
||||
{
|
||||
key: "export",
|
||||
type: "button",
|
||||
icon: <Download className="h-3.5 w-3.5" />,
|
||||
label: "Export",
|
||||
onClick: (table) => exportTableToExcel({ ...exportConfig, tableInstance: table }),
|
||||
},
|
||||
{
|
||||
key: "add-user",
|
||||
type: "button",
|
||||
icon: <UserPlus className="h-3.5 w-3.5" />,
|
||||
label: "Add User",
|
||||
variant: "default",
|
||||
className: "text-primary-foreground",
|
||||
onClick: () => navigate("add"),
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -22,6 +22,8 @@ import AdminSideTabs from "../components/AdminSideTabs"
|
||||
import UserMenu from "@/components/generic/UserMenu"
|
||||
import { ROLE_CONFIG } from "@/data/profile.data"
|
||||
|
||||
import { AdminProvider } from "@/contexts/provider/AdminProvider" // ← add
|
||||
|
||||
const AdminLayout = () => {
|
||||
const { user, logout } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
@@ -81,10 +83,14 @@ const AdminLayout = () => {
|
||||
<AdminSideTabs />
|
||||
</div>
|
||||
</div>
|
||||
<div id="main-body">
|
||||
<Outlet />
|
||||
<Toaster position="bottom-right" richColors />
|
||||
</div>
|
||||
|
||||
{/* ─── All admin contexts live here, scoped to admin routes only ── */}
|
||||
<AdminProvider>
|
||||
<div id="main-body">
|
||||
<Outlet />
|
||||
<Toaster position="bottom-right" richColors />
|
||||
</div>
|
||||
</AdminProvider>
|
||||
</TooltipProvider>
|
||||
</section>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import React from 'react'
|
||||
import { Outlet } from 'react-router-dom'
|
||||
|
||||
const UserManagementLayout = () => {
|
||||
return (
|
||||
<div>
|
||||
<Outlet />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default UserManagementLayout
|
||||
@@ -0,0 +1,11 @@
|
||||
import React from 'react'
|
||||
|
||||
const GroupList = () => {
|
||||
return (
|
||||
<div>
|
||||
GroupList
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default GroupList
|
||||
@@ -0,0 +1,11 @@
|
||||
import React from 'react'
|
||||
|
||||
const ViewGroup = () => {
|
||||
return (
|
||||
<div>
|
||||
ViewGroup
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ViewGroup
|
||||
@@ -0,0 +1,11 @@
|
||||
import React from 'react'
|
||||
|
||||
const AddUser = () => {
|
||||
return (
|
||||
<div>
|
||||
AddUser
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AddUser
|
||||
@@ -0,0 +1,6 @@
|
||||
import { USER_MANAGEMENT } from "@/data/adminTiles.data";
|
||||
import DashboardGrid from "@/components/generic/DashboardGrid";
|
||||
|
||||
export default function UserDashboard() {
|
||||
return <DashboardGrid sections={USER_MANAGEMENT} />
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { House } from "lucide-react";
|
||||
|
||||
import AppBreadcrumb from "@/components/generic/Breadcrumb/AppBreadcrumb";
|
||||
import UsersTable from "../../components/UserTable";
|
||||
|
||||
export default function UserList() {
|
||||
const items = [
|
||||
{ label: "Home", icon: <House className="size-4" />, to: `/admin/users` },
|
||||
{ label: "Users" },
|
||||
]
|
||||
|
||||
return (
|
||||
<section className="bg-muted/60 h-full">
|
||||
<div className="lg:container lg:mx-auto lg:px-0 flex flex-col items-start px-4">
|
||||
<div className="flex flex-col gap-2 my-6 ">
|
||||
<AppBreadcrumb items={items} />
|
||||
</div>
|
||||
|
||||
<div className="w-full">
|
||||
<UsersTable />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import React from 'react'
|
||||
|
||||
const ViewUser = () => {
|
||||
return (
|
||||
<div>
|
||||
ViewUser
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ViewUser
|
||||
@@ -1,8 +1,25 @@
|
||||
import { Outlet } from 'react-router-dom'
|
||||
import ProtectedRoute from '../../../routes/ProtectedRoute'
|
||||
|
||||
// Layouts
|
||||
import AdminLayout from '../layouts/AdminLayout'
|
||||
import UserManagementLayout from '../layouts/UserManagementLayout'
|
||||
|
||||
// Global Pages
|
||||
import Admin from '../pages/Admin'
|
||||
import ProfilePage from '@/components/generic/Profile'
|
||||
|
||||
// Specific Pages
|
||||
import UsersDashboard from '../pages/users/UserDashboard'
|
||||
|
||||
import UserList from '../pages/users/UserList'
|
||||
import AddUser from '../pages/users/AddUser'
|
||||
import ViewUser from '../pages/users/ViewUser'
|
||||
|
||||
import GroupList from '../pages/user_groups/GroupList'
|
||||
import ViewGroup from '../pages/user_groups/ViewGroup'
|
||||
|
||||
|
||||
export const AdminRoutes = {
|
||||
element: <ProtectedRoute allowedRoles={['admin']} />,
|
||||
children: [
|
||||
@@ -12,17 +29,37 @@ export const AdminRoutes = {
|
||||
children: [
|
||||
// Admin Management
|
||||
{ index: true, element: <Admin /> }, // /admin
|
||||
{ path: 'my-profile', element: <ProfilePage /> },
|
||||
{ path: 'my-profile', element: <ProfilePage /> },
|
||||
|
||||
// Users Management
|
||||
// {
|
||||
// path: 'users',
|
||||
// element: <UsersLayout />, // ← shared header/nav for user pages
|
||||
// children: [
|
||||
// { index: true, element: <Users /> }, // /admin/users
|
||||
// { path: 'add', element: <UsersAdd /> }, // /admin/users/add
|
||||
// { path: 'edit/:id', element: <UsersEdit /> }, // /admin/users/edit/123
|
||||
// ]
|
||||
// },
|
||||
{
|
||||
path: 'users',
|
||||
element: <UserManagementLayout />,
|
||||
children: [
|
||||
{ index: true, element: <UsersDashboard /> },
|
||||
|
||||
// User View
|
||||
{
|
||||
path: 'all',
|
||||
element: <Outlet />,
|
||||
children: [
|
||||
{ index: true, element: <UserList /> },
|
||||
{ path: 'add', element: <AddUser /> },
|
||||
{ path: 'view/:userId', element: <ViewUser /> }
|
||||
]
|
||||
},
|
||||
|
||||
// User Group View
|
||||
{
|
||||
path: 'groups',
|
||||
element: <Outlet />,
|
||||
children: [
|
||||
{ index: true, element: <GroupList /> },
|
||||
{ path: 'view/:groupId', element: <ViewGroup /> }
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
// Add here
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user