weather 3

This commit is contained in:
Flatlogic Bot 2025-05-12 14:08:41 +00:00
parent 26f11af35b
commit 12295d7a64
4 changed files with 28 additions and 572 deletions

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
import axios from 'axios';
import axios, { AxiosResponse } from 'axios';
export interface WeatherData {
city: string;
@ -11,8 +11,8 @@ export interface WeatherData {
* 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)}`
const response: AxiosResponse<WeatherData> = await axios.get(
`/weather?city=${encodeURIComponent(city)}`
);
return response.data;
};

View File

@ -217,6 +217,28 @@ const Dashboard = () => {
</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

View File

@ -1,567 +0,0 @@
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;