]> PHS Git Server - phs-admin.git/commitdiff
Fixing base_url imports and making header sticky.
authorcharleswrayjr <charleswrayjr@gmail.com>
Sat, 13 Sep 2025 15:45:34 +0000 (10:45 -0500)
committercharleswrayjr <charleswrayjr@gmail.com>
Sat, 13 Sep 2025 15:45:34 +0000 (10:45 -0500)
src/App.js
src/app/components/AppHeader.js
src/app/services/media.js
src/app/services/message_group_members.js
src/app/services/message_groups.js
src/app/services/messages.js
src/app/services/posts.js
src/app/views/Dashboard/Dashboard.jsx
src/styles/App.css

index 19a34bcecedb0f0191ce7a5898d4d9e0f6848346..b291e31e7eb789a6dd894236e13e19b6383125c9 100755 (executable)
@@ -19,8 +19,12 @@ const App = () => {
       <CssBaseline />
       <SnackbarProvider maxSnack={3}>
         <AuthProvider>
-          <AppHeader />
-          <AppRoutes />
+          <div className="flex flex-col min-h-screen">
+            <AppHeader />
+            <main className="flex-grow pt-16"> {/* Padding for sticky header */}
+              <AppRoutes />
+            </main>
+          </div>
         </AuthProvider>
       </SnackbarProvider>
     </ThemeProvider>
index 52883993039accfce87c2e479924c669a9c66ea9..c33df0ec45ef735ddbddf620f2176bb1efb0243f 100644 (file)
@@ -40,7 +40,7 @@ const AppHeader = () => {
   )), [navItems, navigate] );
 
   return (
-    <AppBar position="static" className="bg-blue-600">
+    <AppBar position="fixed" className="bg-blue-600 z-10">
       <Toolbar className="container">
         <Typography variant="h6" component="div" className="flex-grow">
           PHS Admin
index 90572153f97ef5cbe7623d8c252259768ee909b5..e945f55635d2b0c21304fdd47e59960e14816aea 100644 (file)
@@ -4,7 +4,7 @@
 
 import axios from 'axios';
 
-const BASE_URL = 'http://localhost:3601';
+const { base_url } = require('../../globals');
 
 const getAuthHeaders = () => ({
   headers: { Authorization: `Bearer ${localStorage.getItem('token')}` },
@@ -12,21 +12,21 @@ const getAuthHeaders = () => ({
 
 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
index 0d745bfadb94e7446f71bb73d9d4b8bb34c03739..2229b50daa00613d624f2af822e90597f1c16e1e 100644 (file)
@@ -4,7 +4,7 @@
 
 import axios from 'axios';
 
-const BASE_URL = 'http://localhost:3601';
+const { base_url } = require('../../globals');
 
 const getAuthHeaders = () => ({
   headers: { Authorization: `Bearer ${localStorage.getItem('token')}` },
@@ -12,15 +12,15 @@ const getAuthHeaders = () => ({
 
 export const MessageGroupMembersService = {
   async getMembersByGroupId(group_id, params = {}) {
-    return await axios.get(`${BASE_URL}/message_group_members/group/${group_id}`, { ...getAuthHeaders(), params });
+    return await axios.get(`${base_url}/message_group_members/group/${group_id}`, { ...getAuthHeaders(), params });
   },
   async getMemberByIds(group_id, user_id) {
-    return await axios.get(`${BASE_URL}/message_group_members/ids/${group_id}/${user_id}`, getAuthHeaders());
+    return await axios.get(`${base_url}/message_group_members/ids/${group_id}/${user_id}`, getAuthHeaders());
   },
   async addMember(data) {
-    return await axios.post(`${BASE_URL}/message_group_members/add`, data, getAuthHeaders());
+    return await axios.post(`${base_url}/message_group_members/add`, data, getAuthHeaders());
   },
   async removeMember(data) {
-    return await axios.delete(`${BASE_URL}/message_group_members/remove`, { ...getAuthHeaders(), data });
+    return await axios.delete(`${base_url}/message_group_members/remove`, { ...getAuthHeaders(), data });
   },
 };
\ No newline at end of file
index 42ab639d86445c88cb246d3d252967b1a1332b98..490ee9cabf284037f7906933c1e68c3953c8f25a 100644 (file)
@@ -4,7 +4,7 @@
 
 import axios from 'axios';
 
-const BASE_URL = 'http://localhost:3601';
+const { base_url } = require('../../globals');
 
 const getAuthHeaders = () => ({
   headers: { Authorization: `Bearer ${localStorage.getItem('token')}` },
@@ -12,15 +12,15 @@ const getAuthHeaders = () => ({
 
 export const MessageGroupService = {
   async getMessageGroups(params = {}) {
-    return await axios.get(`${BASE_URL}/message_group`, { ...getAuthHeaders(), params });
+    return await axios.get(`${base_url}/message_group`, { ...getAuthHeaders(), params });
   },
   async getMessageGroup(id) {
-    return await axios.get(`${BASE_URL}/message_group/${id}`, getAuthHeaders());
+    return await axios.get(`${base_url}/message_group/${id}`, getAuthHeaders());
   },
   async createMessageGroup(data) {
-    return await axios.post(`${BASE_URL}/message_group/create`, data, getAuthHeaders());
+    return await axios.post(`${base_url}/message_group/create`, data, getAuthHeaders());
   },
   async updateMessageGroup(id, data) {
-    return await axios.put(`${BASE_URL}/message_group/${id}`, data, getAuthHeaders());
+    return await axios.put(`${base_url}/message_group/${id}`, data, getAuthHeaders());
   },
 };
\ No newline at end of file
index decbe252ed1acde3196f1663cf413d07301c9b08..0c1f3a0fbc3d05c4e33b6bd2735628770d8df80a 100644 (file)
@@ -4,7 +4,7 @@
 
 import axios from 'axios';
 
-const BASE_URL = 'http://localhost:3601';
+const { base_url } = require('../../globals');
 
 const getAuthHeaders = () => ({
   headers: { Authorization: `Bearer ${localStorage.getItem('token')}` },
@@ -12,21 +12,21 @@ const getAuthHeaders = () => ({
 
 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
index 850959e60ced8b2253ffea0c31b914e4aeb85cdd..07d086c85a7d511b5c48f36eecaac1fc63b304d5 100644 (file)
@@ -4,7 +4,7 @@
 
 import axios from 'axios';
 
-import { base_url } from '../../globals';
+const { base_url } = require('../../globals');
 
 
 export const PostService = {
index 436750b5e44dfa9c4cc99ff34fad694427587e0d..3b28ef7cb1b1291e8211ad8eabf61e9c159cadfe 100755 (executable)
 /**
- * @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>
   );
 };
 
index cf0105dc0e88fc28b1f7b7b7c71fdd021d753718..33c4fb9cd1f8bae14865c1cd99d0dce204a7338e 100755 (executable)
 .App-link {
   color: #61dafb;
 }
+
+.container {
+    @apply max-w-7xl mx-auto p-4;
+}
+
+.table-header {
+    @apply bg-gray-100 font-semibold text-gray-800;
+}
+
+.table-row {
+    @apply border-b hover:bg-gray-50;
+}
+
+.chat-container {
+    @apply max-w-3xl mx-auto p-4 bg-white shadow-md rounded-lg;
+}
+
+.chat-message {
+    @apply p-2 my-2 rounded-lg;
+}
+
+.chat-message-sent {
+    @apply bg-blue-100 ml-auto max-w-xs;
+}
+
+.chat-message-received {
+    @apply bg-gray-100 mr-auto max-w-xs;
+}