From a985abb9c7e6a58e229d44efc1eed8e89614832b Mon Sep 17 00:00:00 2001 From: Aloento <11802769+Aloento@users.noreply.github.com> Date: Mon, 3 Feb 2025 14:26:54 +0100 Subject: [PATCH 1/7] Enhances user navigation and form validation Stores the current URL before redirecting to the login page Redirects to the stored URL after user login Improves form validation by ensuring end date is provided for maintenance events --- src/Components/Auth/UserMgr.ts | 5 +++++ src/Components/New/useNewForm.ts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Components/Auth/UserMgr.ts b/src/Components/Auth/UserMgr.ts index 20358a9..7bde0f1 100644 --- a/src/Components/Auth/UserMgr.ts +++ b/src/Components/Auth/UserMgr.ts @@ -51,6 +51,8 @@ export class UserMgr extends UserManager { const state = btoa(stateObj).replace(/=+$/, ''); const loginUrl = `${process.env.SD_BACKEND_URL}/auth/login?state=${state}`; + const current = window.location.href; + await this.settings.userStore.set("current", current); window.location.href = loginUrl; } @@ -89,6 +91,9 @@ export class UserMgr extends UserManager { }); await this.storeUser(user); + this.settings.userStore.get("current").then((current) => { + window.location.href = current || "/"; + }); return user; } } diff --git a/src/Components/New/useNewForm.ts b/src/Components/New/useNewForm.ts index aafaf33..7d88c5b 100644 --- a/src/Components/New/useNewForm.ts +++ b/src/Components/New/useNewForm.ts @@ -179,7 +179,7 @@ export function useNewForm() { start_date: start.toISOString() } - if (type === EventType.Maintenance) { + if (type === EventType.Maintenance && end) { body.end_date = end } From 3c92a13fc925d8499a65db91e5b57a98bf79bef1 Mon Sep 17 00:00:00 2001 From: Aloento <11802769+Aloento@users.noreply.github.com> Date: Tue, 4 Feb 2025 15:01:47 +0100 Subject: [PATCH 2/7] Adds availability service context and data fetching Implements a context provider for service availability data Fetches and processes availability information from the backend Enhances loading state management with a spinner component --- src/Services/Availability.tsx | 135 ++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 src/Services/Availability.tsx diff --git a/src/Services/Availability.tsx b/src/Services/Availability.tsx new file mode 100644 index 0000000..d49ad07 --- /dev/null +++ b/src/Services/Availability.tsx @@ -0,0 +1,135 @@ +import { ScaleLoadingSpinner } from "@telekom/scale-components-react"; +import { useCreation, useRequest } from "ahooks"; +import { createContext, JSX, Suspense, useContext, useEffect, useState } from "react"; +import { BehaviorSubject } from "rxjs"; +import { Station } from "~/Helpers/Entities"; +import { Logger } from "~/Helpers/Logger"; +import { DB } from "./DB"; +import { useStatus } from "./Status"; +import { Models } from "./Status.Models"; + +interface AvailEntity { + year: number + month: number + percentage: number +} + +interface ServiceAvaEntity { + id: number + name: string + availability: AvailEntity[] + region: string +} + +interface IAvailability { + RS: Models.IRegionService, + Percentages: number[] +} + +interface IContext { + Availa: IAvailability[]; + Region: Models.IRegion +} + +const db = new DB(() => []); + +const CTX = createContext({} as IContext); +const key = "Availability"; + +await db.load(key); + +const log = new Logger("Service", key); + +/** + * @author Aloento + * @since 1.0.0 + * @version 0.1.0 + */ +export function useAvailability() { + const ctx = useContext(CTX); + + if (db.Ins.length < 1) { + throw new Promise((res) => { + const i = setInterval(() => { + if (db.Ins.length > 0) { + clearInterval(i); + res(ctx); + } + }, 100); + }); + } + + return ctx; +} + +/** + * @author Aloento + * @since 1.0.0 + * @version 0.1.0 + */ +export function AvailaContext({ children }: { children: JSX.Element }) { + const { DB } = useStatus(); + const [region, setRegion] = useState(DB.Regions[0]); + const [ins, setDB] = useState(db.Ins); + + const regionSub = useCreation( + () => Station.get(key, () => { + const first = DB.Regions[0]; + return new BehaviorSubject(first); + }), []); + + useEffect(() => { + const sub = regionSub.subscribe(setRegion); + return () => sub.unsubscribe(); + }, []); + + const url = process.env.SD_BACKEND_URL; + + useRequest(async () => { + const res = await fetch(`${url}/v2/availability`); + const data = (await res.json()).data as ServiceAvaEntity[]; + + const raw = [] as IAvailability[]; + + for (const service of data) { + const rs = DB.RegionService.find(x => x.Id === service.id); + + if (!rs) { + log.info("Service not found.", service); + continue; + } + + if (!service.availability || service.availability.length < 6) { + log.info(`Skipped ${key}.`, service); + continue; + } + + const ava = service.availability + .map(x => x.percentage) + .slice(0, 6) + .reverse(); + + raw.push({ + RS: rs, + Percentages: ava + }); + } + + log.debug(`${key} data processed.`, raw); + return raw; + }, { + cacheKey: key, + onSuccess: (res) => { + setDB(res); + db.save(key, res); + } + }); + + return ( + + }> + {children} + + + ); +} From 5c9417ec357ed31fa1dfa2daf9ed25e2a89c5e5c Mon Sep 17 00:00:00 2001 From: Aloento <11802769+Aloento@users.noreply.github.com> Date: Tue, 4 Feb 2025 15:02:13 +0100 Subject: [PATCH 3/7] Adds database service for object storage Implements a generic database class for managing object storage using IndexedDB. Includes methods for initializing the database, saving, and loading data. Enhances data persistence capabilities in the application. --- src/Services/DB.ts | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 src/Services/DB.ts diff --git a/src/Services/DB.ts b/src/Services/DB.ts new file mode 100644 index 0000000..a5607b0 --- /dev/null +++ b/src/Services/DB.ts @@ -0,0 +1,39 @@ +import { openDB } from "idb"; +import { Dic } from "~/Helpers/Entities"; + +/** + * @author Aloento + * @since 1.0.0 + * @version 0.1.0 + */ +export class DB { + public Ins: T; + + constructor(factory: () => T) { + this.Ins = factory(); + } + + public async init() { + return openDB(Dic.Name, 1, { + upgrade(db) { + db.createObjectStore(Dic.Name); + }, + }); + } + + public async save(key: string, data = this.Ins) { + this.Ins = data; + const db = await this.init(); + await db.put(Dic.Name, data, key); + db.close(); + } + + public async load(key: string) { + const db = await this.init(); + const res = await db.get(Dic.Name, key) as T; + if (res) { + this.Ins = res; + } + db.close(); + } +} From 0c0bea342ebde5b23bb5bbdebff89615bd86d979 Mon Sep 17 00:00:00 2001 From: Aloento <11802769+Aloento@users.noreply.github.com> Date: Tue, 4 Feb 2025 15:04:28 +0100 Subject: [PATCH 4/7] Refactors status management to use new DB instance Replaces global DB variable with an instance of the DB class for better encapsulation and state management. Removes redundant IndexedDB functions and simplifies data loading and saving processes. Updates context provider to reflect changes in state handling. Improves code readability and maintainability. --- src/Services/Status.tsx | 113 ++++++---------------------------------- 1 file changed, 15 insertions(+), 98 deletions(-) diff --git a/src/Services/Status.tsx b/src/Services/Status.tsx index b1fba4b..fea49fe 100644 --- a/src/Services/Status.tsx +++ b/src/Services/Status.tsx @@ -1,8 +1,7 @@ import { useRequest } from "ahooks"; -import { openDB } from "idb"; -import { createContext, useContext, useState } from "react"; -import { Dic } from "~/Helpers/Entities"; +import { createContext, JSX, useContext, useState } from "react"; import { Logger } from "~/Helpers/Logger"; +import { DB } from "./DB"; import { IncidentEntityV2, StatusEntityV2 } from "./Status.Entities"; import { IStatusContext } from "./Status.Models"; import { TransformerV2 } from "./Status.Trans.V2"; @@ -36,18 +35,7 @@ export function EmptyDB(): IStatusContext { } } -/** - * A global variable representing the current state of the status database. - * - * @remarks - * This variable is initialized with the structure provided by the `EmptyDB` function. - * It is used throughout the application to access and update the status data. - * - * @author Aloento - * @since 1.0.0 - * @version 0.1.0 - */ -export let DB = EmptyDB(); +const db = new DB(EmptyDB); interface IContext { DB: IStatusContext; @@ -55,82 +43,11 @@ interface IContext { } const CTX = createContext({} as IContext); -const Store = "Status"; - -/** - * Initializes the IndexedDB database for storing status information. - * - * @returns A promise that resolves to the initialized database instance. - * - * @remarks - * This function sets up the IndexedDB database with the necessary object stores. - * It is called internally to ensure the database is ready for use. - * - * @since 1.0.0 - * @version 0.1.0 - */ -function init() { - return openDB(Dic.Name, 1, { - upgrade(db) { - db.createObjectStore(Store); - }, - }); -} - -/** - * Saves the current state of the status database to IndexedDB. - * - * @returns A promise that resolves when the save operation is complete. - * - * @remarks - * This function writes the current state of the `DB` variable to the IndexedDB database. - * It is called whenever the status data is updated to persist the changes. - * - * @since 1.0.0 - * @version 0.1.0 - */ -async function save() { - const db = await init(); - await db.put(Store, DB, Store); - db.close(); -} +const key = "Status"; -/** - * Loads the status database from IndexedDB. - * - * @returns A promise that resolves when the load operation is complete. - * - * @remarks - * This function reads the status data from the IndexedDB database and updates the `DB` variable. - * It is called during the initialization of the application to restore the previous state. - * - * @since 1.0.0 - * @version 0.1sv.0 - */ -async function load() { - const db = await init(); - const res = await db.get(Store, Store) as IStatusContext; - if (res) { - DB = res; - } - db.close(); -} - -/** - * Loads the status database from IndexedDB. - * - * @returns {Promise} A promise that resolves when the load operation is complete. - * - * @remarks - * This function reads the status data from the IndexedDB database and updates the `DB` variable. - * It is called during the initialization of the application to restore the previous state. - * - * @since 1.0.0 - * @version 0.1sv.0 - */ -await load(); +await db.load(key); -const log = new Logger("Service", "Status"); +const log = new Logger("Service", key); /** * Custom hook to access the status context. @@ -149,10 +66,10 @@ const log = new Logger("Service", "Status"); export function useStatus() { const ctx = useContext(CTX); - if (DB.Regions.length < 1) { + if (db.Ins.Regions.length < 1) { throw new Promise((res) => { const i = setInterval(() => { - if (DB.Regions.length > 0) { + if (db.Ins.Regions.length > 0) { clearInterval(i); res(ctx); } @@ -180,7 +97,7 @@ export function useStatus() { * @version 0.1.0 */ export function StatusContext({ children }: { children: JSX.Element }) { - const [db, setDB] = useState(DB); + const [ins, setDB] = useState(db.Ins); const url = process.env.SD_BACKEND_URL; @@ -206,18 +123,18 @@ export function StatusContext({ children }: { children: JSX.Element }) { }; }, { - cacheKey: log.namespace, + cacheKey: key, onSuccess: (res) => update(TransformerV2(res)), } ); - function update(data: IStatusContext = DB) { - DB = { ...data }; - setDB(DB); - save(); + function update(data: IStatusContext = ins) { + const raw = { ...data }; + setDB(raw); + db.save(key, raw); } return ( - {children} + {children} ); } From 6073742a0055fa10fa997cd49f2196299561b0e7 Mon Sep 17 00:00:00 2001 From: Aloento <11802769+Aloento@users.noreply.github.com> Date: Tue, 4 Feb 2025 15:07:21 +0100 Subject: [PATCH 5/7] Adds context provider for availability matrix Wraps the availability matrix component with a context provider to manage state more effectively. Removes example code comments from the status service for clarity. --- src/Pages/Availability.tsx | 5 ++++- src/Services/Status.tsx | 6 ------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Pages/Availability.tsx b/src/Pages/Availability.tsx index 70d62cf..1d3605e 100644 --- a/src/Pages/Availability.tsx +++ b/src/Pages/Availability.tsx @@ -1,6 +1,7 @@ import { Helmet } from "react-helmet"; import { AvailaMatrix } from "~/Components/Availability/AvailaMatrix"; import { RegionSelector } from "~/Components/Home/RegionSelector"; +import { AvailaContext } from "~/Services/Availability"; /** * @author Aloento @@ -18,6 +19,8 @@ export function Availability() { Topic="Availability" /> - + + + ; } diff --git a/src/Services/Status.tsx b/src/Services/Status.tsx index fea49fe..d335ecc 100644 --- a/src/Services/Status.tsx +++ b/src/Services/Status.tsx @@ -15,12 +15,6 @@ import { TransformerV2 } from "./Status.Trans.V2"; * This function sets up the initial structure for the status database context. * It is used to ensure that the database has a consistent structure before any data is loaded. * - * @example - * ```typescript - * const initialContext = EmptyDB(); - * console.log(initialContext.Services); // Outputs: [] - * ``` - * * @author Aloento * @since 1.0.0 * @version 0.1.0 From 5b85d97c6fdbd9ebadc72395b31cb6d6ce84f0c5 Mon Sep 17 00:00:00 2001 From: Aloento <11802769+Aloento@users.noreply.github.com> Date: Tue, 4 Feb 2025 15:07:48 +0100 Subject: [PATCH 6/7] Refactors availability handling in CategoryGroup component Removes the custom hook for availability and integrates its logic directly into the CategoryGroup component. Utilizes useMemo for optimized filtering of availability data based on the selected category and region. Improves code clarity and reduces complexity by eliminating unnecessary dependencies. --- src/Components/Availability/CategoryGroup.tsx | 20 ++-- .../Availability/useAvailability.ts | 112 ------------------ 2 files changed, 12 insertions(+), 120 deletions(-) delete mode 100644 src/Components/Availability/useAvailability.ts diff --git a/src/Components/Availability/CategoryGroup.tsx b/src/Components/Availability/CategoryGroup.tsx index 51e6b07..d157444 100644 --- a/src/Components/Availability/CategoryGroup.tsx +++ b/src/Components/Availability/CategoryGroup.tsx @@ -1,18 +1,22 @@ +import { useMemo } from "react"; +import { useAvailability } from "~/Services/Availability"; import { Models } from "~/Services/Status.Models"; -import { useAvailability } from "./useAvailability"; - -interface ICategoryGroup { - Category: Models.ICategory; - Topic: string; -} /** * @author Aloento * @since 1.0.0 * @version 0.1.0 */ -export function CategoryGroup({ Category, Topic }: ICategoryGroup) { - const avas = useAvailability(Category, Topic); +export function CategoryGroup({ Category }: { Category: Models.ICategory }) { + const { Availa, Region } = useAvailability(); + + const avas = useMemo(() => { + const res = Availa + .filter(x => x.RS.Region.Id === Region.Id) + .filter(x => x.RS.Service.Category.Id === Category.Id); + + return res; + }, [Availa, Region]); function getColor(val: number): string { const color = val >= 99.95 diff --git a/src/Components/Availability/useAvailability.ts b/src/Components/Availability/useAvailability.ts deleted file mode 100644 index 8fbd5cd..0000000 --- a/src/Components/Availability/useAvailability.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { useCreation, useRequest } from "ahooks"; -import { useEffect, useState } from "react"; -import { BehaviorSubject } from "rxjs"; -import { Station } from "~/Helpers/Entities"; -import { Logger } from "~/Helpers/Logger"; -import { useStatus } from "~/Services/Status"; -import { Models } from "~/Services/Status.Models"; - -const log = new Logger("useAvailability"); - -interface ServiceAvaEntity { - id: number - name: string - availability: AvailEntity[] - region: string -} - -interface AvailEntity { - year: number - month: number - percentage: number -} - -interface IAvailability { - RS: Models.IRegionService, - Percentages: number[] -} - -/** - * Custom hook to manage availability data based on the provided category and topic. - * This hook interacts with the status service and fetches availability data from a backend service. - * It processes and filters the data to return availability information for a specific region and category. - * - * @param category - The category of the service for which availability data is required. - * @param topic - The topic used to subscribe to region updates. - * @returns An array of availability information objects. - * - * @remarks - * This hook uses various state and effect hooks to manage and update the availability data. - * It also utilizes a custom logger for debugging purposes. - * The data fetching is performed using a request hook with caching enabled. - * - * @author Aloento - * @since 1.0.0 - * @version 0.1.0 - */ -export function useAvailability(category: Models.ICategory, topic: string) { - const { DB } = useStatus(); - - const [region, setRegion] = useState(DB.Regions[0]); - const regionSub = useCreation( - () => Station.get>(topic), []); - - useEffect(() => { - const sub = regionSub.subscribe(setRegion); - return () => sub.unsubscribe(); - }, []); - - const [avas, setAvas] = useState( - () => DB.RegionService - .filter(x => x.Region.Id === region.Id) - .filter(x => x.Service.Category.Id === category.Id) - .map(x => ({ - RS: x, - Percentages: Array(6).fill(100) - }))); - - const url = process.env.SD_BACKEND_URL; - - const { data } = useRequest(async () => { - const res = await fetch(`${url}/availability`); - const data = (await res.json()).data as ServiceAvaEntity[]; - - const raw = [] as IAvailability[]; - - for (const service of data) { - const rs = DB.RegionService.find(x => x.Id === service.id); - - if (!rs) { - log.info("Service not found.", service); - continue; - } - - const ava = service.availability - .map(x => x.percentage) - .slice(0, 6) - .reverse(); - - raw.push({ - RS: rs, - Percentages: ava - }); - } - - log.debug("Availability data processed.", raw); - return raw; - }, { - cacheKey: log.namespace - }); - - useEffect(() => { - if (data) { - const res = data - .filter(x => x.RS.Region.Id === region.Id) - .filter(x => x.RS.Service.Category.Id === category.Id); - - setAvas(res); - } - }, [data, category, region]); - - return avas; -} From 12d935b5b1cc9f46d4fcfe3e21028150edc8dacf Mon Sep 17 00:00:00 2001 From: Aloento <11802769+Aloento@users.noreply.github.com> Date: Tue, 4 Feb 2025 15:08:03 +0100 Subject: [PATCH 7/7] Refactors availability matrix to use new service Replaces the use of status service with availability service to streamline region management. Updates the rendering logic to utilize the new region data structure, improving code clarity and maintainability. --- src/Components/Availability/AvailaMatrix.tsx | 25 ++++---------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/src/Components/Availability/AvailaMatrix.tsx b/src/Components/Availability/AvailaMatrix.tsx index e468804..7fb5e64 100644 --- a/src/Components/Availability/AvailaMatrix.tsx +++ b/src/Components/Availability/AvailaMatrix.tsx @@ -1,11 +1,7 @@ import { ScaleTable } from "@telekom/scale-components-react"; -import { useCreation } from "ahooks"; import dayjs from "dayjs"; import { chain } from "lodash"; -import { useEffect, useState } from "react"; -import { BehaviorSubject } from "rxjs"; -import { Station } from "~/Helpers/Entities"; -import { useStatus } from "~/Services/Status"; +import { useAvailability } from "~/Services/Availability"; import { CategoryGroup } from "./CategoryGroup"; /** @@ -14,20 +10,7 @@ import { CategoryGroup } from "./CategoryGroup"; * @version 0.1.0 */ export function AvailaMatrix() { - const { DB } = useStatus(); - const [region, setRegion] = useState(DB.Regions[0]); - - const topic = "Availability"; - const regionSub = useCreation( - () => Station.get(topic, () => { - const first = DB.Regions[0]; - return new BehaviorSubject(first); - }), []); - - useEffect(() => { - const sub = regionSub.subscribe(setRegion); - return () => sub.unsubscribe(); - }, []); + const { Region } = useAvailability(); return ( @@ -63,11 +46,11 @@ export function AvailaMatrix() { - {chain(Array.from(region.Services)) + {chain(Array.from(Region.Services)) .map(x => x.Category) .uniqBy(x => x.Id) .orderBy(x => x.Name) - .map((x, i) => ) + .map((x, i) => ) .value()}