weather
This commit is contained in:
parent
da5e4a1618
commit
2207a69e3f
File diff suppressed because one or more lines are too long
@ -33,6 +33,7 @@ const instructorsRoutes = require('./routes/instructors');
|
|||||||
|
|
||||||
const studentsRoutes = require('./routes/students');
|
const studentsRoutes = require('./routes/students');
|
||||||
|
|
||||||
|
const weatherRoutes = require('./routes/weather');
|
||||||
const rolesRoutes = require('./routes/roles');
|
const rolesRoutes = require('./routes/roles');
|
||||||
|
|
||||||
const permissionsRoutes = require('./routes/permissions');
|
const permissionsRoutes = require('./routes/permissions');
|
||||||
@ -169,6 +170,12 @@ app.use(
|
|||||||
passport.authenticate('jwt', { session: false }),
|
passport.authenticate('jwt', { session: false }),
|
||||||
searchRoutes,
|
searchRoutes,
|
||||||
);
|
);
|
||||||
|
app.use(
|
||||||
|
'/api/weather',
|
||||||
|
passport.authenticate('jwt', { session: false }),
|
||||||
|
weatherRoutes,
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
const publicDir = path.join(__dirname, '../public');
|
const publicDir = path.join(__dirname, '../public');
|
||||||
|
|
||||||
|
|||||||
24
backend/src/routes/weather.js
Normal file
24
backend/src/routes/weather.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const axios = require('axios');
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
// GET /api/weather?city={city}
|
||||||
|
router.get('/', async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const city = req.query.city || 'London';
|
||||||
|
const apiKey = process.env.WEATHER_API_KEY;
|
||||||
|
if (!apiKey) {
|
||||||
|
return res.status(500).json({ error: 'Weather API key not configured' });
|
||||||
|
}
|
||||||
|
const url = `http://api.openweathermap.org/data/2.5/weather?q=${encodeURIComponent(city)}&units=metric&appid=${apiKey}`;
|
||||||
|
const response = await axios.get(url);
|
||||||
|
const { temp } = response.data.main;
|
||||||
|
const description = response.data.weather[0].description;
|
||||||
|
const icon = `http://openweathermap.org/img/wn/${response.data.weather[0].icon}@2x.png`;
|
||||||
|
res.json({ city, temp, description, icon });
|
||||||
|
} catch (err) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
@ -1,4 +1,5 @@
|
|||||||
html {
|
html {
|
||||||
|
font-size: 14px;
|
||||||
@apply h-full;
|
@apply h-full;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
18
frontend/src/helpers/weather.ts
Normal file
18
frontend/src/helpers/weather.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
export interface WeatherData {
|
||||||
|
city: string;
|
||||||
|
temp: number;
|
||||||
|
description: string;
|
||||||
|
icon: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches weather data for the given city from backend API
|
||||||
|
*/
|
||||||
|
export const getWeather = async (city: string): Promise<WeatherData> => {
|
||||||
|
const response = await axios.get<WeatherData>(
|
||||||
|
`/api/weather?city=${encodeURIComponent(city)}`
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
@ -17,6 +17,7 @@ import { WidgetCreator } from '../components/WidgetCreator/WidgetCreator';
|
|||||||
import { SmartWidget } from '../components/SmartWidget/SmartWidget';
|
import { SmartWidget } from '../components/SmartWidget/SmartWidget';
|
||||||
|
|
||||||
import { useAppDispatch, useAppSelector } from '../stores/hooks';
|
import { useAppDispatch, useAppSelector } from '../stores/hooks';
|
||||||
|
import { getWeather, WeatherData } from '../helpers/weather';
|
||||||
const Dashboard = () => {
|
const Dashboard = () => {
|
||||||
const { t } = useTranslation('common');
|
const { t } = useTranslation('common');
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
@ -43,6 +44,9 @@ const Dashboard = () => {
|
|||||||
role: { value: '', label: '' },
|
role: { value: '', label: '' },
|
||||||
});
|
});
|
||||||
const { currentUser } = useAppSelector((state) => state.auth);
|
const { currentUser } = useAppSelector((state) => state.auth);
|
||||||
|
// Weather widget state
|
||||||
|
const [weather, setWeather] = React.useState<WeatherData | null>(null);
|
||||||
|
|
||||||
const { isFetchingQuery } = useAppSelector((state) => state.openAi);
|
const { isFetchingQuery } = useAppSelector((state) => state.openAi);
|
||||||
|
|
||||||
const { rolesWidgets, loading } = useAppSelector((state) => state.roles);
|
const { rolesWidgets, loading } = useAppSelector((state) => state.roles);
|
||||||
@ -110,6 +114,14 @@ const Dashboard = () => {
|
|||||||
getWidgets(widgetsRole?.role?.value || '').then();
|
getWidgets(widgetsRole?.role?.value || '').then();
|
||||||
}, [widgetsRole?.role?.value]);
|
}, [widgetsRole?.role?.value]);
|
||||||
|
|
||||||
|
// Load weather data
|
||||||
|
React.useEffect(() => {
|
||||||
|
getWeather('London')
|
||||||
|
.then((data) => setWeather(data))
|
||||||
|
.catch((err) => console.error('Weather load error', err));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
|
|||||||
567
frontend/src/pages/dashboard.tsx.temp
Normal file
567
frontend/src/pages/dashboard.tsx.temp
Normal file
@ -0,0 +1,567 @@
|
|||||||
|
import * as icon from '@mdi/js';
|
||||||
|
import Head from 'next/head';
|
||||||
|
import React from 'react';
|
||||||
|
import axios from 'axios';
|
||||||
|
import type { ReactElement } from 'react';
|
||||||
|
import LayoutAuthenticated from '../layouts/Authenticated';
|
||||||
|
import SectionMain from '../components/SectionMain';
|
||||||
|
import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton';
|
||||||
|
import BaseIcon from '../components/BaseIcon';
|
||||||
|
import { getPageTitle } from '../config';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
|
|
||||||
|
import { hasPermission } from '../helpers/userPermissions';
|
||||||
|
import { fetchWidgets } from '../stores/roles/rolesSlice';
|
||||||
|
import { WidgetCreator } from '../components/WidgetCreator/WidgetCreator';
|
||||||
|
import { SmartWidget } from '../components/SmartWidget/SmartWidget';
|
||||||
|
|
||||||
|
import { useAppDispatch, useAppSelector } from '../stores/hooks';
|
||||||
|
import { getWeather, WeatherData } from '../helpers/weather';
|
||||||
|
const Dashboard = () => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const iconsColor = useAppSelector((state) => state.style.iconsColor);
|
||||||
|
const corners = useAppSelector((state) => state.style.corners);
|
||||||
|
const cardsStyle = useAppSelector((state) => state.style.cardsStyle);
|
||||||
|
|
||||||
|
const loadingMessage = t('pages.dashboard.loading', {
|
||||||
|
defaultValue: 'Loading...',
|
||||||
|
});
|
||||||
|
|
||||||
|
const [users, setUsers] = React.useState(loadingMessage);
|
||||||
|
const [analytics, setAnalytics] = React.useState(loadingMessage);
|
||||||
|
const [courses, setCourses] = React.useState(loadingMessage);
|
||||||
|
const [discussion_boards, setDiscussion_boards] =
|
||||||
|
React.useState(loadingMessage);
|
||||||
|
const [enrollments, setEnrollments] = React.useState(loadingMessage);
|
||||||
|
const [instructors, setInstructors] = React.useState(loadingMessage);
|
||||||
|
const [students, setStudents] = React.useState(loadingMessage);
|
||||||
|
const [roles, setRoles] = React.useState(loadingMessage);
|
||||||
|
const [permissions, setPermissions] = React.useState(loadingMessage);
|
||||||
|
|
||||||
|
const [widgetsRole, setWidgetsRole] = React.useState({
|
||||||
|
role: { value: '', label: '' },
|
||||||
|
});
|
||||||
|
const { currentUser } = useAppSelector((state) => state.auth);
|
||||||
|
// Weather widget state
|
||||||
|
const [weather, setWeather] = React.useState<WeatherData | null>(null);
|
||||||
|
|
||||||
|
const { isFetchingQuery } = useAppSelector((state) => state.openAi);
|
||||||
|
|
||||||
|
const { rolesWidgets, loading } = useAppSelector((state) => state.roles);
|
||||||
|
|
||||||
|
async function loadData() {
|
||||||
|
const entities = [
|
||||||
|
'users',
|
||||||
|
'analytics',
|
||||||
|
'courses',
|
||||||
|
'discussion_boards',
|
||||||
|
'enrollments',
|
||||||
|
'instructors',
|
||||||
|
'students',
|
||||||
|
'roles',
|
||||||
|
'permissions',
|
||||||
|
];
|
||||||
|
const fns = [
|
||||||
|
setUsers,
|
||||||
|
setAnalytics,
|
||||||
|
setCourses,
|
||||||
|
setDiscussion_boards,
|
||||||
|
setEnrollments,
|
||||||
|
setInstructors,
|
||||||
|
setStudents,
|
||||||
|
setRoles,
|
||||||
|
setPermissions,
|
||||||
|
];
|
||||||
|
|
||||||
|
const requests = entities.map((entity, index) => {
|
||||||
|
if (hasPermission(currentUser, `READ_${entity.toUpperCase()}`)) {
|
||||||
|
return axios.get(`/${entity.toLowerCase()}/count`);
|
||||||
|
} else {
|
||||||
|
fns[index](null);
|
||||||
|
return Promise.resolve({ data: { count: null } });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Promise.allSettled(requests).then((results) => {
|
||||||
|
results.forEach((result, i) => {
|
||||||
|
if (result.status === 'fulfilled') {
|
||||||
|
fns[i](result.value.data.count);
|
||||||
|
} else {
|
||||||
|
fns[i](result.reason.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getWidgets(roleId) {
|
||||||
|
await dispatch(fetchWidgets(roleId));
|
||||||
|
}
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!currentUser) return;
|
||||||
|
loadData().then();
|
||||||
|
setWidgetsRole({
|
||||||
|
role: {
|
||||||
|
value: currentUser?.app_role?.id,
|
||||||
|
label: currentUser?.app_role?.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, [currentUser]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!currentUser || !widgetsRole?.role?.value) return;
|
||||||
|
getWidgets(widgetsRole?.role?.value || '').then();
|
||||||
|
}, [widgetsRole?.role?.value]);
|
||||||
|
|
||||||
|
// Load weather data
|
||||||
|
React.useEffect(() => {
|
||||||
|
getWeather('London')
|
||||||
|
.then((data) => setWeather(data))
|
||||||
|
.catch((err) => console.error('Weather load error', err));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Head>
|
||||||
|
<title>
|
||||||
|
{getPageTitle(
|
||||||
|
t('pages.dashboard.pageTitle', { defaultValue: 'Overview' }),
|
||||||
|
)}
|
||||||
|
</title>
|
||||||
|
</Head>
|
||||||
|
<SectionMain>
|
||||||
|
<SectionTitleLineWithButton
|
||||||
|
icon={icon.mdiChartTimelineVariant}
|
||||||
|
title={t('pages.dashboard.overview', { defaultValue: 'Overview' })}
|
||||||
|
main
|
||||||
|
>
|
||||||
|
{''}
|
||||||
|
</SectionTitleLineWithButton>
|
||||||
|
|
||||||
|
{hasPermission(currentUser, 'CREATE_ROLES') && (
|
||||||
|
<WidgetCreator
|
||||||
|
currentUser={currentUser}
|
||||||
|
isFetchingQuery={isFetchingQuery}
|
||||||
|
setWidgetsRole={setWidgetsRole}
|
||||||
|
widgetsRole={widgetsRole}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!!rolesWidgets.length &&
|
||||||
|
hasPermission(currentUser, 'CREATE_ROLES') && (
|
||||||
|
<p className=' text-gray-500 dark:text-gray-400 mb-4'>
|
||||||
|
{`${widgetsRole?.role?.label || 'Users'}'s widgets`}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className='grid grid-cols-1 gap-6 lg:grid-cols-4 mb-6 grid-flow-dense'>
|
||||||
|
{(isFetchingQuery || loading) && (
|
||||||
|
<div
|
||||||
|
className={` ${
|
||||||
|
corners !== 'rounded-full' ? corners : 'rounded-3xl'
|
||||||
|
} dark:bg-dark-900 text-lg leading-tight text-gray-500 flex items-center ${cardsStyle} dark:border-dark-700 p-6`}
|
||||||
|
>
|
||||||
|
<BaseIcon
|
||||||
|
className={`${iconsColor} animate-spin mr-5`}
|
||||||
|
w='w-16'
|
||||||
|
h='h-16'
|
||||||
|
size={48}
|
||||||
|
path={icon.mdiLoading}
|
||||||
|
/>{' '}
|
||||||
|
{t('pages.dashboard.loadingWidgets', {
|
||||||
|
defaultValue: 'Loading widgets...',
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{rolesWidgets &&
|
||||||
|
rolesWidgets.map((widget) => (
|
||||||
|
<SmartWidget
|
||||||
|
key={widget.id}
|
||||||
|
userId={currentUser?.id}
|
||||||
|
widget={widget}
|
||||||
|
roleId={widgetsRole?.role?.value || ''}
|
||||||
|
admin={hasPermission(currentUser, 'CREATE_ROLES')}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!!rolesWidgets.length && <hr className='my-6 ' />}
|
||||||
|
|
||||||
|
<div
|
||||||
|
id='dashboard'
|
||||||
|
className='grid grid-cols-1 gap-6 lg:grid-cols-3 mb-6'
|
||||||
|
>
|
||||||
|
{currentUser && (
|
||||||
|
<div className={`${corners !== 'rounded-full' ? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}>
|
||||||
|
<div className='flex justify-between align-center'>
|
||||||
|
<div>
|
||||||
|
<div className='text-lg leading-tight text-gray-500 dark:text-gray-400'>
|
||||||
|
{t('pages.dashboard.currentUser', { defaultValue: 'Current User' })}
|
||||||
|
</div>
|
||||||
|
<div className='text-xl leading-tight font-medium'>
|
||||||
|
{`${currentUser.firstName} ${currentUser.lastName}`}
|
||||||
|
</div>
|
||||||
|
<div className='text-sm leading-tight text-gray-600 dark:text-gray-500'>
|
||||||
|
{currentUser.email}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<BaseIcon
|
||||||
|
className={`${iconsColor}`}
|
||||||
|
size={48}
|
||||||
|
path={icon.mdiAccount}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{weather && (
|
||||||
|
<div className={`${corners !== 'rounded-full' ? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}>
|
||||||
|
<div className='flex justify-between items-center'>
|
||||||
|
<div>
|
||||||
|
<div className='text-lg leading-tight text-gray-500 dark:text-gray-400'>
|
||||||
|
Weather
|
||||||
|
</div>
|
||||||
|
<div className='text-3xl leading-tight font-semibold'>
|
||||||
|
{weather.temp}°C
|
||||||
|
</div>
|
||||||
|
<div className='text-sm leading-tight text-gray-600 dark:text-gray-500'>
|
||||||
|
{weather.description}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<img src={weather.icon} alt={weather.description} className='w-16 h-16'/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
)}
|
||||||
|
{hasPermission(currentUser, 'READ_USERS') && (
|
||||||
|
<Link href={'/users/users-list'}>
|
||||||
|
<div
|
||||||
|
className={`${
|
||||||
|
corners !== 'rounded-full' ? corners : 'rounded-3xl'
|
||||||
|
} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||||
|
>
|
||||||
|
<div className='flex justify-between align-center'>
|
||||||
|
<div>
|
||||||
|
<div className='text-lg leading-tight text-gray-500 dark:text-gray-400'>
|
||||||
|
Users
|
||||||
|
</div>
|
||||||
|
<div className='text-3xl leading-tight font-semibold'>
|
||||||
|
{users}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<BaseIcon
|
||||||
|
className={`${iconsColor}`}
|
||||||
|
w='w-16'
|
||||||
|
h='h-16'
|
||||||
|
size={48}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
path={icon.mdiAccountGroup || icon.mdiTable}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{hasPermission(currentUser, 'READ_ANALYTICS') && (
|
||||||
|
<Link href={'/analytics/analytics-list'}>
|
||||||
|
<div
|
||||||
|
className={`${
|
||||||
|
corners !== 'rounded-full' ? corners : 'rounded-3xl'
|
||||||
|
} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||||
|
>
|
||||||
|
<div className='flex justify-between align-center'>
|
||||||
|
<div>
|
||||||
|
<div className='text-lg leading-tight text-gray-500 dark:text-gray-400'>
|
||||||
|
Analytics
|
||||||
|
</div>
|
||||||
|
<div className='text-3xl leading-tight font-semibold'>
|
||||||
|
{analytics}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<BaseIcon
|
||||||
|
className={`${iconsColor}`}
|
||||||
|
w='w-16'
|
||||||
|
h='h-16'
|
||||||
|
size={48}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
path={
|
||||||
|
'mdiChartLine' in icon
|
||||||
|
? icon['mdiChartLine' as keyof typeof icon]
|
||||||
|
: icon.mdiTable || icon.mdiTable
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{hasPermission(currentUser, 'READ_COURSES') && (
|
||||||
|
<Link href={'/courses/courses-list'}>
|
||||||
|
<div
|
||||||
|
className={`${
|
||||||
|
corners !== 'rounded-full' ? corners : 'rounded-3xl'
|
||||||
|
} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||||
|
>
|
||||||
|
<div className='flex justify-between align-center'>
|
||||||
|
<div>
|
||||||
|
<div className='text-lg leading-tight text-gray-500 dark:text-gray-400'>
|
||||||
|
Courses
|
||||||
|
</div>
|
||||||
|
<div className='text-3xl leading-tight font-semibold'>
|
||||||
|
{courses}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<BaseIcon
|
||||||
|
className={`${iconsColor}`}
|
||||||
|
w='w-16'
|
||||||
|
h='h-16'
|
||||||
|
size={48}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
path={
|
||||||
|
'mdiBookOpenPageVariant' in icon
|
||||||
|
? icon['mdiBookOpenPageVariant' as keyof typeof icon]
|
||||||
|
: icon.mdiTable || icon.mdiTable
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{hasPermission(currentUser, 'READ_DISCUSSION_BOARDS') && (
|
||||||
|
<Link href={'/discussion_boards/discussion_boards-list'}>
|
||||||
|
<div
|
||||||
|
className={`${
|
||||||
|
corners !== 'rounded-full' ? corners : 'rounded-3xl'
|
||||||
|
} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||||
|
>
|
||||||
|
<div className='flex justify-between align-center'>
|
||||||
|
<div>
|
||||||
|
<div className='text-lg leading-tight text-gray-500 dark:text-gray-400'>
|
||||||
|
Discussion boards
|
||||||
|
</div>
|
||||||
|
<div className='text-3xl leading-tight font-semibold'>
|
||||||
|
{discussion_boards}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<BaseIcon
|
||||||
|
className={`${iconsColor}`}
|
||||||
|
w='w-16'
|
||||||
|
h='h-16'
|
||||||
|
size={48}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
path={
|
||||||
|
'mdiForumOutline' in icon
|
||||||
|
? icon['mdiForumOutline' as keyof typeof icon]
|
||||||
|
: icon.mdiTable || icon.mdiTable
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{hasPermission(currentUser, 'READ_ENROLLMENTS') && (
|
||||||
|
<Link href={'/enrollments/enrollments-list'}>
|
||||||
|
<div
|
||||||
|
className={`${
|
||||||
|
corners !== 'rounded-full' ? corners : 'rounded-3xl'
|
||||||
|
} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||||
|
>
|
||||||
|
<div className='flex justify-between align-center'>
|
||||||
|
<div>
|
||||||
|
<div className='text-lg leading-tight text-gray-500 dark:text-gray-400'>
|
||||||
|
Enrollments
|
||||||
|
</div>
|
||||||
|
<div className='text-3xl leading-tight font-semibold'>
|
||||||
|
{enrollments}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<BaseIcon
|
||||||
|
className={`${iconsColor}`}
|
||||||
|
w='w-16'
|
||||||
|
h='h-16'
|
||||||
|
size={48}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
path={
|
||||||
|
'mdiClipboardCheckOutline' in icon
|
||||||
|
? icon[
|
||||||
|
'mdiClipboardCheckOutline' as keyof typeof icon
|
||||||
|
]
|
||||||
|
: icon.mdiTable || icon.mdiTable
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{hasPermission(currentUser, 'READ_INSTRUCTORS') && (
|
||||||
|
<Link href={'/instructors/instructors-list'}>
|
||||||
|
<div
|
||||||
|
className={`${
|
||||||
|
corners !== 'rounded-full' ? corners : 'rounded-3xl'
|
||||||
|
} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||||
|
>
|
||||||
|
<div className='flex justify-between align-center'>
|
||||||
|
<div>
|
||||||
|
<div className='text-lg leading-tight text-gray-500 dark:text-gray-400'>
|
||||||
|
Instructors
|
||||||
|
</div>
|
||||||
|
<div className='text-3xl leading-tight font-semibold'>
|
||||||
|
{instructors}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<BaseIcon
|
||||||
|
className={`${iconsColor}`}
|
||||||
|
w='w-16'
|
||||||
|
h='h-16'
|
||||||
|
size={48}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
path={
|
||||||
|
'mdiAccountTie' in icon
|
||||||
|
? icon['mdiAccountTie' as keyof typeof icon]
|
||||||
|
: icon.mdiTable || icon.mdiTable
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{hasPermission(currentUser, 'READ_STUDENTS') && (
|
||||||
|
<Link href={'/students/students-list'}>
|
||||||
|
<div
|
||||||
|
className={`${
|
||||||
|
corners !== 'rounded-full' ? corners : 'rounded-3xl'
|
||||||
|
} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||||
|
>
|
||||||
|
<div className='flex justify-between align-center'>
|
||||||
|
<div>
|
||||||
|
<div className='text-lg leading-tight text-gray-500 dark:text-gray-400'>
|
||||||
|
Students
|
||||||
|
</div>
|
||||||
|
<div className='text-3xl leading-tight font-semibold'>
|
||||||
|
{students}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<BaseIcon
|
||||||
|
className={`${iconsColor}`}
|
||||||
|
w='w-16'
|
||||||
|
h='h-16'
|
||||||
|
size={48}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
path={
|
||||||
|
'mdiSchool' in icon
|
||||||
|
? icon['mdiSchool' as keyof typeof icon]
|
||||||
|
: icon.mdiTable || icon.mdiTable
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{hasPermission(currentUser, 'READ_ROLES') && (
|
||||||
|
<Link href={'/roles/roles-list'}>
|
||||||
|
<div
|
||||||
|
className={`${
|
||||||
|
corners !== 'rounded-full' ? corners : 'rounded-3xl'
|
||||||
|
} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||||
|
>
|
||||||
|
<div className='flex justify-between align-center'>
|
||||||
|
<div>
|
||||||
|
<div className='text-lg leading-tight text-gray-500 dark:text-gray-400'>
|
||||||
|
Roles
|
||||||
|
</div>
|
||||||
|
<div className='text-3xl leading-tight font-semibold'>
|
||||||
|
{roles}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<BaseIcon
|
||||||
|
className={`${iconsColor}`}
|
||||||
|
w='w-16'
|
||||||
|
h='h-16'
|
||||||
|
size={48}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
path={
|
||||||
|
icon.mdiShieldAccountVariantOutline || icon.mdiTable
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{hasPermission(currentUser, 'READ_PERMISSIONS') && (
|
||||||
|
<Link href={'/permissions/permissions-list'}>
|
||||||
|
<div
|
||||||
|
className={`${
|
||||||
|
corners !== 'rounded-full' ? corners : 'rounded-3xl'
|
||||||
|
} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||||
|
>
|
||||||
|
<div className='flex justify-between align-center'>
|
||||||
|
<div>
|
||||||
|
<div className='text-lg leading-tight text-gray-500 dark:text-gray-400'>
|
||||||
|
Permissions
|
||||||
|
</div>
|
||||||
|
<div className='text-3xl leading-tight font-semibold'>
|
||||||
|
{permissions}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<BaseIcon
|
||||||
|
className={`${iconsColor}`}
|
||||||
|
w='w-16'
|
||||||
|
h='h-16'
|
||||||
|
size={48}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
path={icon.mdiShieldAccountOutline || icon.mdiTable}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</SectionMain>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Dashboard.getLayout = function getLayout(page: ReactElement) {
|
||||||
|
return <LayoutAuthenticated>{page}</LayoutAuthenticated>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Dashboard;
|
||||||
@ -65,6 +65,9 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
borderRadius: {
|
borderRadius: {
|
||||||
|
fontSize: {
|
||||||
|
base: '0.875rem',
|
||||||
|
},
|
||||||
'3xl': '2rem',
|
'3xl': '2rem',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user