prototype 1.1
This commit is contained in:
+2
-1
@@ -1,7 +1,8 @@
|
||||
import React from 'react';
|
||||
import { Admin, Resource } from 'react-admin';
|
||||
import { Admin, Resource, Layout } from 'react-admin';
|
||||
import { authProvider } from './authProvider';
|
||||
import { dataProvider } from './dataProvider';
|
||||
import { MyLayout } from './layout/MyLayout';
|
||||
import Dashboard from './resources/dashboard/Dashboard';
|
||||
import { UserList, UserEdit } from './resources/users';
|
||||
import { ReportList, ReportEdit } from './resources/reports';
|
||||
|
||||
@@ -1,11 +1,22 @@
|
||||
// src/resources/dashboard/Dashboard.tsx
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Card, CardContent, Typography, Box, CircularProgress } from '@mui/material';
|
||||
import {
|
||||
Card, CardContent, Typography, Box, CircularProgress, Grid,
|
||||
} from '@mui/material';
|
||||
import PeopleIcon from '@mui/icons-material/People';
|
||||
import CalendarTodayIcon from '@mui/icons-material/CalendarToday';
|
||||
import EventIcon from '@mui/icons-material/Event';
|
||||
import BookOnlineIcon from '@mui/icons-material/BookOnline';
|
||||
import RateReviewIcon from '@mui/icons-material/RateReview';
|
||||
import SubscriptionsIcon from '@mui/icons-material/Subscriptions';
|
||||
import ConfirmationNumberIcon from '@mui/icons-material/ConfirmationNumber';
|
||||
import { useDataProvider } from 'react-admin';
|
||||
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
|
||||
|
||||
const Dashboard = () => {
|
||||
const [stats, setStats] = useState<Record<string, number> | null>(null);
|
||||
const [ticketsStats, setTicketsStats] = useState<{ total: number } | null>(null);
|
||||
const [users, setUsers] = useState<any[]>([]);
|
||||
const [tickets, setTickets] = useState<any[]>([]);
|
||||
const dataProvider = useDataProvider();
|
||||
|
||||
const fetchJson = async (url: string) => {
|
||||
@@ -23,10 +34,14 @@ const Dashboard = () => {
|
||||
Promise.all([
|
||||
fetchJson('/v1/admin/stats'),
|
||||
fetchJson('/v1/admin/tickets/stats'),
|
||||
fetchJson('/v1/admin/users'),
|
||||
fetchJson('/v1/admin/tickets'),
|
||||
])
|
||||
.then(([statsData, ticketsData]) => {
|
||||
.then(([statsData, ticketsData, usersData, ticketsDataArr]) => {
|
||||
setStats(statsData);
|
||||
setTicketsStats(ticketsData);
|
||||
setUsers(Array.isArray(usersData) ? usersData : []);
|
||||
setTickets(Array.isArray(ticketsDataArr) ? ticketsDataArr : []);
|
||||
})
|
||||
.catch(console.error);
|
||||
}, []);
|
||||
@@ -34,29 +49,92 @@ const Dashboard = () => {
|
||||
if (!stats || !ticketsStats) return <CircularProgress />;
|
||||
|
||||
const cards = [
|
||||
{ label: 'Users', value: stats.users },
|
||||
{ label: 'Calendars', value: stats.calendars },
|
||||
{ label: 'Events', value: stats.events },
|
||||
{ label: 'Bookings', value: stats.bookings },
|
||||
{ label: 'Reviews', value: stats.reviews },
|
||||
{ label: 'Subscriptions', value: stats.subscriptions },
|
||||
{ label: 'Tickets', value: ticketsStats.total },
|
||||
{ label: 'Users', value: stats.users, icon: <PeopleIcon />, color: '#1976d2' },
|
||||
{ label: 'Calendars', value: stats.calendars, icon: <CalendarTodayIcon />, color: '#388e3c' },
|
||||
{ label: 'Events', value: stats.events, icon: <EventIcon />, color: '#f57c00' },
|
||||
{ label: 'Bookings', value: stats.bookings, icon: <BookOnlineIcon />, color: '#7b1fa2' },
|
||||
{ label: 'Reviews', value: stats.reviews, icon: <RateReviewIcon />, color: '#c2185b' },
|
||||
{ label: 'Subscriptions', value: stats.subscriptions, icon: <SubscriptionsIcon />, color: '#0097a7' },
|
||||
{ label: 'Tickets', value: ticketsStats.total, icon: <ConfirmationNumberIcon />, color: '#d32f2f' },
|
||||
];
|
||||
|
||||
const roleCounts: Record<string, number> = {};
|
||||
users.forEach(user => {
|
||||
const role = user.role || 'unknown';
|
||||
roleCounts[role] = (roleCounts[role] || 0) + 1;
|
||||
});
|
||||
const roleData = Object.entries(roleCounts).map(([role, count]) => ({ role, count }));
|
||||
|
||||
const ticketStatusCounts: Record<string, number> = {};
|
||||
tickets.forEach(ticket => {
|
||||
const status = ticket.status || 'unknown';
|
||||
ticketStatusCounts[status] = (ticketStatusCounts[status] || 0) + 1;
|
||||
});
|
||||
const ticketStatusData = Object.entries(ticketStatusCounts).map(([status, count]) => ({ status, count }));
|
||||
|
||||
return (
|
||||
<Box display="flex" flexWrap="wrap" gap={3}>
|
||||
{cards.map(({ label, value }) => (
|
||||
<Box key={label} flex="1 1 200px">
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Typography color="textSecondary" gutterBottom>
|
||||
{label}
|
||||
</Typography>
|
||||
<Typography variant="h5">{value}</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Box>
|
||||
))}
|
||||
<Box>
|
||||
<Grid container spacing={3}>
|
||||
{cards.map((card) => (
|
||||
<Grid size={{ xs: 12, sm: 6, md: 4, lg: 3 }} key={card.label}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Box display="flex" alignItems="center" mb={1}>
|
||||
<Box mr={2} sx={{ color: card.color }}>
|
||||
{card.icon}
|
||||
</Box>
|
||||
<Typography variant="h6" color="textSecondary">
|
||||
{card.label}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Typography variant="h4" fontWeight="bold">
|
||||
{card.value}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
|
||||
<Box mt={4}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Users by Role
|
||||
</Typography>
|
||||
<ResponsiveContainer width="100%" height={300}>
|
||||
<BarChart data={roleData}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="role" />
|
||||
<YAxis allowDecimals={false} />
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
<Bar dataKey="count" fill="#1976d2" name="Count" />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Box>
|
||||
|
||||
<Box mt={4}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Tickets by Status
|
||||
</Typography>
|
||||
<ResponsiveContainer width="100%" height={300}>
|
||||
<BarChart data={ticketStatusData}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="status" />
|
||||
<YAxis allowDecimals={false} />
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
<Bar dataKey="count" fill="#AA46BE" name="Count" />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user