import axios from 'axios';
-const BASE_URL = 'http://localhost:3601';
+const { base_url } = require('../../globals');
const getAuthHeaders = () => ({
headers: { Authorization: `Bearer ${localStorage.getItem('token')}` },
export const MediaService = {
async getMedia(params = {}) {
- return await axios.get(`${BASE_URL}/media`, { ...getAuthHeaders(), params });
+ return await axios.get(`${base_url}/media`, { ...getAuthHeaders(), params });
},
async getMediaByFilePath(file_path) {
- return await axios.get(`${BASE_URL}/media/file_path/${file_path}`, getAuthHeaders());
+ return await axios.get(`${base_url}/media/file_path/${file_path}`, getAuthHeaders());
},
async getMediaById(id) {
- return await axios.get(`${BASE_URL}/media/${id}`, getAuthHeaders());
+ return await axios.get(`${base_url}/media/${id}`, getAuthHeaders());
},
async createMedia(data) {
- return await axios.post(`${BASE_URL}/media/create`, data, getAuthHeaders());
+ return await axios.post(`${base_url}/media/create`, data, getAuthHeaders());
},
async updateMedia(id, data) {
- return await axios.put(`${BASE_URL}/media/${id}`, data, getAuthHeaders());
+ return await axios.put(`${base_url}/media/${id}`, data, getAuthHeaders());
},
async softDeleteMedia(id, data) {
- return await axios.put(`${BASE_URL}/media/${id}/soft_delete`, data, getAuthHeaders());
+ return await axios.put(`${base_url}/media/${id}/soft_delete`, data, getAuthHeaders());
},
};
\ No newline at end of file
import axios from 'axios';
-const BASE_URL = 'http://localhost:3601';
+const { base_url } = require('../../globals');
const getAuthHeaders = () => ({
headers: { Authorization: `Bearer ${localStorage.getItem('token')}` },
export const MessageService = {
async getMessages(params = {}) {
- return await axios.get(`${BASE_URL}/message`, { ...getAuthHeaders(), params });
+ return await axios.get(`${base_url}/message`, { ...getAuthHeaders(), params });
},
async getMessagesByGroupId(group_id, params = {}) {
- return await axios.get(`${BASE_URL}/message/group/${group_id}`, { ...getAuthHeaders(), params });
+ return await axios.get(`${base_url}/message/group/${group_id}`, { ...getAuthHeaders(), params });
},
async getMessagesByRecipientId(recipient_id, params = {}) {
- return await axios.get(`${BASE_URL}/message/recipient/${recipient_id}`, { ...getAuthHeaders(), params });
+ return await axios.get(`${base_url}/message/recipient/${recipient_id}`, { ...getAuthHeaders(), params });
},
async getMessage(id) {
- return await axios.get(`${BASE_URL}/message/${id}`, getAuthHeaders());
+ return await axios.get(`${base_url}/message/${id}`, getAuthHeaders());
},
async createMessage(data) {
- return await axios.post(`${BASE_URL}/message/create`, data, getAuthHeaders());
+ return await axios.post(`${base_url}/message/create`, data, getAuthHeaders());
},
async markMessageAsRead(id) {
- return await axios.put(`${BASE_URL}/message/${id}/mark_as_read`, {}, getAuthHeaders());
+ return await axios.put(`${base_url}/message/${id}/mark_as_read`, {}, getAuthHeaders());
},
};
\ No newline at end of file
/**
- * @file Dashboard view with navigation to main features
+ * @file Dashboard view with informative widgets
*/
-import React, { useContext, useMemo } from 'react';
-import { Container, Grid, Card, CardContent, Typography, Button } from '@mui/material';
+import React, { useContext, useEffect, useState } from 'react';
+import { Container, Grid, Card, CardContent, Typography, Box, Button } from '@mui/material';
+import { MaterialReactTable } from 'material-react-table';
import { useNavigate } from 'react-router-dom';
+import { useSnackbar } from 'notistack';
import { AuthContext } from '../../components/AuthContext';
+import { MessageService, MediaService, PostService, MessageGroupService } from '../../services';
+import { getFormattedDate, truncateContent } from '../../utils';
const Dashboard = () => {
const navigate = useNavigate();
- const { user } = useContext(AuthContext) || {};
+ const { enqueueSnackbar } = useSnackbar();
+ const { user } = useContext(AuthContext);
+ const [recentMessages, setRecentMessages] = useState([]);
+ const [recentMedia, setRecentMedia] = useState([]);
+ const [recentPosts, setRecentPosts] = useState([]);
+ const [recentGroups, setRecentGroups] = useState([]);
- console.log(user);
+ useEffect(() => {
+ const fetchData = async () => {
+ try {
+ // Fetch recent messages
+ const messages = await MessageService.getMessages({ limit: 5 });
+ setRecentMessages(messages.data);
- const features = useMemo( () => user ? [
- { title: 'Messages', path: '/messages', description: 'View and send messages', roles: ['User', 'Admin'] },
- { title: 'Message Groups', path: '/message-groups', description: 'Manage group chats', roles: ['User', 'Admin'] },
- { title: 'Media', path: '/media', description: 'Upload and view media', roles: ['User', 'Admin'] },
- { title: 'Posts', path: '/posts', description: 'Create and view posts', roles: ['User', 'Admin'] },
- { title: 'Docker', path: '/docker', description: 'Manage Docker containers', roles: ['Admin'] },
- { title: 'VPN', path: '/vpn', description: 'Manage OpenVPN Connections and Clients', roles: ['Admin'] },
- { title: 'GIT', path: '/git', description: 'Manage GIT repositories', roles: ['Admin'] },
- ].filter(feature => feature.roles.some( role => [...user.roles]?.map( r => r.name )?.includes( role ) ) ) : [], [user] );
+ // Fetch recent media (filtered by visibility for non-Admin)
+ const mediaParams = user?.roles.includes('Admin') ? { limit: 5 } : { limit: 5, visibility: ['public', 'family'] };
+ const media = await MediaService.getMedia(mediaParams);
+ setRecentMedia(media.data);
- /**
- *
- * @type {unknown[]}
- */
- const featureCards = useMemo( () => features.map((feature) => (
- (
- <Grid item xs={12} sm= {6} md={4} key={feature.title}>
- <Card className="shadow-md rounded-lg">
- <CardContent>
- <Typography variant="h6" className="font-semiold">
- {feature.title}
- </Typography>
- <Typography variant="body2" className="text-gray-600 mb-4">
- {feature.description}
- </Typography>
- <Button
- variant="contained"
- color="primary"
- onClick={() => navigate(feature.path)}
- >
- Go to {feature.title}
- </Button>
- </CardContent>
- </Card>
- </Grid>
- )
- )), [features, navigate] );
+ // Fetch recent posts (filtered by visibility for non-Admin)
+ const postsParams = user?.roles.includes('Admin') ? { limit: 5 } : { limit: 5, visibility: ['public', 'family'] };
+ const posts = await PostService.getPosts(postsParams);
+ setRecentPosts(posts.data);
+
+ // Fetch recent message groups
+ const groups = await MessageGroupService.getMessageGroups({ limit: 5 });
+ setRecentGroups(groups.data);
+ } catch (error) {
+ enqueueSnackbar(`Error fetching dashboard data: ${error.message}`, { variant: 'error' });
+ }
+ };
+ fetchData();
+ }, [enqueueSnackbar, user]);
+
+ const messageColumns = [
+ { accessorKey: 'sender_id', header: 'Sender ID', size: 100 },
+ { accessorKey: 'content', header: 'Content', size: 200, Cell: ({ cell }) => truncateContent(cell.getValue(), 50) },
+ { accessorKey: 'created_at', header: 'Sent At', size: 150, Cell: ({ cell }) => getFormattedDate(cell.getValue()) },
+ ];
+
+ const mediaColumns = [
+ { accessorKey: 'file_path', header: 'File Path', size: 200 },
+ { accessorKey: 'file_type', header: 'Type', size: 100 },
+ { accessorKey: 'visibility', header: 'Visibility', size: 100 },
+ { accessorKey: 'created_at', header: 'Uploaded At', size: 150, Cell: ({ cell }) => getFormattedDate(cell.getValue()) },
+ ];
+
+ const postColumns = [
+ { accessorKey: 'title', header: 'Title', size: 200 },
+ { accessorKey: 'post_type', header: 'Type', size: 100 },
+ { accessorKey: 'visibility', header: 'Visibility', size: 100 },
+ { accessorKey: 'created_at', header: 'Posted At', size: 150, Cell: ({ cell }) => getFormattedDate(cell.getValue()) },
+ ];
+
+ const groupColumns = [
+ { accessorKey: 'name', header: 'Group Name', size: 200 },
+ { accessorKey: 'created_by_id', header: 'Created By', size: 100 },
+ { accessorKey: 'created_at', header: 'Created At', size: 150, Cell: ({ cell }) => getFormattedDate(cell.getValue()) },
+ ];
return (
- <div>
- <Container maxWidth="xl" className="container">
- <Typography variant="h4" className="text-3xl font-bold mb-6 mt-4">
- Welcome to PHS Admin{user ? `, ${user.first_name || 'User ' + user.id}` : ''}
- </Typography>
- <Grid container spacing={3}>
- {featureCards}
+ <Container maxWidth="xl" className="container">
+ <Typography variant="h4" className="text-3xl font-bold mb-6 mt-4">
+ Welcome to PHS Admin{user ? `, ${user.first_name || 'User ' + user.id}` : ''}
+ </Typography>
+ <Grid container spacing={3}>
+ <Grid item xs={12} md={6}>
+ <Card className="shadow-md rounded-lg">
+ <CardContent>
+ <Typography variant="h6" className="font-semibold mb-2">
+ Recent Messages
+ </Typography>
+ <MaterialReactTable
+ columns={messageColumns}
+ data={recentMessages}
+ enableColumnActions={false}
+ enableColumnFilters={false}
+ enablePagination={false}
+ enableSorting={false}
+ muiTablePaperProps={{ className: 'shadow-none' }}
+ muiTableHeadCellProps={{ className: 'table-header' }}
+ muiTableBodyCellProps={{ className: 'table-row' }}
+ />
+ <Box mt={2}>
+ <Button variant="contained" color="primary" onClick={() => navigate('/messages')}>
+ View All Messages
+ </Button>
+ </Box>
+ </CardContent>
+ </Card>
+ </Grid>
+ <Grid item xs={12} md={6}>
+ <Card className="shadow-md rounded-lg">
+ <CardContent>
+ <Typography variant="h6" className="font-semibold mb-2">
+ Recent Media
+ </Typography>
+ <MaterialReactTable
+ columns={mediaColumns}
+ data={recentMedia}
+ enableColumnActions={false}
+ enableColumnFilters={false}
+ enablePagination={false}
+ enableSorting={false}
+ muiTablePaperProps={{ className: 'shadow-none' }}
+ muiTableHeadCellProps={{ className: 'table-header' }}
+ muiTableBodyCellProps={{ className: 'table-row' }}
+ />
+ <Box mt={2}>
+ <Button variant="contained" color="primary" onClick={() => navigate('/media')}>
+ View All Media
+ </Button>
+ </Box>
+ </CardContent>
+ </Card>
+ </Grid>
+ <Grid item xs={12} md={6}>
+ <Card className="shadow-md rounded-lg">
+ <CardContent>
+ <Typography variant="h6" className="font-semibold mb-2">
+ Recent Posts
+ </Typography>
+ <MaterialReactTable
+ columns={postColumns}
+ data={recentPosts}
+ enableColumnActions={false}
+ enableColumnFilters={false}
+ enablePagination={false}
+ enableSorting={false}
+ muiTablePaperProps={{ className: 'shadow-none' }}
+ muiTableHeadCellProps={{ className: 'table-header' }}
+ muiTableBodyCellProps={{ className: 'table-row' }}
+ />
+ <Box mt={2}>
+ <Button variant="contained" color="primary" onClick={() => navigate('/posts')}>
+ View All Posts
+ </Button>
+ </Box>
+ </CardContent>
+ </Card>
</Grid>
- </Container>
- </div>
+ <Grid item xs={12} md={6}>
+ <Card className="shadow-md rounded-lg">
+ <CardContent>
+ <Typography variant="h6" className="font-semibold mb-2">
+ Recent Message Groups
+ </Typography>
+ <MaterialReactTable
+ columns={groupColumns}
+ data={recentGroups}
+ enableColumnActions={false}
+ enableColumnFilters={false}
+ enablePagination={false}
+ enableSorting={false}
+ muiTablePaperProps={{ className: 'shadow-none' }}
+ muiTableHeadCellProps={{ className: 'table-header' }}
+ muiTableBodyCellProps={{ className: 'table-row' }}
+ />
+ <Box mt={2}>
+ <Button variant="contained" color="primary" onClick={() => navigate('/message-groups')}>
+ View All Groups
+ </Button>
+ </Box>
+ </CardContent>
+ </Card>
+ </Grid>
+ </Grid>
+ </Container>
);
};