prototype 1.1

This commit is contained in:
2026-05-07 13:01:59 +03:00
parent eb9bbed092
commit fde574c7a0
4 changed files with 494 additions and 53 deletions
+2 -1
View File
@@ -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';
+101 -23
View File
@@ -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>
);
};