diff --git a/package.json b/package.json
index 607b709..1112cd7 100644
--- a/package.json
+++ b/package.json
@@ -30,7 +30,9 @@
"react-router-dom": "^7.11.0",
"sass": "^1.97.0",
"swr": "^2.3.8",
- "vite-plugin-compression": "^0.5.1"
+ "vite-plugin-compression": "^0.5.1",
+ "echarts": "^6.0.0",
+ "echarts-for-react": "^3.0.5"
},
"devDependencies": {
"typescript":">=4.0.0",
diff --git a/public/locales/bg.yaml b/public/locales/bg.yaml
index ff79441..111adbf 100644
--- a/public/locales/bg.yaml
+++ b/public/locales/bg.yaml
@@ -109,6 +109,11 @@ network_devices:
new_network_device: Ново мрежово устройство
edit:
edit_network_device: Редакция на мрежово устройство
+rooms:
+ big-room: Голяма зала
+ small-room: Малка зала
+ kitchen: Кухня
+ outside: Навън
views:
dashboard:
sensor_readings: Температура и влажност на въздуха
@@ -119,7 +124,7 @@ views:
colibri_message_contact: |
Ако имате проблем с ползването му, или е нужно да заявите достъп за нови служители, моля свържете се с Инит Лаб на
имейл us@initlab.org.
- colibri_message_emergency: При неотложни случаи, моля обадете се на тел. 0883 433 990 (Венцислав).
+ colibri_message_emergency: При неотложни случаи, моля обадете се на тел. 02 422 54 36.
devices:
lock: заключи
unlock: отключи
@@ -165,6 +170,8 @@ views:
lights: Осветление
hvac: Климатици
sensors: Графики
+ dark_mode: Тъмна тема
+ language: Език
oauth_application_management: OAuth интеграция
oauth_token_management: Упълномощени приложения
registrations:
@@ -227,6 +234,8 @@ views:
are_you_sure: 'Сигурни ли сте?'
sensors:
title: Показания на сензорите
+ temperature: Температура
+ humidity: Влажност
action_log:
title: Лог на действията
columns:
diff --git a/public/locales/en.yaml b/public/locales/en.yaml
index 36c32e0..3882310 100644
--- a/public/locales/en.yaml
+++ b/public/locales/en.yaml
@@ -103,6 +103,11 @@ network_devices:
new_network_device: New network device
edit:
edit_network_device: Edit network device
+rooms:
+ big-room: Big Room
+ small-room: Small Room
+ kitchen: Kitchen
+ outside: Outside
views:
dashboard:
sensor_readings: Air temperature and humidity
@@ -112,7 +117,7 @@ views:
colibri_message_contact: |
In case of technical difficulties, or you need to request access for new employees, please contact init Lab
at us@initlab.org.
- colibri_message_emergency: In case of major malfunction, please call +359 883 433 990 (Vencislav).
+ colibri_message_emergency: In case of major malfunction, please call +359 2 422 54 36.
devices:
lock: lock
unlock: unlock
@@ -158,6 +163,8 @@ views:
lights: Lights
hvac: HVAC
sensors: Sensors
+ dark_mode: Dark Mode
+ language: Language
oauth_application_management: OAuth integration
oauth_token_management: Authorized applications
registrations:
@@ -219,6 +226,8 @@ views:
are_you_sure: 'Are you shnur?'
sensors:
title: Sensor readings
+ temperature: Temperature
+ humidity: Humidity
action_log:
title: Action logs
columns:
diff --git a/src/App.tsx b/src/App.tsx
index 5c56a58..eedd1cf 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,4 +1,4 @@
-import {createElement} from 'react';
+import {createElement, useEffect} from 'react';
import {Container} from 'react-bootstrap';
import {Route, Routes} from 'react-router-dom';
@@ -14,10 +14,13 @@ import {useVariant} from './hooks/useVariant.ts';
import {getDoorActions, getHvacActions, getLightActions} from "./utils/device.ts";
import Devices from "./pages/Devices.tsx";
import {useDocumentTitle} from '@uidotdev/usehooks';
+import {useTheme} from './hooks/useTheme.ts';
function App() {
const variant = useVariant();
useDocumentTitle(variant.title);
+ const [theme] = useTheme();
+ useEffect(() => document.documentElement.setAttribute('data-bs-theme', theme), [theme]);
return (<>
diff --git a/src/config.ts b/src/config.ts
index dea8439..1c29a3b 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -13,10 +13,10 @@ export const oidc = {
export type MqttConfig = {[topic: string]: {label: string}}
export const sensors: MqttConfig = {
- 'sensors/big-room': {label: 'Big room',},
- 'sensors/small-room': {label: 'Small room',},
- 'sensors/kitchen': {label: 'Kitchen',},
- 'sensors/outside': {label: 'Outside',},
+ 'sensors/big-room/test': {label: 'big-room'},
+ 'sensors/small-room/test': {label: 'small-room',},
+ 'sensors/kitchen/test': {label: 'kitchen',},
+ 'sensors/outside/test': {label: 'outside',},
};
export const variantHosts: {[hostname: string]: string} = {
@@ -45,12 +45,4 @@ export const variants: {[variant: string]: VariantConfig} = {
logo: {url: colibriLogo, alt: 'colibri logo',},
title: 'Casa Libri',
},
-}
-
-export const grafana = {
- urls: [
- 'https://stats.initlab.org/d-solo/SGAb0ZXMk/temperature-and-humidity?orgId=1&refresh=1m&panelId=4',
- 'https://stats.initlab.org/d-solo/SGAb0ZXMk/temperature-and-humidity?orgId=1&refresh=1m&panelId=5',
- 'https://stats.initlab.org/d-solo/SGAb0ZXMk/temperature-and-humidity?orgId=1&refresh=1m&panelId=10',
- ]
-};
+}
\ No newline at end of file
diff --git a/src/hooks/useEndpoints.ts b/src/hooks/useEndpoints.ts
index 0c41aa6..edc4549 100644
--- a/src/hooks/useEndpoints.ts
+++ b/src/hooks/useEndpoints.ts
@@ -2,7 +2,7 @@ import useSWR, {type Fetcher, type SWRConfiguration} from 'swr';
import {authenticatedFetcher, fetcher} from '../utils/swr.js';
import {useAuthStorage} from './useAuthStorage.ts';
import type {FaunaPresentUser, FaunaUser} from "../fauna-types";
-import type {PortierActionLogEntry, PortierDevice, RawMqttReading} from "../portier-types";
+import type {MqttSensorHistory, PortierActionLogEntry, PortierDevice, RawMqttReading} from "../portier-types";
function useAuthSWR(key: URL | string, config?: SWRConfiguration) {
const {accessToken} = useAuthStorage();
@@ -33,6 +33,12 @@ export function useMqttStatus(config?: SWRConfiguration) {
return useCheckSWR<{ [topic: string]: RawMqttReading }>(import.meta.env.MQTT_PROXY_URL.concat('status'), config);
}
+export function useMqttHistory(config?: SWRConfiguration) {
+ return useCheckSWR<{
+ [sensor: string]: MqttSensorHistory
+ }>(import.meta.env.MQTT_PROXY_URL.concat('sensors/'), config);
+}
+
export function useActionLog(config?: SWRConfiguration) {
return useAuthSWR(import.meta.env.PORTIER_URL.concat('api/actionLog/0/0'), config);
}
diff --git a/src/hooks/useLocale.ts b/src/hooks/useLocale.ts
new file mode 100644
index 0000000..9039d7b
--- /dev/null
+++ b/src/hooks/useLocale.ts
@@ -0,0 +1,20 @@
+import {type Dispatch, useEffect} from 'react';
+import {useLocalStorage} from '@uidotdev/usehooks';
+import {useCurrentUser} from './useEndpoints.ts';
+import {useTranslation} from "react-i18next";
+
+const LOCALE_KEY = 'locale';
+
+export function useLocale(): [string | undefined, Dispatch] {
+ const {data: user} = useCurrentUser();
+ const [locale, setStoredLocale] = useLocalStorage(LOCALE_KEY);
+
+ const {i18n} = useTranslation();
+
+ useEffect(() => {
+ i18n.changeLanguage(locale ?? user?.locale ?? 'bg').then(() => {})
+ }, [i18n, locale, user?.locale]);
+
+
+ return [locale, setStoredLocale];
+}
diff --git a/src/hooks/useTheme.ts b/src/hooks/useTheme.ts
new file mode 100644
index 0000000..e9987ef
--- /dev/null
+++ b/src/hooks/useTheme.ts
@@ -0,0 +1,8 @@
+import {useLocalStorage, useMediaQuery} from '@uidotdev/usehooks';
+
+const THEME_KEY = 'theme';
+
+export function useTheme() {
+ const darkMode = useMediaQuery('(prefers-color-scheme: dark)') ? 'dark' : 'light';
+ return useLocalStorage(THEME_KEY, darkMode);
+}
diff --git a/src/i18n.ts b/src/i18n.ts
index 6b17d6d..4a2cf2f 100644
--- a/src/i18n.ts
+++ b/src/i18n.ts
@@ -1,13 +1,12 @@
import i18n from 'i18next';
import Backend from 'i18next-http-backend';
-import { initReactI18next } from 'react-i18next';
-import { load } from 'js-yaml';
+import {initReactI18next} from 'react-i18next';
+import {load} from 'js-yaml';
i18n
.use(Backend)
.use(initReactI18next)
.init({
- fallbackLng: 'bg',
backend: {
loadPath: import.meta.env.BASE_URL + 'locales/{{lng}}.yaml',
parse: (data: any) => load(data),
diff --git a/src/layout/NavBar.tsx b/src/layout/NavBar.tsx
index 0d7f3ff..8cd97b1 100644
--- a/src/layout/NavBar.tsx
+++ b/src/layout/NavBar.tsx
@@ -1,29 +1,36 @@
-import {useEffect} from 'react';
import {NavLink, useLocation} from 'react-router-dom';
import {Container, Image, Nav, Navbar, NavDropdown} from 'react-bootstrap';
import {useTranslation} from 'react-i18next';
import './NavBar.css';
-import i18n from '../i18n.ts';
import {useVariant} from '../hooks/useVariant.ts';
import {useCurrentUser} from '../hooks/useEndpoints.ts';
+import {useTheme} from '../hooks/useTheme.ts';
import RequireRole from '../widgets/RequireRole.tsx';
import RequireVariant from "../widgets/RequireVariant.tsx";
+import {useLocale} from '../hooks/useLocale.ts';
const NavBar = () => {
const {t} = useTranslation();
+ const [locale, setLocale] = useLocale();
const backendUrl = import.meta.env.OIDC_AUTHORITY_URL;
const {
data: user,
} = useCurrentUser();
const variant = useVariant();
- useEffect(function () {
- if (user?.locale) {
- i18n.changeLanguage(user.locale).then(() => {
- });
- }
- }, [user]);
+ const [theme, setTheme] = useTheme();
+ const changeLanguage = async () =>
+ setLocale((!locale || locale == 'bg') ? 'en' : 'bg');
+
+ const changeTheme = () => setTheme(theme == 'light' ? 'dark' : 'light');
+
+// useEffect(function () {
+// if (user?.locale) {
+// i18n.changeLanguage(user.locale).then(() => {
+// });
+// }
+// }, [user]);
const location = useLocation();
@@ -71,6 +78,12 @@ const NavBar = () => {
{t('views.navigation.labbers')}
+
+