Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
caff5271b8 | ||
|
|
8f190212ca | ||
|
|
bc7afa892d | ||
|
|
1925e4ef49 | ||
|
|
e81823aa47 | ||
|
|
b8c1684f9a | ||
|
|
96a0d6d541 | ||
|
|
12295d7a64 | ||
|
|
26f11af35b | ||
|
|
2207a69e3f | ||
|
|
da5e4a1618 | ||
|
|
221b0266a0 |
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,3 +1,8 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
*/node_modules/
|
*/node_modules/
|
||||||
*/build/
|
*/build/
|
||||||
|
|
||||||
|
**/node_modules/
|
||||||
|
**/build/
|
||||||
|
.DS_Store
|
||||||
|
.env
|
||||||
File diff suppressed because one or more lines are too long
@ -33,6 +33,8 @@ const instructorsRoutes = require('./routes/instructors');
|
|||||||
|
|
||||||
const studentsRoutes = require('./routes/students');
|
const studentsRoutes = require('./routes/students');
|
||||||
|
|
||||||
|
const weatherRoutes = require('./routes/weather');
|
||||||
|
const exchangeRoutes = require('./routes/exchange');
|
||||||
const rolesRoutes = require('./routes/roles');
|
const rolesRoutes = require('./routes/roles');
|
||||||
|
|
||||||
const permissionsRoutes = require('./routes/permissions');
|
const permissionsRoutes = require('./routes/permissions');
|
||||||
@ -101,6 +103,9 @@ app.use('/api/auth', authRoutes);
|
|||||||
app.use('/api/file', fileRoutes);
|
app.use('/api/file', fileRoutes);
|
||||||
app.use('/api/pexels', pexelsRoutes);
|
app.use('/api/pexels', pexelsRoutes);
|
||||||
app.enable('trust proxy');
|
app.enable('trust proxy');
|
||||||
|
app.use('/api/exchange', exchangeRoutes);
|
||||||
|
app.use('/exchange', exchangeRoutes);
|
||||||
|
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
'/api/users',
|
'/api/users',
|
||||||
@ -169,6 +174,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');
|
||||||
|
|
||||||
|
|||||||
26
backend/src/routes/exchange.js
Normal file
26
backend/src/routes/exchange.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const axios = require('axios');
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
// GET /api/exchange?base=USD
|
||||||
|
router.get('/', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const apiKey = process.env.EXCHANGE_API_KEY;
|
||||||
|
const baseCurrency = req.query.base || 'USD';
|
||||||
|
if (apiKey) {
|
||||||
|
const url = `https://api.apilayer.com/exchangerates_data/latest?base=${encodeURIComponent(baseCurrency)}&symbols=USD,EUR`;
|
||||||
|
const response = await axios.get(url, { headers: { apikey: apiKey } });
|
||||||
|
return res.json(response.data);
|
||||||
|
} else {
|
||||||
|
const url = `https://api.exchangerate.host/latest?base=${encodeURIComponent(baseCurrency)}&symbols=USD,EUR`;
|
||||||
|
const response = await axios.get(url);
|
||||||
|
return res.json(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Exchange route error:', error.message);
|
||||||
|
res.status(500).json({ error: 'Failed to fetch exchange rates' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
28
backend/src/routes/exchange.js.temp
Normal file
28
backend/src/routes/exchange.js.temp
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const axios = require('axios');
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
// GET /api/exchange?base=USD
|
||||||
|
router.get('/', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const apiKey = process.env.EXCHANGE_API_KEY;
|
||||||
|
const baseCurrency = req.query.base || 'USD';
|
||||||
|
if (apiKey) {
|
||||||
|
const url = `https://api.apilayer.com/exchangerates_data/latest?base=${encodeURIComponent(baseCurrency)}&symbols=USD,EUR`;
|
||||||
|
const response = await axios.get(url, { headers: { apikey: apiKey } });
|
||||||
|
return res.json(response.data);
|
||||||
|
}
|
||||||
|
const url = `https://api.exchangerate.host/latest?base=${encodeURIComponent(baseCurrency)}&symbols=USD,EUR`;
|
||||||
|
const response = await axios.get(url);
|
||||||
|
return res.json(response.data);
|
||||||
|
const response = await axios.get(url);
|
||||||
|
return res.json(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Exchange route error:', error.message);
|
||||||
|
res.status(500).json({ error: 'Failed to fetch exchange rates' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
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 || 'a67c348e50fc2db380ee36e1219db498';
|
||||||
|
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
frontend/json/runtimeError.json
Normal file
1
frontend/json/runtimeError.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
@ -5,6 +5,7 @@
|
|||||||
"overview": "Übersicht",
|
"overview": "Übersicht",
|
||||||
"loadingWidgets": "Widgets werden geladen...",
|
"loadingWidgets": "Widgets werden geladen...",
|
||||||
"loading": "Laden..."
|
"loading": "Laden..."
|
||||||
|
"currentUser": "Aktueller Benutzer",
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"pageTitle": "Anmeldung",
|
"pageTitle": "Anmeldung",
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
"overview": "Overview",
|
"overview": "Overview",
|
||||||
"loadingWidgets": "Loading widgets...",
|
"loadingWidgets": "Loading widgets...",
|
||||||
"loading": "Loading..."
|
"loading": "Loading..."
|
||||||
|
"currentUser": "Current User",
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"pageTitle": "Login",
|
"pageTitle": "Login",
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
"overview": "Resumen",
|
"overview": "Resumen",
|
||||||
"loadingWidgets": "Cargando widgets...",
|
"loadingWidgets": "Cargando widgets...",
|
||||||
"loading": "Cargando..."
|
"loading": "Cargando..."
|
||||||
|
"currentUser": "Usuario actual",
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"pageTitle": "Inicio de sesión",
|
"pageTitle": "Inicio de sesión",
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
"overview": "Vue d'ensemble",
|
"overview": "Vue d'ensemble",
|
||||||
"loadingWidgets": "Chargement des widgets...",
|
"loadingWidgets": "Chargement des widgets...",
|
||||||
"loading": "Chargement..."
|
"loading": "Chargement..."
|
||||||
|
"currentUser": "Utilisateur actuel",
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"pageTitle": "Connexion",
|
"pageTitle": "Connexion",
|
||||||
|
|||||||
@ -12,20 +12,20 @@ export const colorsBgLight = {
|
|||||||
light: ' bg-white text-black text-black dark:bg-dark-900 dark:text-white',
|
light: ' bg-white text-black text-black dark:bg-dark-900 dark:text-white',
|
||||||
contrast: 'bg-gray-800 text-white dark:bg-white dark:text-black',
|
contrast: 'bg-gray-800 text-white dark:bg-white dark:text-black',
|
||||||
success:
|
success:
|
||||||
'bg-emerald-500 border-emerald-500 dark:bg-pavitra-blue dark:border-pavitra-blue text-white',
|
'bg-green-500 border-green-500 dark:bg-green-700 dark:border-green-700 text-white',
|
||||||
danger: 'bg-red-500 border-red-500 text-white',
|
danger: 'bg-red-500 border-red-500 text-white',
|
||||||
warning: 'bg-yellow-500 border-yellow-500 text-white',
|
warning: 'bg-yellow-500 border-yellow-500 text-white',
|
||||||
info: 'bg-blue-500 border-blue-500 dark:bg-pavitra-blue dark:border-pavitra-blue text-white',
|
info: 'bg-green-500 border-green-500 dark:bg-green-700 dark:border-green-700 text-white',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const colorsText = {
|
export const colorsText = {
|
||||||
white: 'text-black dark:text-slate-100',
|
white: 'text-black dark:text-slate-100',
|
||||||
light: 'text-gray-700 dark:text-slate-400',
|
light: 'text-gray-700 dark:text-slate-400',
|
||||||
contrast: 'dark:text-white',
|
contrast: 'dark:text-white',
|
||||||
success: 'text-emerald-500',
|
success: 'text-green-600 dark:text-green-500',
|
||||||
danger: 'text-red-500',
|
danger: 'text-red-500',
|
||||||
warning: 'text-yellow-500',
|
warning: 'text-yellow-500',
|
||||||
info: 'text-blue-500',
|
info: 'text-green-600 dark:text-green-500',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const colorsOutline = {
|
export const colorsOutline = {
|
||||||
@ -34,10 +34,10 @@ export const colorsOutline = {
|
|||||||
contrast: [colorsText.contrast, 'border-gray-900 dark:border-slate-100'].join(
|
contrast: [colorsText.contrast, 'border-gray-900 dark:border-slate-100'].join(
|
||||||
' ',
|
' ',
|
||||||
),
|
),
|
||||||
success: [colorsText.success, 'border-emerald-500'].join(' '),
|
success: [colorsText.success, 'border-green-500'].join(' '),
|
||||||
danger: [colorsText.danger, 'border-red-500'].join(' '),
|
danger: [colorsText.danger, 'border-red-500'].join(' '),
|
||||||
warning: [colorsText.warning, 'border-yellow-500'].join(' '),
|
warning: [colorsText.warning, 'border-yellow-500'].join(' '),
|
||||||
info: [colorsText.info, 'border-blue-500'].join(' '),
|
info: [colorsText.info, 'border-green-500'].join(' '),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getButtonColor = (
|
export const getButtonColor = (
|
||||||
@ -56,30 +56,30 @@ export const getButtonColor = (
|
|||||||
whiteDark: 'ring-gray-200 dark:ring-dark-500',
|
whiteDark: 'ring-gray-200 dark:ring-dark-500',
|
||||||
lightDark: 'ring-gray-200 dark:ring-gray-500',
|
lightDark: 'ring-gray-200 dark:ring-gray-500',
|
||||||
contrast: 'ring-gray-300 dark:ring-gray-400',
|
contrast: 'ring-gray-300 dark:ring-gray-400',
|
||||||
success: 'ring-emerald-300 dark:ring-pavitra-blue',
|
success: 'ring-green-300 dark:ring-green-700',
|
||||||
danger: 'ring-red-300 dark:ring-red-700',
|
danger: 'ring-red-300 dark:ring-red-700',
|
||||||
warning: 'ring-yellow-300 dark:ring-yellow-700',
|
warning: 'ring-yellow-300 dark:ring-yellow-700',
|
||||||
info: 'ring-blue-300 dark:ring-pavitra-blue',
|
info: 'ring-green-300 dark:ring-green-700',
|
||||||
},
|
},
|
||||||
active: {
|
active: {
|
||||||
white: 'bg-gray-100',
|
white: 'bg-gray-100',
|
||||||
whiteDark: 'bg-gray-100 dark:bg-dark-800',
|
whiteDark: 'bg-gray-100 dark:bg-dark-800',
|
||||||
lightDark: 'bg-gray-200 dark:bg-slate-700',
|
lightDark: 'bg-gray-200 dark:bg-slate-700',
|
||||||
contrast: 'bg-gray-700 dark:bg-slate-100',
|
contrast: 'bg-gray-700 dark:bg-slate-100',
|
||||||
success: 'bg-emerald-700 dark:bg-pavitra-blue',
|
success: 'bg-green-700 dark:bg-green-500',
|
||||||
danger: 'bg-red-700 dark:bg-red-600',
|
danger: 'bg-red-700 dark:bg-red-600',
|
||||||
warning: 'bg-yellow-700 dark:bg-yellow-600',
|
warning: 'bg-yellow-700 dark:bg-yellow-600',
|
||||||
info: 'bg-blue-700 dark:bg-pavitra-blue',
|
info: 'bg-green-700 dark:bg-green-500',
|
||||||
},
|
},
|
||||||
bg: {
|
bg: {
|
||||||
white: 'bg-white text-black',
|
white: 'bg-white text-black',
|
||||||
whiteDark: 'bg-white text-black dark:bg-dark-900 dark:text-white',
|
whiteDark: 'bg-white text-black dark:bg-dark-900 dark:text-white',
|
||||||
lightDark: 'bg-gray-100 text-black dark:bg-slate-800 dark:text-white',
|
lightDark: 'bg-gray-100 text-black dark:bg-slate-800 dark:text-white',
|
||||||
contrast: 'bg-gray-800 text-white dark:bg-white dark:text-black',
|
contrast: 'bg-gray-800 text-white dark:bg-white dark:text-black',
|
||||||
success: 'bg-emerald-600 dark:bg-pavitra-blue text-white',
|
success: 'bg-green-600 dark:bg-green-500 text-white',
|
||||||
danger: 'bg-red-600 text-white dark:bg-red-500 ',
|
danger: 'bg-red-600 text-white dark:bg-red-500 ',
|
||||||
warning: 'bg-yellow-600 dark:bg-yellow-500 text-white',
|
warning: 'bg-yellow-600 dark:bg-yellow-500 text-white',
|
||||||
info: ' bg-blue-600 dark:bg-pavitra-blue text-white ',
|
info: 'bg-green-600 dark:bg-green-500 text-white',
|
||||||
},
|
},
|
||||||
bgHover: {
|
bgHover: {
|
||||||
white: 'hover:bg-gray-100',
|
white: 'hover:bg-gray-100',
|
||||||
@ -87,40 +87,40 @@ export const getButtonColor = (
|
|||||||
lightDark: 'hover:bg-gray-200 hover:dark:bg-slate-700',
|
lightDark: 'hover:bg-gray-200 hover:dark:bg-slate-700',
|
||||||
contrast: 'hover:bg-gray-700 hover:dark:bg-slate-100',
|
contrast: 'hover:bg-gray-700 hover:dark:bg-slate-100',
|
||||||
success:
|
success:
|
||||||
'hover:bg-emerald-700 hover:border-emerald-700 hover:dark:bg-pavitra-blue hover:dark:border-pavitra-blue',
|
'hover:bg-green-700 hover:border-green-700 hover:dark:bg-green-500 hover:dark:border-green-500',
|
||||||
danger:
|
danger:
|
||||||
'hover:bg-red-700 hover:border-red-700 hover:dark:bg-red-600 hover:dark:border-red-600',
|
'hover:bg-red-700 hover:border-red-700 hover:dark:bg-red-600 hover:dark:border-red-600',
|
||||||
warning:
|
warning:
|
||||||
'hover:bg-yellow-700 hover:border-yellow-700 hover:dark:bg-yellow-600 hover:dark:border-yellow-600',
|
'hover:bg-yellow-700 hover:border-yellow-700 hover:dark:bg-yellow-600 hover:dark:border-yellow-600',
|
||||||
info: 'hover:bg-blue-700 hover:border-blue-700 hover:dark:bg-pavitra-blue/80 hover:dark:border-pavitra-blue/80',
|
info: 'hover:bg-green-700 hover:border-green-700 hover:dark:bg-green-500 hover:dark:border-green-500',
|
||||||
},
|
},
|
||||||
borders: {
|
borders: {
|
||||||
white: 'border-white',
|
white: 'border-white',
|
||||||
whiteDark: 'border-white dark:border-dark-900',
|
whiteDark: 'border-white dark:border-dark-900',
|
||||||
lightDark: 'border-gray-100 dark:border-slate-800',
|
lightDark: 'border-gray-100 dark:border-slate-800',
|
||||||
contrast: 'border-gray-800 dark:border-white',
|
contrast: 'border-gray-800 dark:border-white',
|
||||||
success: 'border-emerald-600 dark:border-pavitra-blue',
|
success: 'border-green-600 dark:border-green-500',
|
||||||
danger: 'border-red-600 dark:border-red-500',
|
danger: 'border-red-600 dark:border-red-500',
|
||||||
warning: 'border-yellow-600 dark:border-yellow-500',
|
warning: 'border-yellow-600 dark:border-yellow-500',
|
||||||
info: 'border-blue-600 border-blue-600 dark:border-pavitra-blue',
|
info: 'border-green-600 dark:border-green-500',
|
||||||
},
|
},
|
||||||
text: {
|
text: {
|
||||||
contrast: 'dark:text-slate-100',
|
contrast: 'dark:text-slate-100',
|
||||||
success: 'text-emerald-600 dark:text-pavitra-blue',
|
success: 'text-emerald-600 dark:text-pavitra-blue',
|
||||||
danger: 'text-red-600 dark:text-red-500',
|
success: 'text-green-600 dark:text-green-500',
|
||||||
warning: 'text-yellow-600 dark:text-yellow-500',
|
warning: 'text-yellow-600 dark:text-yellow-500',
|
||||||
info: 'text-blue-600 dark:text-pavitra-blue',
|
info: 'text-green-600 dark:text-green-500',
|
||||||
},
|
},
|
||||||
outlineHover: {
|
outlineHover: {
|
||||||
contrast:
|
contrast:
|
||||||
'hover:bg-gray-800 hover:text-gray-100 hover:dark:bg-slate-100 hover:dark:text-black',
|
'hover:bg-gray-800 hover:text-gray-100 hover:dark:bg-slate-100 hover:dark:text-black',
|
||||||
success:
|
success:
|
||||||
'hover:bg-emerald-600 hover:text-white hover:text-white hover:dark:text-white hover:dark:border-pavitra-blue',
|
'hover:bg-green-600 hover:text-white hover:text-white hover:dark:text-white hover:dark:border-green-500',
|
||||||
danger:
|
danger:
|
||||||
'hover:bg-red-600 hover:text-white hover:text-white hover:dark:text-white hover:dark:border-red-600',
|
'hover:bg-red-600 hover:text-white hover:text-white hover:dark:text-white hover:dark:border-red-600',
|
||||||
warning:
|
warning:
|
||||||
'hover:bg-yellow-600 hover:text-white hover:text-white hover:dark:text-white hover:dark:border-yellow-600',
|
'hover:bg-yellow-600 hover:text-white hover:text-white hover:dark:text-white hover:dark:border-yellow-600',
|
||||||
info: 'hover:bg-blue-600 hover:bg-blue-600 hover:text-white hover:dark:text-white hover:dark:border-pavitra-blue',
|
info: 'hover:bg-green-600 hover:border-green-600 hover:text-white hover:dark:text-white hover:dark:border-green-500',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,7 @@ export const localStorageDarkModeKey = 'darkMode';
|
|||||||
|
|
||||||
export const localStorageStyleKey = 'style';
|
export const localStorageStyleKey = 'style';
|
||||||
|
|
||||||
export const containerMaxW = 'xl:max-w-full xl:mx-auto 2xl:mx-20';
|
export const containerMaxW = 'max-w-screen-2xl mx-auto px-4';
|
||||||
|
|
||||||
export const appTitle = 'created by Flatlogic generator!';
|
export const appTitle = 'created by Flatlogic generator!';
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
html {
|
html {
|
||||||
|
font-size: 14px;
|
||||||
@apply h-full;
|
@apply h-full;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
21
frontend/src/helpers/exchange.ts
Normal file
21
frontend/src/helpers/exchange.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import axios, { AxiosResponse } from 'axios';
|
||||||
|
|
||||||
|
export interface ExchangeData {
|
||||||
|
base: string;
|
||||||
|
rates: {
|
||||||
|
USD: number;
|
||||||
|
EUR: number;
|
||||||
|
};
|
||||||
|
date: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches latest exchange rates for USD and EUR relative to the given base currency
|
||||||
|
*/
|
||||||
|
export const getExchangeRates = async (
|
||||||
|
base: string
|
||||||
|
): Promise<ExchangeData> => {
|
||||||
|
const response: AxiosResponse<ExchangeData> = await axios.get(`/exchange?base=${encodeURIComponent(base)}`);
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
18
frontend/src/helpers/weather.ts
Normal file
18
frontend/src/helpers/weather.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import axios, { AxiosResponse } 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: AxiosResponse<WeatherData> = await axios.get(
|
||||||
|
`/weather?city=${encodeURIComponent(city)}`
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
@ -17,6 +17,8 @@ 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';
|
||||||
|
import { getExchangeRates, ExchangeData } from '../helpers/exchange';
|
||||||
const Dashboard = () => {
|
const Dashboard = () => {
|
||||||
const { t } = useTranslation('common');
|
const { t } = useTranslation('common');
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
@ -43,6 +45,16 @@ const Dashboard = () => {
|
|||||||
role: { value: '', label: '' },
|
role: { value: '', label: '' },
|
||||||
});
|
});
|
||||||
const { currentUser } = useAppSelector((state) => state.auth);
|
const { currentUser } = useAppSelector((state) => state.auth);
|
||||||
|
// Weather widget state
|
||||||
|
// City selection for weather widget
|
||||||
|
const [selectedCity, setSelectedCity] = React.useState('London');
|
||||||
|
|
||||||
|
const [weather, setWeather] = React.useState<WeatherData | null>(null);
|
||||||
|
const [exchangeData, setExchangeData] = React.useState<ExchangeData | null>(null);
|
||||||
|
const [baseCurrency, setBaseCurrency] = React.useState('USD');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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 +122,21 @@ const Dashboard = () => {
|
|||||||
getWidgets(widgetsRole?.role?.value || '').then();
|
getWidgets(widgetsRole?.role?.value || '').then();
|
||||||
}, [widgetsRole?.role?.value]);
|
}, [widgetsRole?.role?.value]);
|
||||||
|
|
||||||
|
// Load weather data
|
||||||
|
// Load exchange rates data
|
||||||
|
React.useEffect(() => {
|
||||||
|
getExchangeRates(baseCurrency)
|
||||||
|
.then(data => setExchangeData(data))
|
||||||
|
.catch(err => console.error('Exchange load error', err));
|
||||||
|
}, [baseCurrency]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
getWeather(selectedCity)
|
||||||
|
.then((data) => setWeather(data))
|
||||||
|
.catch((err) => console.error('Weather load error', err));
|
||||||
|
}, [selectedCity]);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
@ -181,6 +208,81 @@ const Dashboard = () => {
|
|||||||
id='dashboard'
|
id='dashboard'
|
||||||
className='grid grid-cols-1 gap-6 lg:grid-cols-3 mb-6'
|
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 flex-col">
|
||||||
|
<div className="flex justify-between items-center mb-4">
|
||||||
|
<div>
|
||||||
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
|
Weather
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<select
|
||||||
|
id="city-select"
|
||||||
|
value={selectedCity}
|
||||||
|
onChange={(e) => setSelectedCity(e.target.value)}
|
||||||
|
className="text-sm h-8 w-28 p-1 border border-gray-300 rounded-md focus:ring-green-500 focus:border-green-500 bg-white dark:bg-dark-800 dark:border-dark-600"
|
||||||
|
>
|
||||||
|
<option value="London">London</option>
|
||||||
|
<option value="Minsk">Minsk</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{exchangeData?.rates?.USD != null && (
|
||||||
|
<div className={`${corners !== 'rounded-full' ? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
|
Exchange Rates
|
||||||
|
</div>
|
||||||
|
<div className="mt-4 text-sm text-gray-600 dark:text-gray-500">
|
||||||
|
<div>{`1 ${exchangeData.base} = ${exchangeData.rates?.USD?.toFixed(4) ?? '-'} USD`}</div>
|
||||||
|
<div>{`1 ${exchangeData.base} = ${exchangeData.rates?.EUR?.toFixed(4) ?? '-'} EUR`}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{hasPermission(currentUser, 'READ_USERS') && (
|
{hasPermission(currentUser, 'READ_USERS') && (
|
||||||
<Link href={'/users/users-list'}>
|
<Link href={'/users/users-list'}>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -31,30 +31,30 @@ interface StyleState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const initialState: StyleState = {
|
const initialState: StyleState = {
|
||||||
asideStyle: styles.white.aside,
|
asideStyle: styles.pride.aside,
|
||||||
asideScrollbarsStyle: styles.white.asideScrollbars,
|
asideScrollbarsStyle: styles.pride.asideScrollbars,
|
||||||
asideBrandStyle: styles.white.asideBrand,
|
asideBrandStyle: styles.pride.asideBrand,
|
||||||
asideMenuItemStyle: styles.white.asideMenuItem,
|
asideMenuItemStyle: styles.pride.asideMenuItem,
|
||||||
asideMenuItemActiveStyle: styles.white.asideMenuItemActive,
|
asideMenuItemActiveStyle: styles.pride.asideMenuItemActive,
|
||||||
asideMenuDropdownStyle: styles.white.asideMenuDropdown,
|
asideMenuDropdownStyle: styles.pride.asideMenuDropdown,
|
||||||
navBarItemLabelStyle: styles.white.navBarItemLabel,
|
navBarItemLabelStyle: styles.pride.navBarItemLabel,
|
||||||
navBarItemLabelHoverStyle: styles.white.navBarItemLabelHover,
|
navBarItemLabelHoverStyle: styles.pride.navBarItemLabelHover,
|
||||||
navBarItemLabelActiveColorStyle: styles.white.navBarItemLabelActiveColor,
|
navBarItemLabelActiveColorStyle: styles.pride.navBarItemLabelActiveColor,
|
||||||
overlayStyle: styles.white.overlay,
|
overlayStyle: styles.pride.overlay,
|
||||||
darkMode: false,
|
darkMode: false,
|
||||||
bgLayoutColor: styles.white.bgLayoutColor,
|
bgLayoutColor: styles.pride.bgLayoutColor,
|
||||||
iconsColor: styles.white.iconsColor,
|
iconsColor: styles.pride.iconsColor,
|
||||||
activeLinkColor: styles.white.activeLinkColor,
|
activeLinkColor: styles.pride.activeLinkColor,
|
||||||
cardsColor: styles.white.cardsColor,
|
cardsColor: styles.pride.cardsColor,
|
||||||
focusRingColor: styles.white.focusRingColor,
|
focusRingColor: styles.pride.focusRingColor,
|
||||||
corners: styles.white.corners,
|
corners: styles.pride.corners,
|
||||||
cardsStyle: styles.white.cardsStyle,
|
cardsStyle: styles.pride.cardsStyle,
|
||||||
linkColor: styles.white.linkColor,
|
linkColor: styles.pride.linkColor,
|
||||||
websiteHeder: styles.white.websiteHeder,
|
websiteHeder: styles.pride.websiteHeder,
|
||||||
borders: styles.white.borders,
|
borders: styles.pride.borders,
|
||||||
shadow: styles.white.shadow,
|
shadow: styles.pride.shadow,
|
||||||
websiteSectionStyle: styles.white.websiteSectionStyle,
|
websiteSectionStyle: styles.pride.websiteSectionStyle,
|
||||||
textSecondary: styles.white.textSecondary,
|
textSecondary: styles.pride.textSecondary,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const styleSlice = createSlice({
|
export const styleSlice = createSlice({
|
||||||
|
|||||||
@ -25,53 +25,51 @@ interface StyleObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const basic: StyleObject = {
|
export const basic: StyleObject = {
|
||||||
aside: 'bg-gray-800',
|
aside: 'bg-green-100',
|
||||||
asideScrollbars: 'aside-scrollbars-gray',
|
asideScrollbars: 'aside-scrollbars-light',
|
||||||
asideBrand: 'bg-gray-900 text-white',
|
asideBrand: 'bg-green-100 text-green-900',
|
||||||
asideMenuItem: 'text-gray-300 hover:text-white',
|
asideMenuItem: 'text-green-700 hover:text-green-900',
|
||||||
asideMenuItemActive: 'font-bold text-white',
|
asideMenuItemActive: 'font-bold text-green-900',
|
||||||
asideMenuDropdown: 'bg-gray-700/50',
|
asideMenuDropdown: 'bg-green-100/50',
|
||||||
navBarItemLabel: 'text-black',
|
navBarItemLabel: 'text-green-700',
|
||||||
navBarItemLabelHover: 'hover:text-blue-500',
|
navBarItemLabelHover: 'hover:text-green-800',
|
||||||
navBarItemLabelActiveColor: 'text-blue-600',
|
navBarItemLabelActiveColor: 'text-green-800',
|
||||||
overlay: 'from-gray-700 via-gray-900 to-gray-700',
|
overlay: 'from-green-100 via-green-200 to-green-100',
|
||||||
activeLinkColor: 'bg-gray-100/70',
|
activeLinkColor: 'bg-green-100/70',
|
||||||
bgLayoutColor: 'bg-gray-50',
|
bgLayoutColor: 'bg-green-50',
|
||||||
iconsColor: 'text-blue-500',
|
iconsColor: 'text-green-500',
|
||||||
cardsColor: 'bg-white',
|
cardsColor: 'bg-green-100',
|
||||||
focusRingColor:
|
focusRingColor:
|
||||||
'focus:ring focus:ring-blue-600 focus:border-blue-600 focus:outline-none dark:focus:ring-blue-600 border-gray-300 dark:focus:border-blue-600',
|
'focus:ring focus:ring-green-600 focus:border-green-600 focus:outline-none dark:focus:ring-green-600 border-gray-300 dark:focus:border-green-600',
|
||||||
corners: 'rounded',
|
corners: 'rounded',
|
||||||
cardsStyle: 'bg-white border border-pavitra-400',
|
cardsStyle: 'bg-green-100 border border-green-200',
|
||||||
linkColor: 'text-black',
|
linkColor: 'text-green-700',
|
||||||
websiteHeder: '',
|
websiteHeder: '',
|
||||||
borders: '',
|
borders: '',
|
||||||
shadow: '',
|
shadow: '',
|
||||||
websiteSectionStyle: '',
|
websiteSectionStyle: '',
|
||||||
textSecondary: '',
|
textSecondary: 'text-green-700',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const white: StyleObject = {
|
export const white: StyleObject = {
|
||||||
aside: 'bg-white dark:text-white',
|
aside: 'bg-white text-gray-900',
|
||||||
asideScrollbars: 'aside-scrollbars-light',
|
asideScrollbars: 'aside-scrollbars-light',
|
||||||
asideBrand: '',
|
asideBrand: 'text-gray-900',
|
||||||
asideMenuItem:
|
asideMenuItem: 'text-gray-700 hover:text-gray-900',
|
||||||
'text-gray-700 hover:bg-gray-100/70 dark:text-dark-500 dark:hover:text-white dark:hover:bg-dark-800',
|
asideMenuItemActive: 'font-semibold text-gray-900',
|
||||||
asideMenuItemActive: 'font-bold text-black dark:text-white',
|
asideMenuDropdown: 'bg-gray-100',
|
||||||
asideMenuDropdown: 'bg-gray-100/75',
|
navBarItemLabel: 'text-gray-900',
|
||||||
navBarItemLabel: 'text-blue-600',
|
navBarItemLabelHover: 'hover:text-gray-900',
|
||||||
navBarItemLabelHover: 'hover:text-black',
|
navBarItemLabelActiveColor: 'text-gray-900',
|
||||||
navBarItemLabelActiveColor: 'text-black',
|
overlay: 'from-white via-white to-white',
|
||||||
overlay: 'from-white via-gray-100 to-white',
|
activeLinkColor: 'bg-gray-100',
|
||||||
activeLinkColor: 'bg-gray-100/70',
|
bgLayoutColor: 'bg-white',
|
||||||
bgLayoutColor: 'bg-gray-50',
|
iconsColor: 'text-gray-700',
|
||||||
iconsColor: 'text-blue-500',
|
|
||||||
cardsColor: 'bg-white',
|
cardsColor: 'bg-white',
|
||||||
focusRingColor:
|
focusRingColor: 'focus:ring focus:ring-gray-300 focus:border-gray-300 focus:outline-none border-gray-200',
|
||||||
'focus:ring focus:ring-blue-600 focus:border-blue-600 focus:outline-none border-gray-300 dark:focus:ring-blue-600 dark:focus:border-blue-600',
|
corners: 'none',
|
||||||
corners: 'rounded',
|
cardsStyle: 'bg-white border border-gray-200',
|
||||||
cardsStyle: 'bg-white border border-pavitra-400',
|
linkColor: 'text-gray-700',
|
||||||
linkColor: 'text-blue-600',
|
|
||||||
websiteHeder: 'border-b border-gray-200',
|
websiteHeder: 'border-b border-gray-200',
|
||||||
borders: 'border-gray-200',
|
borders: 'border-gray-200',
|
||||||
shadow: '',
|
shadow: '',
|
||||||
@ -79,6 +77,34 @@ export const white: StyleObject = {
|
|||||||
textSecondary: 'text-gray-500',
|
textSecondary: 'text-gray-500',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const pride: StyleObject = {
|
||||||
|
aside: 'bg-gradient-to-r from-red-500 via-yellow-300 to-purple-600',
|
||||||
|
asideScrollbars: 'aside-scrollbars-light',
|
||||||
|
asideBrand: 'text-white',
|
||||||
|
asideMenuItem: 'text-white hover:text-yellow-200',
|
||||||
|
asideMenuItemActive: 'font-semibold text-white',
|
||||||
|
asideMenuDropdown: 'bg-white/20',
|
||||||
|
navBarItemLabel: 'text-white',
|
||||||
|
navBarItemLabelHover: 'hover:text-yellow-200',
|
||||||
|
navBarItemLabelActiveColor: 'text-yellow-300',
|
||||||
|
overlay: 'from-red-500 via-yellow-500 to-purple-500',
|
||||||
|
activeLinkColor: 'bg-white/30',
|
||||||
|
bgLayoutColor: 'bg-gradient-to-r from-red-50 via-yellow-50 to-purple-50',
|
||||||
|
iconsColor: 'text-white',
|
||||||
|
cardsColor: 'bg-white/30',
|
||||||
|
focusRingColor: 'focus:ring focus:ring-pink-500 focus:border-pink-500 focus:outline-none',
|
||||||
|
corners: 'rounded-lg',
|
||||||
|
cardsStyle: 'bg-white/50 border border-white/50',
|
||||||
|
linkColor: 'text-white',
|
||||||
|
websiteHeder: 'border-b border-white/50',
|
||||||
|
borders: 'border-white/50',
|
||||||
|
shadow: 'shadow-lg',
|
||||||
|
websiteSectionStyle: '',
|
||||||
|
textSecondary: 'text-gray-200',
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export const dataGridStyles = {
|
export const dataGridStyles = {
|
||||||
'& .MuiDataGrid-cell': {
|
'& .MuiDataGrid-cell': {
|
||||||
paddingX: 3,
|
paddingX: 3,
|
||||||
|
|||||||
@ -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