diff --git a/webapp/README.md b/webapp/README.md index 254a5f0..6cbd57d 100644 --- a/webapp/README.md +++ b/webapp/README.md @@ -8,11 +8,13 @@ Plus d'infos ici : [https://feature-sliced.github.io/documentation/docs/get-star - Avoir Node.js installé sur votre machine [voir comment faire](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm#using-a-node-version-manager-to-install-nodejs-and-npm) ## Installer les packages + ``` npm ci ``` ## Démarrer le serveur + ``` npm run dev ``` @@ -22,4 +24,4 @@ npm run dev - `npm run build` : package l'application pour la mise en production (dossier [dist](./dist)) - `npm run preview` : Démarre le serveur à partir du build pour la production (dans le dist) - `npm run check:arch` : Vérifier si les imports du projet respectent le principe de l'architecture FSD. -- `npm run arch:tree` : Génère un fichier text contenant l'arborescence du projet. \ No newline at end of file +- `npm run arch:tree` : Génère un fichier text contenant l'arborescence du projet. diff --git a/webapp/components.json b/webapp/components.json index 711c837..793a405 100644 --- a/webapp/components.json +++ b/webapp/components.json @@ -22,4 +22,4 @@ "hooks": "@/hooks" }, "registries": {} -} \ No newline at end of file +} diff --git a/webapp/eslint.config.ts b/webapp/eslint.config.ts index 93b060a..5479510 100644 --- a/webapp/eslint.config.ts +++ b/webapp/eslint.config.ts @@ -1,13 +1,19 @@ -import js from "@eslint/js"; +import pluginReact from "eslint-plugin-react"; import globals from "globals"; import tseslint from "typescript-eslint"; -import pluginReact from "eslint-plugin-react"; + +import js from "@eslint/js"; import json from "@eslint/json"; -import { defineConfig } from "eslint/config"; import pluginPrettier from "eslint-config-prettier/flat"; +import { defineConfig } from "eslint/config"; export default defineConfig([ - { files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"], plugins: { js }, extends: ["js/recommended"], languageOptions: { globals: globals.browser } }, + { + files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"], + plugins: { js }, + extends: ["js/recommended"], + languageOptions: { globals: globals.browser }, + }, tseslint.configs.recommended, { ...pluginReact.configs.flat.recommended, @@ -16,17 +22,27 @@ export default defineConfig([ * Disable react/display-name since it's currently broken * TypeError: Error while loading rule 'react/display-name': sourceCode.getAllComments is not a function */ - "react/display-name": ["off"] + "react/display-name": ["off"], }, settings: { react: { - version: "detect" - } + version: "detect", + }, }, }, - /** @ts-expect-error json plugin not typed well*/ - { files: ["**/*.json"], plugins: { json }, language: "json/json", extends: ["json/recommended"] }, - /** @ts-expect-error json plugin not typed well*/ - { files: ["**/*.jsonc"], plugins: { json }, language: "json/jsonc", extends: ["json/recommended"] }, + { + files: ["**/*.json"], + /** @ts-expect-error json plugin not typed well*/ + plugins: { json }, + language: "json/json", + extends: ["json/recommended"], + }, + { + files: ["**/*.jsonc"], + /** @ts-expect-error json plugin not typed well*/ + plugins: { json }, + language: "json/jsonc", + extends: ["json/recommended"], + }, pluginPrettier, ]); diff --git a/webapp/index.html b/webapp/index.html index 072a57e..9174257 100644 --- a/webapp/index.html +++ b/webapp/index.html @@ -2,12 +2,22 @@ - - + + frontend
- + diff --git a/webapp/scripts/check-architecture.cjs b/webapp/scripts/check-architecture.cjs index 935ac05..c337398 100644 --- a/webapp/scripts/check-architecture.cjs +++ b/webapp/scripts/check-architecture.cjs @@ -3,21 +3,21 @@ * Vérifie que les imports respectent les règles de l'architecture */ -const fs = require('fs'); -const path = require('path'); +const fs = require("fs"); +const path = require("path"); -const LAYERS = ['app', 'pages', 'widgets', 'features', 'entities', 'shared']; +const LAYERS = ["app", "pages", "widgets", "features", "entities", "shared"]; const LAYER_HIERARCHY = { - 'shared': [], - 'entities': ['shared'], - 'features': ['entities', 'shared'], - 'widgets': ['features', 'entities', 'shared'], - 'pages': ['widgets', 'features', 'entities', 'shared'], - 'app': ['pages', 'widgets', 'features', 'entities', 'shared'] + shared: [], + entities: ["shared"], + features: ["entities", "shared"], + widgets: ["features", "entities", "shared"], + pages: ["widgets", "features", "entities", "shared"], + app: ["pages", "widgets", "features", "entities", "shared"], }; function getLayerFromPath(filePath) { - const relativePath = path.relative(path.join(__dirname, 'src'), filePath); + const relativePath = path.relative(path.join(__dirname, "src"), filePath); const firstDir = relativePath.split(path.sep)[0]; return LAYERS.includes(firstDir) ? firstDir : null; } @@ -33,12 +33,15 @@ function checkImports(filePath, fileContent) { while ((match = importRegex.exec(fileContent)) !== null) { const importedLayer = match[1]; - if (LAYERS.includes(importedLayer) && !LAYER_HIERARCHY[layer].includes(importedLayer)) { + if ( + LAYERS.includes(importedLayer) && + !LAYER_HIERARCHY[layer].includes(importedLayer) + ) { violations.push({ file: filePath, layer, importedLayer, - line: fileContent.substring(0, match.index).split('\n').length + line: fileContent.substring(0, match.index).split("\n").length, }); } } @@ -54,10 +57,17 @@ function scanDirectory(dir) { const filePath = path.join(dir, file); const stat = fs.statSync(filePath); - if (stat.isDirectory() && !file.startsWith('.') && file !== 'node_modules') { + if ( + stat.isDirectory() && + !file.startsWith(".") && + file !== "node_modules" + ) { violations.push(...scanDirectory(filePath)); - } else if (stat.isFile() && (file.endsWith('.ts') || file.endsWith('.tsx'))) { - const content = fs.readFileSync(filePath, 'utf8'); + } else if ( + stat.isFile() && + (file.endsWith(".ts") || file.endsWith(".tsx")) + ) { + const content = fs.readFileSync(filePath, "utf8"); violations.push(...checkImports(filePath, content)); } } @@ -66,21 +76,27 @@ function scanDirectory(dir) { } // Exécution -console.log('🔍 Vérification de l\'architecture FSD...\n'); +console.log("🔍 Vérification de l'architecture FSD...\n"); -const srcDir = path.join(__dirname, '../src'); +const srcDir = path.join(__dirname, "../src"); const violations = scanDirectory(srcDir); if (violations.length === 0) { - console.log('✅ Aucune violation détectée ! L\'architecture FSD est respectée.'); + console.log( + "✅ Aucune violation détectée ! L'architecture FSD est respectée.", + ); } else { console.log(`❌ ${violations.length} violation(s) détectée(s):\n`); violations.forEach(({ file, layer, importedLayer, line }) => { const relativeFile = path.relative(__dirname, file); console.log(` ${relativeFile}:${line}`); - console.log(` ↳ La couche "${layer}" ne peut pas importer depuis "${importedLayer}"`); - console.log(` ↳ Imports autorisés: ${LAYER_HIERARCHY[layer].join(', ') || 'aucun'}\n`); + console.log( + ` ↳ La couche "${layer}" ne peut pas importer depuis "${importedLayer}"`, + ); + console.log( + ` ↳ Imports autorisés: ${LAYER_HIERARCHY[layer].join(", ") || "aucun"}\n`, + ); }); process.exit(1); diff --git a/webapp/scripts/generate-tree.cjs b/webapp/scripts/generate-tree.cjs index f99f96c..ba63b29 100644 --- a/webapp/scripts/generate-tree.cjs +++ b/webapp/scripts/generate-tree.cjs @@ -6,16 +6,10 @@ * @example node scripts/generate-tree.cjs ./frontend/src docs/frontend-archi.md */ -const fs = require('fs'); -const path = require('path'); - -const IGNORE_LIST = [ - 'node_modules', - '.git', - 'dist', - 'coverage', - '.vscode', -]; +const fs = require("fs"); +const path = require("path"); + +const IGNORE_LIST = ["node_modules", ".git", "dist", "coverage", ".vscode"]; // Caractères Unicode (maintenant commentés) // const PREFIX_BRANCH = '├── '; @@ -24,11 +18,10 @@ const IGNORE_LIST = [ // const PREFIX_EMPTY = ' '; // Alternative avec des caractères ASCII-safe pour une compatibilité maximale -const PREFIX_BRANCH = '|-- '; -const PREFIX_LAST_BRANCH = '`-- '; // Utilise un accent grave pour simuler la branche finale -const PREFIX_CHILD = '| '; -const PREFIX_EMPTY = ' '; - +const PREFIX_BRANCH = "|-- "; +const PREFIX_LAST_BRANCH = "`-- "; // Utilise un accent grave pour simuler la branche finale +const PREFIX_CHILD = "| "; +const PREFIX_EMPTY = " "; /** * Fonction récursive qui génère l'arborescence d'un dossier. @@ -36,24 +29,25 @@ const PREFIX_EMPTY = ' '; * @param {string} prefix - Le préfixe de ligne pour l'indentation. * @returns {string[]} Un tableau de chaînes, chaque chaîne étant une ligne de l'arbre. */ -function generateTree(directory, prefix = '') { - const lines = []; - const files = fs.readdirSync(directory) - .filter(file => !IGNORE_LIST.includes(file)); - - files.forEach((file, index) => { - const filePath = path.join(directory, file); - const isLast = index === files.length - 1; - const isDirectory = fs.statSync(filePath).isDirectory(); - - lines.push(prefix + (isLast ? PREFIX_LAST_BRANCH : PREFIX_BRANCH) + file); - - if (isDirectory) { - const childPrefix = prefix + (isLast ? PREFIX_EMPTY : PREFIX_CHILD); - lines.push(...generateTree(filePath, childPrefix)); - } - }); - return lines; +function generateTree(directory, prefix = "") { + const lines = []; + const files = fs + .readdirSync(directory) + .filter((file) => !IGNORE_LIST.includes(file)); + + files.forEach((file, index) => { + const filePath = path.join(directory, file); + const isLast = index === files.length - 1; + const isDirectory = fs.statSync(filePath).isDirectory(); + + lines.push(prefix + (isLast ? PREFIX_LAST_BRANCH : PREFIX_BRANCH) + file); + + if (isDirectory) { + const childPrefix = prefix + (isLast ? PREFIX_EMPTY : PREFIX_CHILD); + lines.push(...generateTree(filePath, childPrefix)); + } + }); + return lines; } // --- Point d'entrée du script --- @@ -62,35 +56,45 @@ const targetDir = process.argv[2]; const outputFile = process.argv[3]; if (!targetDir) { - console.error('❌ Erreur : Veuillez spécifier le chemin du dossier à analyser.'); - console.error('Usage: node scripts/generate-tree.cjs [fichier_sortie]'); - process.exit(1); + console.error( + "❌ Erreur : Veuillez spécifier le chemin du dossier à analyser.", + ); + console.error( + "Usage: node scripts/generate-tree.cjs [fichier_sortie]", + ); + process.exit(1); } if (!fs.existsSync(targetDir) || !fs.statSync(targetDir).isDirectory()) { - console.error(`❌ Erreur : Le dossier "${targetDir}" n'est pas un dossier valide.`); - process.exit(1); + console.error( + `❌ Erreur : Le dossier "${targetDir}" n'est pas un dossier valide.`, + ); + process.exit(1); } const treeLines = [ - `\`\`\`text`, - path.basename(targetDir), - ...generateTree(targetDir), - `\`\`\`` + `\`\`\`text`, + path.basename(targetDir), + ...generateTree(targetDir), + `\`\`\``, ]; -const outputString = treeLines.join('\n'); +const outputString = treeLines.join("\n"); -console.log('🌳 Arborescence générée :\n'); +console.log("🌳 Arborescence générée :\n"); console.log(outputString); if (outputFile) { - try { - // Le BOM n'est plus nécessaire avec des caractères ASCII purs. - fs.writeFileSync(outputFile, outputString, 'utf8'); - console.log(`\n✅ Arborescence sauvegardée avec succès dans : ${outputFile}`); - } catch (error) { - console.error(`\n❌ Erreur lors de l'écriture du fichier : ${error.message}`); - process.exit(1); - } -} \ No newline at end of file + try { + // Le BOM n'est plus nécessaire avec des caractères ASCII purs. + fs.writeFileSync(outputFile, outputString, "utf8"); + console.log( + `\n✅ Arborescence sauvegardée avec succès dans : ${outputFile}`, + ); + } catch (error) { + console.error( + `\n❌ Erreur lors de l'écriture du fichier : ${error.message}`, + ); + process.exit(1); + } +} diff --git a/webapp/src/app/App.tsx b/webapp/src/app/App.tsx index ad90399..1c0541e 100644 --- a/webapp/src/app/App.tsx +++ b/webapp/src/app/App.tsx @@ -1,12 +1,15 @@ -import { ThemeProvider, ApiProvider, AuthProvider } from "@providers"; -import { AppRouter } from "./routes/AppRouter"; +import { ApiProvider, AuthProvider, ThemeProvider } from "@providers"; +import { AppRouter } from "./routes/AppRouter"; import "./styles/all4trees.css"; import "./styles/globals.css"; function App() { return ( - + @@ -16,4 +19,4 @@ function App() { ); } -export default App; \ No newline at end of file +export default App; diff --git a/webapp/src/app/api/client.ts b/webapp/src/app/api/client.ts index 1b94b27..b735bee 100644 --- a/webapp/src/app/api/client.ts +++ b/webapp/src/app/api/client.ts @@ -1,4 +1,4 @@ -const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000/graphql'; +const API_URL = import.meta.env.VITE_API_URL || "http://localhost:8000/graphql"; export const fetchJsonFixture = async (name: string) => { const response = await fetch(`/src/fixtures/${name}.json`); @@ -7,8 +7,8 @@ export const fetchJsonFixture = async (name: string) => { export const fetchGraphQLData = async () => { const res = await fetch(API_URL, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, + method: "POST", + headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query: ` query { @@ -22,20 +22,20 @@ export const fetchGraphQLData = async () => { month } } -}` +}`, }), }); const data = await res.json(); return data; -} +}; export const fetchWithAuth = async ( endpoint: string, options: RequestInit = {}, - authToken: string | null + authToken: string | null, ) => { const headers = new Headers(options.headers); - headers.set('Authorization', authToken ? `Bearer ${authToken}` : ''); + headers.set("Authorization", authToken ? `Bearer ${authToken}` : ""); const res = await fetch(`${API_URL}${endpoint}`, { ...options, @@ -45,10 +45,10 @@ export const fetchWithAuth = async ( if (!res.ok) { console.error(`Erreur API: ${res.status} ${res.statusText}`); const errorData = await res.json().catch(() => ({ - error: 'Erreur de communication', + error: "Erreur de communication", details: [res.statusText], })); - console.error('Détails de l\'erreur:', JSON.stringify(errorData, null, 2)); + console.error("Détails de l'erreur:", JSON.stringify(errorData, null, 2)); const error = new Error(`Erreur API: ${res.status}`); @@ -64,13 +64,13 @@ export const fetchWithAuth = async ( export const fetchJSONWithAuth = async ( endpoint: string, options: RequestInit = {}, - authToken: string | null + authToken: string | null, ) => (await fetchWithAuth(endpoint, options, authToken)).json(); export const createApiClient = () => ({ getData() { - return fetchJsonFixture('exampleData'); - } + return fetchJsonFixture("exampleData"); + }, }); export type ApiClient = ReturnType; diff --git a/webapp/src/app/providers/ApiProvider.tsx b/webapp/src/app/providers/ApiProvider.tsx index 422de7a..c0b7ed6 100644 --- a/webapp/src/app/providers/ApiProvider.tsx +++ b/webapp/src/app/providers/ApiProvider.tsx @@ -1,26 +1,25 @@ -import { createContext, type ReactNode, useContext, useMemo } from 'react'; -import { createApiClient } from '../api/client'; +import { type ReactNode, createContext, useContext, useMemo } from "react"; + +import { createApiClient } from "../api/client"; interface ApiProviderProps { - children: ReactNode; + children: ReactNode; } -export const ApiContext = createContext | undefined>(undefined); +export const ApiContext = createContext< + ReturnType | undefined +>(undefined); export function useApi() { - const context = useContext(ApiContext); - if (context === undefined) { - throw new Error('useApi must be used within an ApiProvider'); - } - return context; + const context = useContext(ApiContext); + if (context === undefined) { + throw new Error("useApi must be used within an ApiProvider"); + } + return context; } export function ApiProvider({ children }: ApiProviderProps) { - const api = useMemo(() => createApiClient(), []); + const api = useMemo(() => createApiClient(), []); - return ( - - {children} - - ); -} \ No newline at end of file + return {children}; +} diff --git a/webapp/src/app/providers/AuthProvider.tsx b/webapp/src/app/providers/AuthProvider.tsx index e01d29f..2c97a91 100644 --- a/webapp/src/app/providers/AuthProvider.tsx +++ b/webapp/src/app/providers/AuthProvider.tsx @@ -1,76 +1,84 @@ -import { useState, useEffect, createContext, type ReactNode, useContext } from 'react'; +import { + type ReactNode, + createContext, + useContext, + useEffect, + useState, +} from "react"; -const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:3000/api'; +const API_URL = import.meta.env.VITE_API_URL || "http://localhost:3000/api"; export interface AuthContextType { - isAuthenticated: boolean; - token: string | null; - login: (token: string) => void; - logout: () => void; + isAuthenticated: boolean; + token: string | null; + login: (token: string) => void; + logout: () => void; } -export const AuthContext = createContext(undefined); +export const AuthContext = createContext( + undefined, +); export const useAuth = () => { - const context = useContext(AuthContext); - if (!context) { - throw new Error('useAuth must be used within AuthProvider'); - } - return context; + const context = useContext(AuthContext); + if (!context) { + throw new Error("useAuth must be used within AuthProvider"); + } + return context; }; interface AuthProviderProps { - children: ReactNode; + children: ReactNode; } export function AuthProvider({ children }: AuthProviderProps) { - const [isAuthenticated, setIsAuthenticated] = useState(false); - const [token, setToken] = useState(null); + const [isAuthenticated, setIsAuthenticated] = useState(false); + const [token, setToken] = useState(null); - useEffect(() => { - const storedToken = localStorage.getItem('token'); + useEffect(() => { + const storedToken = localStorage.getItem("token"); - if (storedToken) { - // Vérifier si le token est toujours valide - fetch(`${API_URL}/auth/verify`, { - headers: { - 'Authorization': `Bearer ${storedToken}` - } - }) - .then(res => res.json()) - .then(data => { - if (data.valid) { - setToken(storedToken); - setIsAuthenticated(true); - } else { - // Token invalide, nettoyer le localStorage - localStorage.removeItem('token'); - localStorage.removeItem('user'); - } - }) - .catch(() => { - localStorage.removeItem('token'); - localStorage.removeItem('user'); - }); - } - }, []); + if (storedToken) { + // Vérifier si le token est toujours valide + fetch(`${API_URL}/auth/verify`, { + headers: { + Authorization: `Bearer ${storedToken}`, + }, + }) + .then((res) => res.json()) + .then((data) => { + if (data.valid) { + setToken(storedToken); + setIsAuthenticated(true); + } else { + // Token invalide, nettoyer le localStorage + localStorage.removeItem("token"); + localStorage.removeItem("user"); + } + }) + .catch(() => { + localStorage.removeItem("token"); + localStorage.removeItem("user"); + }); + } + }, []); - const login = (newToken: string) => { - setToken(newToken); - setIsAuthenticated(true); - localStorage.setItem('token', newToken); - }; + const login = (newToken: string) => { + setToken(newToken); + setIsAuthenticated(true); + localStorage.setItem("token", newToken); + }; - const logout = () => { - localStorage.removeItem('token'); - localStorage.removeItem('user'); - setToken(null); - setIsAuthenticated(false); - }; + const logout = () => { + localStorage.removeItem("token"); + localStorage.removeItem("user"); + setToken(null); + setIsAuthenticated(false); + }; - return ( - - {children} - - ); + return ( + + {children} + + ); } diff --git a/webapp/src/app/providers/ThemeProvider.tsx b/webapp/src/app/providers/ThemeProvider.tsx index 473f79e..db9f716 100644 --- a/webapp/src/app/providers/ThemeProvider.tsx +++ b/webapp/src/app/providers/ThemeProvider.tsx @@ -1,73 +1,76 @@ -import { createContext, useContext, useEffect, useState } from "react" +import { createContext, useContext, useEffect, useState } from "react"; -type Theme = "dark" | "light" | "system" +type Theme = "dark" | "light" | "system"; type ThemeProviderProps = { - children: React.ReactNode - defaultTheme?: Theme - storageKey?: string -} + children: React.ReactNode; + defaultTheme?: Theme; + storageKey?: string; +}; type ThemeProviderState = { - theme: Theme - setTheme: (theme: Theme) => void -} + theme: Theme; + setTheme: (theme: Theme) => void; +}; const initialState: ThemeProviderState = { - theme: "system", - setTheme: () => null, -} + theme: "system", + setTheme: () => null, +}; -const ThemeProviderContext = createContext(initialState) +const ThemeProviderContext = createContext(initialState); export function ThemeProvider({ - children, - defaultTheme = "system", - storageKey = "vite-ui-theme", - ...props + children, + defaultTheme = "system", + storageKey = "vite-ui-theme", + ...props }: ThemeProviderProps) { - const [theme, setTheme] = useState( - () => (localStorage.getItem(storageKey) as Theme) || defaultTheme - ) - - useEffect(() => { - const root = window.document.documentElement - - root.classList.remove("light", "dark") - - if (theme === "system") { - const systemTheme = window.matchMedia("(prefers-color-scheme: dark)") - .matches - ? "dark" - : "light" - - root.classList.add(systemTheme) - return - } - - root.classList.add(theme) - }, [theme]) - - const value = { - theme, - setTheme: (theme: Theme) => { - localStorage.setItem(storageKey, theme) - setTheme(theme) - }, + const [theme, setTheme] = useState( + () => (localStorage.getItem(storageKey) as Theme) || defaultTheme, + ); + + useEffect(() => { + const root = window.document.documentElement; + + root.classList.remove("light", "dark"); + + if (theme === "system") { + const systemTheme = window.matchMedia("(prefers-color-scheme: dark)") + .matches + ? "dark" + : "light"; + + root.classList.add(systemTheme); + return; } - return ( - - {children} - - ) + root.classList.add(theme); + }, [theme]); + + const value = { + theme, + setTheme: (theme: Theme) => { + localStorage.setItem(storageKey, theme); + setTheme(theme); + }, + }; + + return ( + + {children} + + ); } export const useTheme = () => { - const context = useContext(ThemeProviderContext) + const context = useContext(ThemeProviderContext); - if (context === undefined) - throw new Error("useTheme must be used within a ThemeProvider") + if (context === undefined) + throw new Error("useTheme must be used within a ThemeProvider"); - return context -} \ No newline at end of file + return context; +}; diff --git a/webapp/src/app/providers/index.ts b/webapp/src/app/providers/index.ts index ba3560e..38beedb 100644 --- a/webapp/src/app/providers/index.ts +++ b/webapp/src/app/providers/index.ts @@ -1,3 +1,3 @@ export { ThemeProvider } from "./ThemeProvider"; export { ApiProvider, useApi } from "./ApiProvider"; -export { AuthProvider, useAuth } from "./AuthProvider"; \ No newline at end of file +export { AuthProvider, useAuth } from "./AuthProvider"; diff --git a/webapp/src/app/routes/AppRouter.tsx b/webapp/src/app/routes/AppRouter.tsx index 535a905..4567fbb 100644 --- a/webapp/src/app/routes/AppRouter.tsx +++ b/webapp/src/app/routes/AppRouter.tsx @@ -1,5 +1,5 @@ -import { MainPage } from '@pages'; +import { MainPage } from "@pages"; export function AppRouter() { - return ; + return ; } diff --git a/webapp/src/app/styles/all4trees.css b/webapp/src/app/styles/all4trees.css index c59a44f..ec6ddf5 100644 --- a/webapp/src/app/styles/all4trees.css +++ b/webapp/src/app/styles/all4trees.css @@ -6,27 +6,31 @@ ========================================================================== */ :root { - /* ======================================================================== + /* ======================================================================== COULEURS PRINCIPALES - CHARTE ALL4TREES ======================================================================== */ - --color-nuit: #0f0f0f; - --color-onyx: #424242; - --color-brunswick-green: #224d41; - --color-vert-kelly: #54b025; - --color-jaune-vert: #99cf16; - --color-citrouille: #f98038; - --color-alabaster: #f5f4eb; - --color-bleu: #2d6db4; - --color-vert-de-gris: #3fafb6; + --color-nuit: #0f0f0f; + --color-onyx: #424242; + --color-brunswick-green: #224d41; + --color-vert-kelly: #54b025; + --color-jaune-vert: #99cf16; + --color-citrouille: #f98038; + --color-alabaster: #f5f4eb; + --color-bleu: #2d6db4; + --color-vert-de-gris: #3fafb6; - /* ======================================================================== + /* ======================================================================== TYPOGRAPHIE - CHARTE ALL4TREES ======================================================================== */ - --font-title-primary: "Phosphate", "Phosphate Solid", Impact, "Arial Black", system-ui, sans-serif; - --font-body-primary: Arial, system-ui, -apple-system, "Segoe UI", sans-serif; + --font-title-primary: + "Phosphate", "Phosphate Solid", Impact, "Arial Black", system-ui, sans-serif; + --font-body-primary: Arial, system-ui, -apple-system, "Segoe UI", sans-serif; - --font-title-secondary: "Century Gothic", "Century Gothic Black", "Arial Black", system-ui, sans-serif; - --font-body-secondary: "Open Sans", Arial, system-ui, -apple-system, "Segoe UI", sans-serif; -} \ No newline at end of file + --font-title-secondary: + "Century Gothic", "Century Gothic Black", "Arial Black", system-ui, + sans-serif; + --font-body-secondary: + "Open Sans", Arial, system-ui, -apple-system, "Segoe UI", sans-serif; +} diff --git a/webapp/src/app/styles/globals.css b/webapp/src/app/styles/globals.css index e70dda4..c74a228 100644 --- a/webapp/src/app/styles/globals.css +++ b/webapp/src/app/styles/globals.css @@ -12,30 +12,29 @@ RESET CSS ======================================================================== */ - @import "@app/styles/all4trees.css"; @import "@/index.css"; * { - margin: 0; - padding: 0; - box-sizing: border-box; + margin: 0; + padding: 0; + box-sizing: border-box; } html { - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-rendering: optimizeLegibility; } body { - font-size: 1rem; - line-height: 1.5; - font-weight: 400; - color-scheme: light dark; + font-size: 1rem; + line-height: 1.5; + font-weight: 400; + color-scheme: light dark; } h1 { - font-size: 3.2em; - line-height: 1.1; -} \ No newline at end of file + font-size: 3.2em; + line-height: 1.1; +} diff --git a/webapp/src/components/Charts/ExampleGraph.tsx b/webapp/src/components/Charts/ExampleGraph.tsx index 0ed7958..2f45d7a 100644 --- a/webapp/src/components/Charts/ExampleGraph.tsx +++ b/webapp/src/components/Charts/ExampleGraph.tsx @@ -1,114 +1,116 @@ -import { GitCommitVertical } from "lucide-react" -import { CartesianGrid, Line, LineChart, XAxis } from "recharts" +import { GitCommitVertical } from "lucide-react"; +import { CartesianGrid, Line, LineChart, XAxis } from "recharts"; import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@ui/card" + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@ui/card"; import { - ChartContainer, - ChartTooltip, - ChartTooltipContent, - type ChartConfig, -} from "@ui/chart" + type ChartConfig, + ChartContainer, + ChartTooltip, + ChartTooltipContent, +} from "@ui/chart"; -export const description = "A line chart with custom dots" +export const description = "A line chart with custom dots"; interface DataPoint { - year: number - month: number - kwhConsumed: number + year: number; + month: number; + kwhConsumed: number; } export interface GraphConsoElecProps { - name: string - chartData: DataPoint[] + name: string; + chartData: DataPoint[]; } const chartConfig = { - year: { - label: "Année", - color: "var(--chart-1)", - }, - month: { - label: "Mois", - color: "var(--chart-2)", - }, - kwhConsumed: { - label: "kWh Consommés", - color: "var(--chart-3)", - }, -} satisfies ChartConfig + year: { + label: "Année", + color: "var(--chart-1)", + }, + month: { + label: "Mois", + color: "var(--chart-2)", + }, + kwhConsumed: { + label: "kWh Consommés", + color: "var(--chart-3)", + }, +} satisfies ChartConfig; export function ExampleGraph({ name, chartData }: GraphConsoElecProps) { - chartData.sort((a, b) => { - if (a.year === b.year) { - return a.month - b.month - } - return a.year - b.year - }); - return ( - - - Consommation Electrique de {name} - 2015-2023 - - - - - - - - } - /> - { - const r = payload.kwhConsumed / 20 - return ( - - ) - }} - /> - - - - - ) -} \ No newline at end of file + chartData.sort((a, b) => { + if (a.year === b.year) { + return a.month - b.month; + } + return a.year - b.year; + }); + return ( + + + + Consommation Electrique de {name} + + 2015-2023 + + + + + + + + } + /> + { + const r = payload.kwhConsumed / 20; + return ( + + ); + }} + /> + + + + + ); +} diff --git a/webapp/src/components/Header.tsx b/webapp/src/components/Header.tsx index 21cb396..b187111 100644 --- a/webapp/src/components/Header.tsx +++ b/webapp/src/components/Header.tsx @@ -1,46 +1,49 @@ // frontend/src/shared/ui/Header/Header.tsx import "@app/styles/all4trees.css"; -import logo from '@assets/logo_all4trees.png'; -import { Button } from "@ui/button" + +import logo from "@assets/logo_all4trees.png"; + +import { Button } from "@ui/button"; + import { ModeToggle } from "./ModeToggle"; interface HeaderProps { - onLogout: () => void; - onLogin: () => void; - isLogin: boolean; + onLogout: () => void; + onLogin: () => void; + isLogin: boolean; } -export function Header({ - onLogout, - onLogin, - isLogin, -}: HeaderProps) { - return ( -
-
-
- Logo +export function Header({ onLogout, onLogin, isLogin }: HeaderProps) { + return ( +
+
+
+ Logo -
- {isLogin ? ( - - ) : ( - - )} - -
-
-
-
- ); -} \ No newline at end of file +
+ {isLogin ? ( + + ) : ( + + )} + +
+
+
+
+ ); +} diff --git a/webapp/src/components/ModeToggle.tsx b/webapp/src/components/ModeToggle.tsx index 46850c5..9c9e214 100644 --- a/webapp/src/components/ModeToggle.tsx +++ b/webapp/src/components/ModeToggle.tsx @@ -1,36 +1,41 @@ -import { Moon, Sun } from "lucide-react" +import { Moon, Sun } from "lucide-react"; -import { Button } from "@/components/ui/button" +import { useTheme } from "@/app/providers/ThemeProvider"; +import { Button } from "@/components/ui/button"; import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu" -import { useTheme } from "@/app/providers/ThemeProvider" + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; export function ModeToggle() { - const { setTheme } = useTheme() + const { setTheme } = useTheme(); - return ( - - - - - - setTheme("light")}> - Light - - setTheme("dark")}> - Dark - - - - ) - -} \ No newline at end of file + return ( + + + + + + setTheme("light")}> + Light + + setTheme("dark")}> + Dark + + + + ); +} diff --git a/webapp/src/components/index.ts b/webapp/src/components/index.ts index 8e2fc40..1d8ab55 100644 --- a/webapp/src/components/index.ts +++ b/webapp/src/components/index.ts @@ -1,3 +1,3 @@ export { Header } from "./Header"; export { ModeToggle } from "./ModeToggle"; -export { ExampleGraph, type GraphConsoElecProps } from "./Charts/ExampleGraph"; \ No newline at end of file +export { ExampleGraph, type GraphConsoElecProps } from "./Charts/ExampleGraph"; diff --git a/webapp/src/components/ui/button.tsx b/webapp/src/components/ui/button.tsx index 65d4fcd..c478f6d 100644 --- a/webapp/src/components/ui/button.tsx +++ b/webapp/src/components/ui/button.tsx @@ -1,8 +1,9 @@ -import * as React from "react" -import { Slot } from "@radix-ui/react-slot" -import { cva, type VariantProps } from "class-variance-authority" +import { type VariantProps, cva } from "class-variance-authority"; +import * as React from "react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; + +import { Slot } from "@radix-ui/react-slot"; const buttonVariants = cva( "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", @@ -31,27 +32,28 @@ const buttonVariants = cva( variant: "default", size: "default", }, - } -) + }, +); export interface ButtonProps - extends React.ButtonHTMLAttributes, + extends + React.ButtonHTMLAttributes, VariantProps { - asChild?: boolean + asChild?: boolean; } const Button = React.forwardRef( ({ className, variant, size, asChild = false, ...props }, ref) => { - const Comp = asChild ? Slot : "button" + const Comp = asChild ? Slot : "button"; return ( - ) - } -) -Button.displayName = "Button" + ); + }, +); +Button.displayName = "Button"; -export { Button, buttonVariants } +export { Button, buttonVariants }; diff --git a/webapp/src/components/ui/card.tsx b/webapp/src/components/ui/card.tsx index cabfbfc..13b4726 100644 --- a/webapp/src/components/ui/card.tsx +++ b/webapp/src/components/ui/card.tsx @@ -1,6 +1,6 @@ -import * as React from "react" +import * as React from "react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; const Card = React.forwardRef< HTMLDivElement, @@ -10,12 +10,12 @@ const Card = React.forwardRef< ref={ref} className={cn( "rounded-xl border bg-card text-card-foreground shadow", - className + className, )} {...props} /> -)) -Card.displayName = "Card" +)); +Card.displayName = "Card"; const CardHeader = React.forwardRef< HTMLDivElement, @@ -26,8 +26,8 @@ const CardHeader = React.forwardRef< className={cn("flex flex-col space-y-1.5 p-6", className)} {...props} /> -)) -CardHeader.displayName = "CardHeader" +)); +CardHeader.displayName = "CardHeader"; const CardTitle = React.forwardRef< HTMLDivElement, @@ -38,8 +38,8 @@ const CardTitle = React.forwardRef< className={cn("font-semibold leading-none tracking-tight", className)} {...props} /> -)) -CardTitle.displayName = "CardTitle" +)); +CardTitle.displayName = "CardTitle"; const CardDescription = React.forwardRef< HTMLDivElement, @@ -50,16 +50,20 @@ const CardDescription = React.forwardRef< className={cn("text-sm text-muted-foreground", className)} {...props} /> -)) -CardDescription.displayName = "CardDescription" +)); +CardDescription.displayName = "CardDescription"; const CardContent = React.forwardRef< HTMLDivElement, React.HTMLAttributes >(({ className, ...props }, ref) => ( -
-)) -CardContent.displayName = "CardContent" +
+)); +CardContent.displayName = "CardContent"; const CardFooter = React.forwardRef< HTMLDivElement, @@ -70,7 +74,14 @@ const CardFooter = React.forwardRef< className={cn("flex items-center p-6 pt-0", className)} {...props} /> -)) -CardFooter.displayName = "CardFooter" +)); +CardFooter.displayName = "CardFooter"; -export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } +export { + Card, + CardHeader, + CardFooter, + CardTitle, + CardDescription, + CardContent, +}; diff --git a/webapp/src/components/ui/chart.tsx b/webapp/src/components/ui/chart.tsx index 23dc1c1..556804c 100644 --- a/webapp/src/components/ui/chart.tsx +++ b/webapp/src/components/ui/chart.tsx @@ -1,48 +1,48 @@ -import * as React from "react" -import * as RechartsPrimitive from "recharts" +import * as React from "react"; +import * as RechartsPrimitive from "recharts"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; // Format: { THEME_NAME: CSS_SELECTOR } -const THEMES = { light: "", dark: ".dark" } as const +const THEMES = { light: "", dark: ".dark" } as const; export type ChartConfig = { [k in string]: { - label?: React.ReactNode - icon?: React.ComponentType + label?: React.ReactNode; + icon?: React.ComponentType; } & ( | { color?: string; theme?: never } | { color?: never; theme: Record } - ) -} + ); +}; type ChartContextProps = { - config: ChartConfig -} + config: ChartConfig; +}; -const ChartContext = React.createContext(null) +const ChartContext = React.createContext(null); function useChart() { - const context = React.useContext(ChartContext) + const context = React.useContext(ChartContext); if (!context) { - throw new Error("useChart must be used within a ") + throw new Error("useChart must be used within a "); } - return context + return context; } const ChartContainer = React.forwardRef< HTMLDivElement, React.ComponentProps<"div"> & { - config: ChartConfig + config: ChartConfig; children: React.ComponentProps< typeof RechartsPrimitive.ResponsiveContainer - >["children"] + >["children"]; } >(({ id, className, children, config, ...props }, ref) => { - const uniqueId = React.useId() - const chartId = `chart-${id || uniqueId.replace(/:/g, "")}` + const uniqueId = React.useId(); + const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`; return ( @@ -51,27 +51,30 @@ const ChartContainer = React.forwardRef< ref={ref} className={cn( "flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none", - className + className, )} {...props} > - + {children}
- ) -}) -ChartContainer.displayName = "Chart" + ); +}); +ChartContainer.displayName = "Chart"; const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => { const colorConfig = Object.entries(config).filter( - ([, config]) => config.theme || config.color - ) + ([, config]) => config.theme || config.color, + ); if (!colorConfig.length) { - return null + return null; } return ( @@ -85,30 +88,30 @@ ${colorConfig .map(([key, itemConfig]) => { const color = itemConfig.theme?.[theme as keyof typeof itemConfig.theme] || - itemConfig.color - return color ? ` --color-${key}: ${color};` : null + itemConfig.color; + return color ? ` --color-${key}: ${color};` : null; }) .join("\n")} } -` +`, ) .join("\n"), }} /> - ) -} + ); +}; -const ChartTooltip = RechartsPrimitive.Tooltip +const ChartTooltip = RechartsPrimitive.Tooltip; const ChartTooltipContent = React.forwardRef< HTMLDivElement, React.ComponentProps & React.ComponentProps<"div"> & { - hideLabel?: boolean - hideIndicator?: boolean - indicator?: "line" | "dot" | "dashed" - nameKey?: string - labelKey?: string + hideLabel?: boolean; + hideIndicator?: boolean; + indicator?: "line" | "dot" | "dashed"; + nameKey?: string; + labelKey?: string; } >( ( @@ -127,36 +130,36 @@ const ChartTooltipContent = React.forwardRef< nameKey, labelKey, }, - ref + ref, ) => { - const { config } = useChart() + const { config } = useChart(); const tooltipLabel = React.useMemo(() => { if (hideLabel || !payload?.length) { - return null + return null; } - const [item] = payload - const key = `${labelKey || item?.dataKey || item?.name || "value"}` - const itemConfig = getPayloadConfigFromPayload(config, item, key) + const [item] = payload; + const key = `${labelKey || item?.dataKey || item?.name || "value"}`; + const itemConfig = getPayloadConfigFromPayload(config, item, key); const value = !labelKey && typeof label === "string" ? config[label as keyof typeof config]?.label || label - : itemConfig?.label + : itemConfig?.label; if (labelFormatter) { return (
{labelFormatter(value, payload)}
- ) + ); } if (!value) { - return null + return null; } - return
{value}
+ return
{value}
; }, [ label, labelFormatter, @@ -165,20 +168,20 @@ const ChartTooltipContent = React.forwardRef< labelClassName, config, labelKey, - ]) + ]); if (!active || !payload?.length) { - return null + return null; } - const nestLabel = payload.length === 1 && indicator !== "dot" + const nestLabel = payload.length === 1 && indicator !== "dot"; return (
{!nestLabel ? tooltipLabel : null} @@ -186,16 +189,16 @@ const ChartTooltipContent = React.forwardRef< {payload .filter((item) => item.type !== "none") .map((item, index) => { - const key = `${nameKey || item.name || item.dataKey || "value"}` - const itemConfig = getPayloadConfigFromPayload(config, item, key) - const indicatorColor = color || item.payload.fill || item.color + const key = `${nameKey || item.name || item.dataKey || "value"}`; + const itemConfig = getPayloadConfigFromPayload(config, item, key); + const indicatorColor = color || item.payload.fill || item.color; return (
svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground", - indicator === "dot" && "items-center" + indicator === "dot" && "items-center", )} > {formatter && item?.value !== undefined && item.name ? ( @@ -215,7 +218,7 @@ const ChartTooltipContent = React.forwardRef< "w-0 border-[1.5px] border-dashed bg-transparent": indicator === "dashed", "my-0.5": nestLabel && indicator === "dashed", - } + }, )} style={ { @@ -229,7 +232,7 @@ const ChartTooltipContent = React.forwardRef<
@@ -247,33 +250,33 @@ const ChartTooltipContent = React.forwardRef< )}
- ) + ); })}
- ) - } -) -ChartTooltipContent.displayName = "ChartTooltip" + ); + }, +); +ChartTooltipContent.displayName = "ChartTooltip"; -const ChartLegend = RechartsPrimitive.Legend +const ChartLegend = RechartsPrimitive.Legend; const ChartLegendContent = React.forwardRef< HTMLDivElement, React.ComponentProps<"div"> & Pick & { - hideIcon?: boolean - nameKey?: string + hideIcon?: boolean; + nameKey?: string; } >( ( { className, hideIcon = false, payload, verticalAlign = "bottom", nameKey }, - ref + ref, ) => { - const { config } = useChart() + const { config } = useChart(); if (!payload?.length) { - return null + return null; } return ( @@ -282,20 +285,20 @@ const ChartLegendContent = React.forwardRef< className={cn( "flex items-center justify-center gap-4", verticalAlign === "top" ? "pb-3" : "pt-3", - className + className, )} > {payload .filter((item) => item.type !== "none") .map((item) => { - const key = `${nameKey || item.dataKey || "value"}` - const itemConfig = getPayloadConfigFromPayload(config, item, key) + const key = `${nameKey || item.dataKey || "value"}`; + const itemConfig = getPayloadConfigFromPayload(config, item, key); return (
svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground" + "flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground", )} > {itemConfig?.icon && !hideIcon ? ( @@ -310,22 +313,22 @@ const ChartLegendContent = React.forwardRef< )} {itemConfig?.label}
- ) + ); })}
- ) - } -) -ChartLegendContent.displayName = "ChartLegend" + ); + }, +); +ChartLegendContent.displayName = "ChartLegend"; // Helper to extract item config from a payload. function getPayloadConfigFromPayload( config: ChartConfig, payload: unknown, - key: string + key: string, ) { if (typeof payload !== "object" || payload === null) { - return undefined + return undefined; } const payloadPayload = @@ -333,15 +336,15 @@ function getPayloadConfigFromPayload( typeof payload.payload === "object" && payload.payload !== null ? payload.payload - : undefined + : undefined; - let configLabelKey: string = key + let configLabelKey: string = key; if ( key in payload && typeof payload[key as keyof typeof payload] === "string" ) { - configLabelKey = payload[key as keyof typeof payload] as string + configLabelKey = payload[key as keyof typeof payload] as string; } else if ( payloadPayload && key in payloadPayload && @@ -349,12 +352,12 @@ function getPayloadConfigFromPayload( ) { configLabelKey = payloadPayload[ key as keyof typeof payloadPayload - ] as string + ] as string; } return configLabelKey in config ? config[configLabelKey] - : config[key as keyof typeof config] + : config[key as keyof typeof config]; } export { @@ -364,4 +367,4 @@ export { ChartLegend, ChartLegendContent, ChartStyle, -} +}; diff --git a/webapp/src/components/ui/dropdown-menu.tsx b/webapp/src/components/ui/dropdown-menu.tsx index e804bca..5b67ee7 100644 --- a/webapp/src/components/ui/dropdown-menu.tsx +++ b/webapp/src/components/ui/dropdown-menu.tsx @@ -1,25 +1,26 @@ -import * as React from "react" -import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" -import { Check, ChevronRight, Circle } from "lucide-react" +import { Check, ChevronRight, Circle } from "lucide-react"; +import * as React from "react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; -const DropdownMenu = DropdownMenuPrimitive.Root +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; -const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger +const DropdownMenu = DropdownMenuPrimitive.Root; -const DropdownMenuGroup = DropdownMenuPrimitive.Group +const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; -const DropdownMenuPortal = DropdownMenuPrimitive.Portal +const DropdownMenuGroup = DropdownMenuPrimitive.Group; -const DropdownMenuSub = DropdownMenuPrimitive.Sub +const DropdownMenuPortal = DropdownMenuPrimitive.Portal; -const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup +const DropdownMenuSub = DropdownMenuPrimitive.Sub; + +const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup; const DropdownMenuSubTrigger = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & { - inset?: boolean + inset?: boolean; } >(({ className, inset, children, ...props }, ref) => ( {children} -)) +)); DropdownMenuSubTrigger.displayName = - DropdownMenuPrimitive.SubTrigger.displayName + DropdownMenuPrimitive.SubTrigger.displayName; const DropdownMenuSubContent = React.forwardRef< React.ElementRef, @@ -46,13 +47,13 @@ const DropdownMenuSubContent = React.forwardRef< ref={ref} className={cn( "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]", - className + className, )} {...props} /> -)) +)); DropdownMenuSubContent.displayName = - DropdownMenuPrimitive.SubContent.displayName + DropdownMenuPrimitive.SubContent.displayName; const DropdownMenuContent = React.forwardRef< React.ElementRef, @@ -65,18 +66,18 @@ const DropdownMenuContent = React.forwardRef< className={cn( "z-50 max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md", "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]", - className + className, )} {...props} /> -)) -DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName +)); +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName; const DropdownMenuItem = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & { - inset?: boolean + inset?: boolean; } >(({ className, inset, ...props }, ref) => ( svg]:size-4 [&>svg]:shrink-0", inset && "pl-8", - className + className, )} {...props} /> -)) -DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName +)); +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName; const DropdownMenuCheckboxItem = React.forwardRef< React.ElementRef, @@ -99,7 +100,7 @@ const DropdownMenuCheckboxItem = React.forwardRef< ref={ref} className={cn( "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", - className + className, )} checked={checked} {...props} @@ -111,9 +112,9 @@ const DropdownMenuCheckboxItem = React.forwardRef< {children} -)) +)); DropdownMenuCheckboxItem.displayName = - DropdownMenuPrimitive.CheckboxItem.displayName + DropdownMenuPrimitive.CheckboxItem.displayName; const DropdownMenuRadioItem = React.forwardRef< React.ElementRef, @@ -123,7 +124,7 @@ const DropdownMenuRadioItem = React.forwardRef< ref={ref} className={cn( "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", - className + className, )} {...props} > @@ -134,13 +135,13 @@ const DropdownMenuRadioItem = React.forwardRef< {children} -)) -DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName +)); +DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName; const DropdownMenuLabel = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & { - inset?: boolean + inset?: boolean; } >(({ className, inset, ...props }, ref) => ( -)) -DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName +)); +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName; const DropdownMenuSeparator = React.forwardRef< React.ElementRef, @@ -164,8 +165,8 @@ const DropdownMenuSeparator = React.forwardRef< className={cn("-mx-1 my-1 h-px bg-muted", className)} {...props} /> -)) -DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName +)); +DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName; const DropdownMenuShortcut = ({ className, @@ -176,9 +177,9 @@ const DropdownMenuShortcut = ({ className={cn("ml-auto text-xs tracking-widest opacity-60", className)} {...props} /> - ) -} -DropdownMenuShortcut.displayName = "DropdownMenuShortcut" + ); +}; +DropdownMenuShortcut.displayName = "DropdownMenuShortcut"; export { DropdownMenu, @@ -196,4 +197,4 @@ export { DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuRadioGroup, -} +}; diff --git a/webapp/src/components/ui/input.tsx b/webapp/src/components/ui/input.tsx index 69b64fb..7db5241 100644 --- a/webapp/src/components/ui/input.tsx +++ b/webapp/src/components/ui/input.tsx @@ -1,6 +1,6 @@ -import * as React from "react" +import * as React from "react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; const Input = React.forwardRef>( ({ className, type, ...props }, ref) => { @@ -9,14 +9,14 @@ const Input = React.forwardRef>( type={type} className={cn( "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", - className + className, )} ref={ref} {...props} /> - ) - } -) -Input.displayName = "Input" + ); + }, +); +Input.displayName = "Input"; -export { Input } +export { Input }; diff --git a/webapp/src/components/ui/label.tsx b/webapp/src/components/ui/label.tsx index 683faa7..36ab4e7 100644 --- a/webapp/src/components/ui/label.tsx +++ b/webapp/src/components/ui/label.tsx @@ -1,12 +1,13 @@ -import * as React from "react" -import * as LabelPrimitive from "@radix-ui/react-label" -import { cva, type VariantProps } from "class-variance-authority" +import { type VariantProps, cva } from "class-variance-authority"; +import * as React from "react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; + +import * as LabelPrimitive from "@radix-ui/react-label"; const labelVariants = cva( - "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" -) + "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", +); const Label = React.forwardRef< React.ElementRef, @@ -18,7 +19,7 @@ const Label = React.forwardRef< className={cn(labelVariants(), className)} {...props} /> -)) -Label.displayName = LabelPrimitive.Root.displayName +)); +Label.displayName = LabelPrimitive.Root.displayName; -export { Label } +export { Label }; diff --git a/webapp/src/components/ui/popover.tsx b/webapp/src/components/ui/popover.tsx index fdcb511..f9f8d87 100644 --- a/webapp/src/components/ui/popover.tsx +++ b/webapp/src/components/ui/popover.tsx @@ -1,13 +1,14 @@ -import * as React from "react" -import * as PopoverPrimitive from "@radix-ui/react-popover" +import * as React from "react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; -const Popover = PopoverPrimitive.Root +import * as PopoverPrimitive from "@radix-ui/react-popover"; -const PopoverTrigger = PopoverPrimitive.Trigger +const Popover = PopoverPrimitive.Root; -const PopoverAnchor = PopoverPrimitive.Anchor +const PopoverTrigger = PopoverPrimitive.Trigger; + +const PopoverAnchor = PopoverPrimitive.Anchor; const PopoverContent = React.forwardRef< React.ElementRef, @@ -20,12 +21,12 @@ const PopoverContent = React.forwardRef< sideOffset={sideOffset} className={cn( "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-popover-content-transform-origin]", - className + className, )} {...props} /> -)) -PopoverContent.displayName = PopoverPrimitive.Content.displayName +)); +PopoverContent.displayName = PopoverPrimitive.Content.displayName; -export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor } +export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }; diff --git a/webapp/src/components/ui/radio-group.tsx b/webapp/src/components/ui/radio-group.tsx index 9d3a26e..b05ebe3 100644 --- a/webapp/src/components/ui/radio-group.tsx +++ b/webapp/src/components/ui/radio-group.tsx @@ -1,8 +1,9 @@ -import * as React from "react" -import * as RadioGroupPrimitive from "@radix-ui/react-radio-group" -import { Circle } from "lucide-react" +import { Circle } from "lucide-react"; +import * as React from "react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; + +import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"; const RadioGroup = React.forwardRef< React.ElementRef, @@ -14,9 +15,9 @@ const RadioGroup = React.forwardRef< {...props} ref={ref} /> - ) -}) -RadioGroup.displayName = RadioGroupPrimitive.Root.displayName + ); +}); +RadioGroup.displayName = RadioGroupPrimitive.Root.displayName; const RadioGroupItem = React.forwardRef< React.ElementRef, @@ -27,7 +28,7 @@ const RadioGroupItem = React.forwardRef< ref={ref} className={cn( "aspect-square h-4 w-4 rounded-full border border-primary text-primary shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50", - className + className, )} {...props} > @@ -35,8 +36,8 @@ const RadioGroupItem = React.forwardRef< - ) -}) -RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName + ); +}); +RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName; -export { RadioGroup, RadioGroupItem } +export { RadioGroup, RadioGroupItem }; diff --git a/webapp/src/components/ui/resizable.tsx b/webapp/src/components/ui/resizable.tsx index cd3cb0e..4460345 100644 --- a/webapp/src/components/ui/resizable.tsx +++ b/webapp/src/components/ui/resizable.tsx @@ -1,7 +1,7 @@ -import { GripVertical } from "lucide-react" -import * as ResizablePrimitive from "react-resizable-panels" +import { GripVertical } from "lucide-react"; +import * as ResizablePrimitive from "react-resizable-panels"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; const ResizablePanelGroup = ({ className, @@ -10,25 +10,25 @@ const ResizablePanelGroup = ({ -) +); -const ResizablePanel = ResizablePrimitive.Panel +const ResizablePanel = ResizablePrimitive.Panel; const ResizableHandle = ({ withHandle, className, ...props }: React.ComponentProps & { - withHandle?: boolean + withHandle?: boolean; }) => ( div]:rotate-90", - className + className, )} {...props} > @@ -38,6 +38,6 @@ const ResizableHandle = ({
)} -) +); -export { ResizablePanelGroup, ResizablePanel, ResizableHandle } +export { ResizablePanelGroup, ResizablePanel, ResizableHandle }; diff --git a/webapp/src/components/ui/separator.tsx b/webapp/src/components/ui/separator.tsx index 6d7f122..a7bacae 100644 --- a/webapp/src/components/ui/separator.tsx +++ b/webapp/src/components/ui/separator.tsx @@ -1,7 +1,8 @@ -import * as React from "react" -import * as SeparatorPrimitive from "@radix-ui/react-separator" +import * as React from "react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; + +import * as SeparatorPrimitive from "@radix-ui/react-separator"; const Separator = React.forwardRef< React.ElementRef, @@ -9,7 +10,7 @@ const Separator = React.forwardRef< >( ( { className, orientation = "horizontal", decorative = true, ...props }, - ref + ref, ) => ( - ) -) -Separator.displayName = SeparatorPrimitive.Root.displayName + ), +); +Separator.displayName = SeparatorPrimitive.Root.displayName; -export { Separator } +export { Separator }; diff --git a/webapp/src/components/ui/sheet.tsx b/webapp/src/components/ui/sheet.tsx index 272cb72..19a2a68 100644 --- a/webapp/src/components/ui/sheet.tsx +++ b/webapp/src/components/ui/sheet.tsx @@ -1,19 +1,20 @@ -"use client" +"use client"; -import * as React from "react" -import * as SheetPrimitive from "@radix-ui/react-dialog" -import { cva, type VariantProps } from "class-variance-authority" -import { X } from "lucide-react" +import { type VariantProps, cva } from "class-variance-authority"; +import { X } from "lucide-react"; +import * as React from "react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; -const Sheet = SheetPrimitive.Root +import * as SheetPrimitive from "@radix-ui/react-dialog"; -const SheetTrigger = SheetPrimitive.Trigger +const Sheet = SheetPrimitive.Root; -const SheetClose = SheetPrimitive.Close +const SheetTrigger = SheetPrimitive.Trigger; -const SheetPortal = SheetPrimitive.Portal +const SheetClose = SheetPrimitive.Close; + +const SheetPortal = SheetPrimitive.Portal; const SheetOverlay = React.forwardRef< React.ElementRef, @@ -22,13 +23,13 @@ const SheetOverlay = React.forwardRef< -)) -SheetOverlay.displayName = SheetPrimitive.Overlay.displayName +)); +SheetOverlay.displayName = SheetPrimitive.Overlay.displayName; const sheetVariants = cva( "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out", @@ -46,11 +47,12 @@ const sheetVariants = cva( defaultVariants: { side: "right", }, - } -) + }, +); interface SheetContentProps - extends React.ComponentPropsWithoutRef, + extends + React.ComponentPropsWithoutRef, VariantProps {} const SheetContent = React.forwardRef< @@ -71,8 +73,8 @@ const SheetContent = React.forwardRef< {children} -)) -SheetContent.displayName = SheetPrimitive.Content.displayName +)); +SheetContent.displayName = SheetPrimitive.Content.displayName; const SheetHeader = ({ className, @@ -81,12 +83,12 @@ const SheetHeader = ({
-) -SheetHeader.displayName = "SheetHeader" +); +SheetHeader.displayName = "SheetHeader"; const SheetFooter = ({ className, @@ -95,12 +97,12 @@ const SheetFooter = ({
-) -SheetFooter.displayName = "SheetFooter" +); +SheetFooter.displayName = "SheetFooter"; const SheetTitle = React.forwardRef< React.ElementRef, @@ -111,8 +113,8 @@ const SheetTitle = React.forwardRef< className={cn("text-lg font-semibold text-foreground", className)} {...props} /> -)) -SheetTitle.displayName = SheetPrimitive.Title.displayName +)); +SheetTitle.displayName = SheetPrimitive.Title.displayName; const SheetDescription = React.forwardRef< React.ElementRef, @@ -123,8 +125,8 @@ const SheetDescription = React.forwardRef< className={cn("text-sm text-muted-foreground", className)} {...props} /> -)) -SheetDescription.displayName = SheetPrimitive.Description.displayName +)); +SheetDescription.displayName = SheetPrimitive.Description.displayName; export { Sheet, @@ -137,4 +139,4 @@ export { SheetFooter, SheetTitle, SheetDescription, -} +}; diff --git a/webapp/src/components/ui/sidebar.tsx b/webapp/src/components/ui/sidebar.tsx index c7ca99c..bb7f7c5 100644 --- a/webapp/src/components/ui/sidebar.tsx +++ b/webapp/src/components/ui/sidebar.tsx @@ -1,62 +1,63 @@ -import * as React from "react" -import { Slot } from "@radix-ui/react-slot" -import { cva, type VariantProps } from "class-variance-authority" -import { PanelLeft } from "lucide-react" - -import { useIsMobile } from "@/hooks/use-mobile" -import { cn } from "@/lib/utils" -import { Button } from "@/components/ui/button" -import { Input } from "@/components/ui/input" -import { Separator } from "@/components/ui/separator" +import { type VariantProps, cva } from "class-variance-authority"; +import { PanelLeft } from "lucide-react"; +import * as React from "react"; + +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Separator } from "@/components/ui/separator"; import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, -} from "@/components/ui/sheet" -import { Skeleton } from "@/components/ui/skeleton" +} from "@/components/ui/sheet"; +import { Skeleton } from "@/components/ui/skeleton"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, -} from "@/components/ui/tooltip" +} from "@/components/ui/tooltip"; +import { useIsMobile } from "@/hooks/use-mobile"; +import { cn } from "@/lib/utils"; + +import { Slot } from "@radix-ui/react-slot"; -const SIDEBAR_COOKIE_NAME = "sidebar_state" -const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7 -const SIDEBAR_WIDTH = "16rem" -const SIDEBAR_WIDTH_MOBILE = "18rem" -const SIDEBAR_WIDTH_ICON = "3rem" -const SIDEBAR_KEYBOARD_SHORTCUT = "b" +const SIDEBAR_COOKIE_NAME = "sidebar_state"; +const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7; +const SIDEBAR_WIDTH = "16rem"; +const SIDEBAR_WIDTH_MOBILE = "18rem"; +const SIDEBAR_WIDTH_ICON = "3rem"; +const SIDEBAR_KEYBOARD_SHORTCUT = "b"; type SidebarContextProps = { - state: "expanded" | "collapsed" - open: boolean - setOpen: (open: boolean) => void - openMobile: boolean - setOpenMobile: (open: boolean) => void - isMobile: boolean - toggleSidebar: () => void -} + state: "expanded" | "collapsed"; + open: boolean; + setOpen: (open: boolean) => void; + openMobile: boolean; + setOpenMobile: (open: boolean) => void; + isMobile: boolean; + toggleSidebar: () => void; +}; -const SidebarContext = React.createContext(null) +const SidebarContext = React.createContext(null); function useSidebar() { - const context = React.useContext(SidebarContext) + const context = React.useContext(SidebarContext); if (!context) { - throw new Error("useSidebar must be used within a SidebarProvider.") + throw new Error("useSidebar must be used within a SidebarProvider."); } - return context + return context; } const SidebarProvider = React.forwardRef< HTMLDivElement, React.ComponentProps<"div"> & { - defaultOpen?: boolean - open?: boolean - onOpenChange?: (open: boolean) => void + defaultOpen?: boolean; + open?: boolean; + onOpenChange?: (open: boolean) => void; } >( ( @@ -69,36 +70,36 @@ const SidebarProvider = React.forwardRef< children, ...props }, - ref + ref, ) => { - const isMobile = useIsMobile() - const [openMobile, setOpenMobile] = React.useState(false) + const isMobile = useIsMobile(); + const [openMobile, setOpenMobile] = React.useState(false); // This is the internal state of the sidebar. // We use openProp and setOpenProp for control from outside the component. - const [_open, _setOpen] = React.useState(defaultOpen) - const open = openProp ?? _open + const [_open, _setOpen] = React.useState(defaultOpen); + const open = openProp ?? _open; const setOpen = React.useCallback( (value: boolean | ((value: boolean) => boolean)) => { - const openState = typeof value === "function" ? value(open) : value + const openState = typeof value === "function" ? value(open) : value; if (setOpenProp) { - setOpenProp(openState) + setOpenProp(openState); } else { - _setOpen(openState) + _setOpen(openState); } // This sets the cookie to keep the sidebar state. - document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}` + document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`; }, - [setOpenProp, open] - ) + [setOpenProp, open], + ); // Helper to toggle the sidebar. const toggleSidebar = React.useCallback(() => { return isMobile ? setOpenMobile((open) => !open) - : setOpen((open) => !open) - }, [isMobile, setOpen, setOpenMobile]) + : setOpen((open) => !open); + }, [isMobile, setOpen, setOpenMobile]); // Adds a keyboard shortcut to toggle the sidebar. React.useEffect(() => { @@ -107,18 +108,18 @@ const SidebarProvider = React.forwardRef< event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey) ) { - event.preventDefault() - toggleSidebar() + event.preventDefault(); + toggleSidebar(); } - } + }; - window.addEventListener("keydown", handleKeyDown) - return () => window.removeEventListener("keydown", handleKeyDown) - }, [toggleSidebar]) + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [toggleSidebar]); // We add a state so that we can do data-state="expanded" or "collapsed". // This makes it easier to style the sidebar with Tailwind classes. - const state = open ? "expanded" : "collapsed" + const state = open ? "expanded" : "collapsed"; const contextValue = React.useMemo( () => ({ @@ -130,8 +131,16 @@ const SidebarProvider = React.forwardRef< setOpenMobile, toggleSidebar, }), - [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar] - ) + [ + state, + open, + setOpen, + isMobile, + openMobile, + setOpenMobile, + toggleSidebar, + ], + ); return ( @@ -146,7 +155,7 @@ const SidebarProvider = React.forwardRef< } className={cn( "group/sidebar-wrapper flex min-h-svh w-full has-[[data-variant=inset]]:bg-sidebar", - className + className, )} ref={ref} {...props} @@ -155,17 +164,17 @@ const SidebarProvider = React.forwardRef<
- ) - } -) -SidebarProvider.displayName = "SidebarProvider" + ); + }, +); +SidebarProvider.displayName = "SidebarProvider"; const Sidebar = React.forwardRef< HTMLDivElement, React.ComponentProps<"div"> & { - side?: "left" | "right" - variant?: "sidebar" | "floating" | "inset" - collapsible?: "offcanvas" | "icon" | "none" + side?: "left" | "right"; + variant?: "sidebar" | "floating" | "inset"; + collapsible?: "offcanvas" | "icon" | "none"; } >( ( @@ -177,28 +186,32 @@ const Sidebar = React.forwardRef< children, ...props }, - ref + ref, ) => { - const { isMobile, state, openMobile, setOpenMobile } = useSidebar() + const { isMobile, state, openMobile, setOpenMobile } = useSidebar(); if (collapsible === "none") { return (
{children}
- ) + ); } if (isMobile) { return ( - + {children}
- ) + ); } return ( @@ -237,7 +250,7 @@ const Sidebar = React.forwardRef< "group-data-[side=right]:rotate-180", variant === "floating" || variant === "inset" ? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]" - : "group-data-[collapsible=icon]:w-[--sidebar-width-icon]" + : "group-data-[collapsible=icon]:w-[--sidebar-width-icon]", )} />
@@ -262,16 +275,16 @@ const Sidebar = React.forwardRef<
- ) - } -) -Sidebar.displayName = "Sidebar" + ); + }, +); +Sidebar.displayName = "Sidebar"; const SidebarTrigger = React.forwardRef< React.ElementRef, React.ComponentProps >(({ className, onClick, ...props }, ref) => { - const { toggleSidebar } = useSidebar() + const { toggleSidebar } = useSidebar(); return ( - ) -}) -SidebarTrigger.displayName = "SidebarTrigger" + ); +}); +SidebarTrigger.displayName = "SidebarTrigger"; const SidebarRail = React.forwardRef< HTMLButtonElement, React.ComponentProps<"button"> >(({ className, ...props }, ref) => { - const { toggleSidebar } = useSidebar() + const { toggleSidebar } = useSidebar(); return ( - - -
-
-

Dashboard

-

- Simple graphs to represent data -

-
-
- {graphData && dataType === "example" && ( - - )} - -
-
-
- - ); -} \ No newline at end of file + return ( + + + + + +
+
+

Dashboard

+

+ Simple graphs to represent data +

+
+
+ {graphData && dataType === "example" && ( + + )} +
+
+
+
+ ); +} diff --git a/webapp/src/widgets/Map.tsx b/webapp/src/widgets/Map.tsx index e2c3dd3..50b50b5 100644 --- a/webapp/src/widgets/Map.tsx +++ b/webapp/src/widgets/Map.tsx @@ -1,42 +1,48 @@ // @ts-expect-error Could not find a declaration file for module 'coordo'. -import { createMap } from "coordo" -import { useEffect, useRef, useState, type FC } from "react"; +import { createMap } from "coordo"; +import { type FC, useEffect, useRef, useState } from "react"; export type MapApi = { - getDataForLayer: (layerId: string) => unknown; -} + getDataForLayer: (layerId: string) => unknown; +}; // Hook personnalisé pour initialiser la carte function useMap(containerSelector: string) { - const [mapApi, setMapApi] = useState({} as MapApi); - const isInitialized = useRef(false); - - useEffect(() => { - if (isInitialized.current) return; - - try { - const api = createMap(containerSelector); - setMapApi(api); - isInitialized.current = true; - } catch (error) { - console.error("Erreur lors de l'initialisation de la carte:", error); - } - }, [containerSelector]); - - return mapApi; + const [mapApi, setMapApi] = useState({} as MapApi); + const isInitialized = useRef(false); + + useEffect(() => { + if (isInitialized.current) return; + + try { + const api = createMap(containerSelector); + setMapApi(api); + isInitialized.current = true; + } catch (error) { + console.error("Erreur lors de l'initialisation de la carte:", error); + } + }, [containerSelector]); + + return mapApi; } export const Map: FC = () => { - const mapApi = useMap("#map"); - - return ( -
console.log(mapApi.getDataForLayer("my-layer"))}> - -
- ); -} - + const mapApi = useMap("#map"); + + return ( +
console.log(mapApi.getDataForLayer("my-layer"))} + > + +
+ ); +}; diff --git a/webapp/src/widgets/MapSidebar.tsx b/webapp/src/widgets/MapSidebar.tsx index aa4c211..3c8a142 100644 --- a/webapp/src/widgets/MapSidebar.tsx +++ b/webapp/src/widgets/MapSidebar.tsx @@ -1,40 +1,44 @@ -import { useState } from "react" +import { useState } from "react"; -import { Label } from "@ui/label" -import { RadioGroup, RadioGroupItem } from "@ui/radio-group" -import { Slider } from "@ui/slider" +import { Label } from "@ui/label"; +import { RadioGroup, RadioGroupItem } from "@ui/radio-group"; +import { Slider } from "@ui/slider"; export function MapSidebar() { - const [value, setValue] = useState([10]) + const [value, setValue] = useState([10]); - return ( - <> -

Section de filtres

- -
- - -
-
- - -
-
-
- - - {value} - -
- -
-
- - ) -} \ No newline at end of file + return ( + <> +

Section de filtres

+ +
+ + +
+
+ + +
+
+
+ + {value} +
+ +
+
+ + ); +} diff --git a/webapp/src/widgets/index.ts b/webapp/src/widgets/index.ts index c0f06c6..00e8abe 100644 --- a/webapp/src/widgets/index.ts +++ b/webapp/src/widgets/index.ts @@ -1,3 +1,3 @@ export { DashboardPopover } from "./DashboardPopover"; export { MapSidebar } from "./MapSidebar"; -export { Map } from "./Map"; \ No newline at end of file +export { Map } from "./Map"; diff --git a/webapp/tailwind.config.ts b/webapp/tailwind.config.ts index 9c89dd2..85d7273 100644 --- a/webapp/tailwind.config.ts +++ b/webapp/tailwind.config.ts @@ -43,16 +43,10 @@ export default { ], // Corps principal : Arial - "a4t-body-primary": [ - "Arial", - ...defaultTheme.fontFamily.sans, - ], + "a4t-body-primary": ["Arial", ...defaultTheme.fontFamily.sans], // Corps secondaire : Open Sans - "a4t-body-secondary": [ - "Open Sans", - ...defaultTheme.fontFamily.sans, - ], + "a4t-body-secondary": ["Open Sans", ...defaultTheme.fontFamily.sans], }, screens: { mdl: "920px", diff --git a/webapp/tsconfig.app.json b/webapp/tsconfig.app.json index b97100a..c99cecb 100644 --- a/webapp/tsconfig.app.json +++ b/webapp/tsconfig.app.json @@ -4,15 +4,9 @@ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", "target": "ES2022", "useDefineForClassFields": true, - "lib": [ - "ES2022", - "DOM", - "DOM.Iterable" - ], + "lib": ["ES2022", "DOM", "DOM.Iterable"], "module": "ESNext", - "types": [ - "vite/client" - ], + "types": ["vite/client"], "skipLibCheck": true, /* Bundler mode */ "moduleResolution": "bundler", @@ -27,9 +21,7 @@ "noUnusedParameters": true, "erasableSyntaxOnly": true, "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true, + "noUncheckedSideEffectImports": true }, - "include": [ - "src" - ] -} \ No newline at end of file + "include": ["src"] +} diff --git a/webapp/tsconfig.json b/webapp/tsconfig.json index b649482..b5d6e68 100644 --- a/webapp/tsconfig.json +++ b/webapp/tsconfig.json @@ -11,39 +11,17 @@ "compilerOptions": { "baseUrl": ".", "paths": { - "@/*": [ - "./src/*" - ], - "@app/*": [ - "./src/app/*" - ], - "@providers": [ - "./src/app/providers/index.ts" - ], - "@components": [ - "./src/components/index.ts" - ], - "@components/*": [ - "./src/components/*" - ], - "@pages": [ - "./src/pages/index.ts" - ], - "@pages/*": [ - "./src/pages/*" - ], - "@widgets": [ - "./src/widgets/index.ts" - ], - "@widgets/*": [ - "./src/widgets/*" - ], - "@ui/*": [ - "./src/components/ui/*" - ], - "@assets/*": [ - "./src/assets/*" - ] + "@/*": ["./src/*"], + "@app/*": ["./src/app/*"], + "@providers": ["./src/app/providers/index.ts"], + "@components": ["./src/components/index.ts"], + "@components/*": ["./src/components/*"], + "@pages": ["./src/pages/index.ts"], + "@pages/*": ["./src/pages/*"], + "@widgets": ["./src/widgets/index.ts"], + "@widgets/*": ["./src/widgets/*"], + "@ui/*": ["./src/components/ui/*"], + "@assets/*": ["./src/assets/*"] } } -} \ No newline at end of file +} diff --git a/webapp/vite.config.ts b/webapp/vite.config.ts index cb3294f..8af3926 100644 --- a/webapp/vite.config.ts +++ b/webapp/vite.config.ts @@ -1,25 +1,23 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' -import tailwindcss from '@tailwindcss/vite' +import path from "path"; -import path from 'path'; +import { defineConfig } from "vite"; + +import tailwindcss from "@tailwindcss/vite"; +import react from "@vitejs/plugin-react"; // https://vite.dev/config/ export default defineConfig({ resolve: { alias: { - '@': path.resolve(__dirname, './src'), - '@app': path.resolve(__dirname, './src/app'), - "@providers": path.resolve(__dirname, './src/app/providers'), - '@components': path.resolve(__dirname, './src/components'), - '@widgets': path.resolve(__dirname, './src/widgets'), - "@assets": path.resolve(__dirname, './src/assets'), - "@pages": path.resolve(__dirname, './src/pages'), - "@ui": path.resolve(__dirname, './src/components/ui'), + "@": path.resolve(__dirname, "./src"), + "@app": path.resolve(__dirname, "./src/app"), + "@providers": path.resolve(__dirname, "./src/app/providers"), + "@components": path.resolve(__dirname, "./src/components"), + "@widgets": path.resolve(__dirname, "./src/widgets"), + "@assets": path.resolve(__dirname, "./src/assets"), + "@pages": path.resolve(__dirname, "./src/pages"), + "@ui": path.resolve(__dirname, "./src/components/ui"), }, }, - plugins: [ - react(), - tailwindcss() - ], -}) + plugins: [react(), tailwindcss()], +}); diff --git a/webapp/webappTree.md b/webapp/webappTree.md index 2c07808..79ee671 100644 --- a/webapp/webappTree.md +++ b/webapp/webappTree.md @@ -61,4 +61,4 @@ src |-- Map.tsx |-- MapSidebar.tsx `-- index.ts -``` \ No newline at end of file +```