Implement admin dash workspaces tab

This commit is contained in:
2024-11-10 15:03:51 +01:00
parent 148001be43
commit 5e2d434b4b
11 changed files with 258 additions and 189 deletions

View File

@@ -1,21 +1,11 @@
import React from 'react';
import { Table, Text, Box, LoadingOverlay, Alert } from '@mantine/core';
import { IconAlertCircle } from '@tabler/icons-react';
import { useAdmin } from '../../../hooks/useAdmin';
const formatBytes = (bytes) => {
const units = ['B', 'KB', 'MB', 'GB'];
let size = bytes;
let unitIndex = 0;
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024;
unitIndex++;
}
return `${size.toFixed(1)} ${units[unitIndex]}`;
};
import { useAdminData } from '../../../hooks/useAdminData';
import { formatBytes } from '../../../utils/formatBytes';
const AdminStatsTab = () => {
const { data: stats, loading, error } = useAdmin('stats');
const { data: stats, loading, error } = useAdminData('stats');
if (loading) {
return <LoadingOverlay visible={true} />;

View File

@@ -16,20 +16,20 @@ import {
IconAlertCircle,
} from '@tabler/icons-react';
import { notifications } from '@mantine/notifications';
import { useAdmin } from '../../../hooks/useAdmin';
import { useUserAdmin } from '../../../hooks/useUserAdmin';
import CreateUserModal from '../../modals/user/CreateUserModal';
import EditUserModal from '../../modals/user/EditUserModal';
import DeleteUserModal from '../../modals/user/DeleteUserModal';
const AdminUsersTab = ({ currentUser }) => {
const {
data: users,
users,
loading,
error,
create,
update,
delete: deleteUser,
} = useAdmin('users');
} = useUserAdmin();
const [createModalOpened, setCreateModalOpened] = useState(false);
const [editModalData, setEditModalData] = useState(null);

View File

@@ -9,27 +9,21 @@ import {
Alert,
} from '@mantine/core';
import { IconTrash, IconEdit, IconAlertCircle } from '@tabler/icons-react';
import { useAdmin } from '../../../hooks/useAdmin';
import { useAdminData } from '../../../hooks/useAdminData';
import { formatBytes } from '../../../utils/formatBytes';
const AdminWorkspacesTab = () => {
const { data: workspaces, loading, error } = useAdmin('workspaces');
const { data: workspaces, loading, error } = useAdminData('workspaces');
const rows = workspaces.map((workspace) => (
<Table.Tr key={workspace.id}>
<Table.Td>{workspace.name}</Table.Td>
<Table.Td>{workspace.owner?.email}</Table.Td>
<Table.Td>{new Date(workspace.createdAt).toLocaleDateString()}</Table.Td>
<Table.Td>{workspace.gitEnabled ? 'Yes' : 'No'}</Table.Td>
<Table.Td>{workspace.userEmail}</Table.Td>
<Table.Td>{workspace.workspaceName}</Table.Td>
<Table.Td>
<Group gap="xs" justify="flex-end">
<ActionIcon variant="subtle" color="blue">
<IconEdit size={16} />
</ActionIcon>
<ActionIcon variant="subtle" color="red">
<IconTrash size={16} />
</ActionIcon>
</Group>
{new Date(workspace.workspaceCreatedAt).toLocaleDateString()}
</Table.Td>
<Table.Td>{workspace.totalFiles}</Table.Td>
<Table.Td>{formatBytes(workspace.totalSize)}</Table.Td>
</Table.Tr>
));
@@ -57,11 +51,11 @@ const AdminWorkspacesTab = () => {
<Table striped highlightOnHover withTableBorder>
<Table.Thead>
<Table.Tr>
<Table.Th>Name</Table.Th>
<Table.Th>Owner</Table.Th>
<Table.Th>Name</Table.Th>
<Table.Th>Created At</Table.Th>
<Table.Th>Git Enabled</Table.Th>
<Table.Th style={{ width: 100 }}>Actions</Table.Th>
<Table.Th>Total Files</Table.Th>
<Table.Th>Total Size</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>{rows}</Table.Tbody>

View File

@@ -1,155 +0,0 @@
import { useState, useCallback, useEffect } from 'react';
import { notifications } from '@mantine/notifications';
import * as adminApi from '../services/adminApi';
export const useAdmin = (resource) => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const fetchData = useCallback(async () => {
setLoading(true);
try {
let result;
switch (resource) {
case 'users':
result = await adminApi.listUsers();
break;
case 'stats':
result = await adminApi.getSystemStats();
break;
default:
throw new Error(`Unknown resource type: ${resource}`);
}
setData(result);
setError(null);
} catch (err) {
console.error(`Failed to fetch ${resource}:`, err);
setError(err.message);
notifications.show({
title: 'Error',
message: `Failed to load ${resource}. Please try again.`,
color: 'red',
});
} finally {
setLoading(false);
}
}, [resource]);
const createItem = useCallback(
async (itemData) => {
try {
let newItem;
switch (resource) {
case 'users':
newItem = await adminApi.createUser(itemData);
break;
default:
throw new Error(`Create not supported for resource: ${resource}`);
}
setData((prevData) =>
Array.isArray(prevData) ? [...prevData, newItem] : prevData
);
notifications.show({
title: 'Success',
message: `${resource} created successfully`,
color: 'green',
});
return { success: true, data: newItem };
} catch (err) {
console.error(`Failed to create ${resource}:`, err);
notifications.show({
title: 'Error',
message: err.message || `Failed to create ${resource}`,
color: 'red',
});
return { success: false, error: err.message };
}
},
[resource]
);
const deleteItem = useCallback(
async (itemId) => {
try {
switch (resource) {
case 'users':
await adminApi.deleteUser(itemId);
break;
default:
throw new Error(`Delete not supported for resource: ${resource}`);
}
setData((prevData) =>
Array.isArray(prevData)
? prevData.filter((item) => item.id !== itemId)
: prevData
);
notifications.show({
title: 'Success',
message: `${resource} deleted successfully`,
color: 'green',
});
return { success: true };
} catch (err) {
console.error(`Failed to delete ${resource}:`, err);
notifications.show({
title: 'Error',
message: err.message || `Failed to delete ${resource}`,
color: 'red',
});
return { success: false, error: err.message };
}
},
[resource]
);
const updateItem = useCallback(
async (itemId, itemData) => {
try {
let updatedItem;
switch (resource) {
case 'users':
updatedItem = await adminApi.updateUser(itemId, itemData);
break;
default:
throw new Error(`Update not supported for resource: ${resource}`);
}
setData((prevData) =>
Array.isArray(prevData)
? prevData.map((item) => (item.id === itemId ? updatedItem : item))
: prevData
);
notifications.show({
title: 'Success',
message: `${resource} updated successfully`,
color: 'green',
});
return { success: true, data: updatedItem };
} catch (err) {
console.error(`Failed to update ${resource}:`, err);
notifications.show({
title: 'Error',
message: err.message || `Failed to update ${resource}`,
color: 'red',
});
return { success: false, error: err.message };
}
},
[resource]
);
// Fetch data on mount
useEffect(() => {
fetchData();
}, [fetchData]);
return {
data,
loading,
error,
refresh: fetchData,
create: createItem,
delete: deleteItem,
update: updateItem,
};
};

View File

@@ -0,0 +1,48 @@
import { useState, useEffect } from 'react';
import { notifications } from '@mantine/notifications';
import { getUsers, getWorkspaces, getSystemStats } from '../services/adminApi';
// Hook for admin data fetching (stats and workspaces)
export const useAdminData = (type) => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const loadData = async () => {
setLoading(true);
setError(null);
try {
let response;
switch (type) {
case 'stats':
response = await getSystemStats();
break;
case 'workspaces':
response = await getWorkspaces();
break;
case 'users':
response = await getUsers();
break;
default:
throw new Error('Invalid data type');
}
setData(response);
} catch (err) {
const message = err.response?.data?.error || err.message;
setError(message);
notifications.show({
title: 'Error',
message: `Failed to load ${type}: ${message}`,
color: 'red',
});
} finally {
setLoading(false);
}
};
useEffect(() => {
loadData();
}, [type]);
return { data, loading, error, reload: loadData };
};

View File

@@ -0,0 +1,79 @@
import { useAdminData } from './useAdminData';
import { createUser, updateUser, deleteUser } from '../services/adminApi';
import { notifications } from '@mantine/notifications';
export const useUserAdmin = () => {
const { data: users, loading, error, reload } = useAdminData('users');
const handleCreate = async (userData) => {
try {
await createUser(userData);
notifications.show({
title: 'Success',
message: 'User created successfully',
color: 'green',
});
reload();
return { success: true };
} catch (err) {
const message = err.response?.data?.error || err.message;
notifications.show({
title: 'Error',
message: `Failed to create user: ${message}`,
color: 'red',
});
return { success: false, error: message };
}
};
const handleUpdate = async (userId, userData) => {
try {
await updateUser(userId, userData);
notifications.show({
title: 'Success',
message: 'User updated successfully',
color: 'green',
});
reload();
return { success: true };
} catch (err) {
const message = err.response?.data?.error || err.message;
notifications.show({
title: 'Error',
message: `Failed to update user: ${message}`,
color: 'red',
});
return { success: false, error: message };
}
};
const handleDelete = async (userId) => {
try {
await deleteUser(userId);
notifications.show({
title: 'Success',
message: 'User deleted successfully',
color: 'green',
});
reload();
return { success: true };
} catch (err) {
const message = err.response?.data?.error || err.message;
notifications.show({
title: 'Error',
message: `Failed to delete user: ${message}`,
color: 'red',
});
return { success: false, error: message };
}
};
return {
users,
loading,
error,
create: handleCreate,
update: handleUpdate,
delete: handleDelete,
};
};

View File

@@ -4,7 +4,7 @@ import { API_BASE_URL } from '../utils/constants';
const ADMIN_BASE_URL = `${API_BASE_URL}/admin`;
// User Management
export const listUsers = async () => {
export const getUsers = async () => {
const response = await apiCall(`${ADMIN_BASE_URL}/users`);
return response.json();
};
@@ -36,6 +36,12 @@ export const updateUser = async (userId, userData) => {
return response.json();
};
// Workspace Management
export const getWorkspaces = async () => {
const response = await apiCall(`${ADMIN_BASE_URL}/workspaces`);
return response.json();
};
// System Statistics
export const getSystemStats = async () => {
const response = await apiCall(`${ADMIN_BASE_URL}/stats`);

View File

@@ -0,0 +1,10 @@
export const formatBytes = (bytes) => {
const units = ['B', 'KB', 'MB', 'GB'];
let size = bytes;
let unitIndex = 0;
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024;
unitIndex++;
}
return `${size.toFixed(1)} ${units[unitIndex]}`;
};