From b399e803b3551354067934c2d9629766dd233ce4 Mon Sep 17 00:00:00 2001 From: Lino Mallevaey Date: Sun, 23 Nov 2025 16:34:11 +0100 Subject: [PATCH] Version : 2025.11.22 --- .gitignore | 182 ++ App.tsx | 67 + README.md | 368 ++++ api.ts | 152 ++ backend/.env.example | 1 + backend/cli.py | 49 + backend/database.py | 16 + backend/main.py | 250 +++ backend/models.py | 52 + backend/requirements.txt | 8 + components/AddBalanceModal.tsx | 102 ++ components/AddOperationModal.tsx | 161 ++ components/BalanceCard.tsx | 131 ++ components/ConfirmationModal.tsx | 81 + components/Dashboard.tsx | 362 ++++ components/ExportButton.tsx | 40 + components/Header.tsx | 70 + components/LoginScreen.tsx | 157 ++ components/OperationsChart.tsx | 85 + components/OperationsTable.tsx | 207 +++ components/PDFDocument.tsx | 291 ++++ index.html | 29 + index.tsx | 16 + package-lock.json | 2759 ++++++++++++++++++++++++++++++ package.json | 27 + public/abacus.svg | 15 + public/icon.png | Bin 0 -> 71257 bytes run_prod.py | 42 + tsconfig.json | 29 + types.ts | 32 + vite.config.ts | 29 + 31 files changed, 5810 insertions(+) create mode 100644 .gitignore create mode 100644 App.tsx create mode 100644 README.md create mode 100644 api.ts create mode 100644 backend/.env.example create mode 100644 backend/cli.py create mode 100644 backend/database.py create mode 100644 backend/main.py create mode 100644 backend/models.py create mode 100644 backend/requirements.txt create mode 100644 components/AddBalanceModal.tsx create mode 100644 components/AddOperationModal.tsx create mode 100644 components/BalanceCard.tsx create mode 100644 components/ConfirmationModal.tsx create mode 100644 components/Dashboard.tsx create mode 100644 components/ExportButton.tsx create mode 100644 components/Header.tsx create mode 100644 components/LoginScreen.tsx create mode 100644 components/OperationsChart.tsx create mode 100644 components/OperationsTable.tsx create mode 100644 components/PDFDocument.tsx create mode 100644 index.html create mode 100644 index.tsx create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 public/abacus.svg create mode 100644 public/icon.png create mode 100644 run_prod.py create mode 100644 tsconfig.json create mode 100644 types.ts create mode 100644 vite.config.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..657050c --- /dev/null +++ b/.gitignore @@ -0,0 +1,182 @@ +# ========================================== +# Node.js / Frontend (React + Vite) +# ========================================== + +# Dependencies +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* +.pnp.* +.yarn/* + +# Build outputs +dist/ +dist-ssr/ +build/ +.next/ +out/ + +# Development +*.local +.env.local +.env.development.local +.env.test.local +.env.production.local + +# Logs +logs/ +*.log + +# Cache +.cache/ +.vite/ +.eslintcache +.parcel-cache/ + +# ========================================== +# Python / Backend (FastAPI) +# ========================================== + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python + +# Virtual environments +venv/ +ENV/ +env/ +.venv/ + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +*.manifest +*.spec + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +.pytest_cache/ +nosetests.xml +coverage.xml +*.cover + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# Celery +celerybeat-schedule +celerybeat.pid + +# ========================================== +# Database +# ========================================== + +*.db +*.sqlite +*.sqlite3 +*.sql + +# ========================================== +# Environment Variables +# ========================================== + +.env +.env.* +!.env.example + +# ========================================== +# IDE / Editors +# ========================================== + +# VSCode +.vscode/* +!.vscode/extensions.json +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json + +# JetBrains IDEs (PyCharm, WebStorm, IntelliJ) +.idea/ +*.iml +*.iws +*.ipr + +# Sublime Text +*.sublime-project +*.sublime-workspace + +# Visual Studio +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# ========================================== +# Operating System +# ========================================== + +# macOS +.DS_Store +.AppleDouble +.LSOverride +._* + +# Windows +Thumbs.db +ehthumbs.db +Desktop.ini +$RECYCLE.BIN/ + +# Linux +*~ +.directory +.Trash-* + +# ========================================== +# Miscellaneous +# ========================================== + +# Temporary files +*.tmp +*.temp +*.swp +*.swo +*~ + +# Archives +*.zip +*.tar.gz +*.rar + +# IDE specific +.history/ diff --git a/App.tsx b/App.tsx new file mode 100644 index 0000000..cfea368 --- /dev/null +++ b/App.tsx @@ -0,0 +1,67 @@ + +import React, { useState, useEffect } from 'react'; +import { Association } from './types'; +import { api } from './api'; +import LoginScreen from './components/LoginScreen'; +import Dashboard from './components/Dashboard'; + +const App: React.FC = () => { + const [activeAssociation, setActiveAssociation] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const loadAssociation = async () => { + try { + const storedAssociationId = localStorage.getItem('abacus-active-association-id'); + if (storedAssociationId) { + const association = await api.getAssociation(storedAssociationId); + setActiveAssociation(association); + } + } catch (error) { + console.error("Failed to load association", error); + localStorage.removeItem('abacus-active-association-id'); + } finally { + setLoading(false); + } + }; + loadAssociation(); + }, []); + + const handleLogin = (association: Association) => { + localStorage.setItem('abacus-active-association-id', association.id); + setActiveAssociation(association); + }; + + const handleLogout = () => { + localStorage.removeItem('abacus-active-association-id'); + setActiveAssociation(null); + }; + + const updateAssociation = async (updatedAssociation: Association) => { + setActiveAssociation(updatedAssociation); + }; + + if (loading) { + return ( +
+
Loading Abacus...
+
+ ); + } + + return ( +
+ {activeAssociation ? ( + + ) : ( + + )} +
+ ); +}; + +export default App; diff --git a/README.md b/README.md new file mode 100644 index 0000000..53ccc80 --- /dev/null +++ b/README.md @@ -0,0 +1,368 @@ +# 🧼 Abacus + +> **Application de comptabilitĂ© simplifiĂ©e pour associations** + +Abacus est une application web moderne conçue spĂ©cifiquement pour la gestion comptable des associations. Elle offre une interface intuitive et Ă©lĂ©gante permettant de gĂ©rer facilement vos balances financiĂšres, d'enregistrer vos opĂ©rations et de visualiser vos donnĂ©es comptables en temps rĂ©el. + +--- + +## 📋 Table des matiĂšres + +- [PrĂ©sentation](#-prĂ©sentation) +- [FonctionnalitĂ©s](#-fonctionnalitĂ©s) +- [Technologies utilisĂ©es](#-technologies-utilisĂ©es) +- [Installation](#-installation) +- [Lancement de l'application](#-lancement-de-lapplication) +- [Utilisation](#-utilisation) +- [Commandes CLI](#-commandes-cli) +- [Architecture](#-architecture) +- [Licence](#-licence) + +--- + +## 🎯 PrĂ©sentation + +**Abacus** est nĂ©e du besoin de simplifier la comptabilitĂ© associative. Au lieu de jongler avec des tableurs complexes, Abacus propose une solution web tout-en-un qui centralise : + +- ✅ **La gestion de vos balances** (compte principal, caisse, Ă©pargne, etc.) +- ✅ **L'enregistrement de vos opĂ©rations** (recettes et dĂ©penses) +- ✅ **La visualisation de vos donnĂ©es** avec des graphiques interactifs +- ✅ **L'export PDF** de vos rapports financiers +- ✅ **La sĂ©curitĂ©** avec un systĂšme d'authentification utilisateur +- ✅ **Le multi-tenant** pour gĂ©rer plusieurs associations sur une mĂȘme instance + +L'application a Ă©tĂ© pensĂ©e pour ĂȘtre **minimaliste**, **rapide** et **accessible**, mĂȘme pour les utilisateurs non techniques. + +--- + +## ⚡ FonctionnalitĂ©s + +### 🏠 Dashboard interactif +- Vue d'ensemble de votre santĂ© financiĂšre +- Affichage en carrousel de toutes vos balances +- Graphiques d'Ă©volution des revenus et dĂ©penses +- Tableaux dĂ©taillĂ©s de toutes les opĂ©rations + +### 💰 Gestion des balances +- CrĂ©ation et suppression de balances multiples +- Modification du nom et du montant initial +- Suivi du solde actuel en temps rĂ©el +- Organisation par cartes visuelles + +### 📊 Gestion des opĂ©rations +- Enregistrement de recettes et dĂ©penses +- CatĂ©gorisation des opĂ©rations (salaires, achats, dons, etc.) +- Ajout de descriptions dĂ©taillĂ©es +- Menu contextuel pour Ă©diter ou supprimer +- Modal de confirmation pour les suppressions + +### 📈 Visualisations +- **Graphiques** : Évolution temporelle avec Recharts +- **Tableaux** : Liste dĂ©taillĂ©e et filtrable de toutes les opĂ©rations +- **Carrousel** : Navigation fluide entre vos diffĂ©rentes balances + +### 📄 Export PDF +- GĂ©nĂ©ration de rapports PDF professionnels +- Consolidation de toutes les opĂ©rations par pĂ©riode +- Une page par balance avec design soignĂ© +- Export direct depuis le dashboard + +### 🔐 SĂ©curitĂ© +- Authentification par utilisateur (login/password) +- Hachage sĂ©curisĂ© des mots de passe (bcrypt) +- Isolation multi-tenant des donnĂ©es +- Sessions sĂ©curisĂ©es + +--- + +## đŸ› ïž Technologies utilisĂ©es + +### **Frontend** +| Technologie | Version | Description | +|-------------|---------|-------------| +| [React](https://react.dev/) | 19.2.0 | Framework UI moderne et performant | +| [TypeScript](https://www.typescriptlang.org/) | 5.8.2 | JavaScript typĂ© pour plus de robustesse | +| [Vite](https://vitejs.dev/) | 6.2.0 | Build tool ultra-rapide | +| [Tailwind CSS](https://tailwindcss.com/) | - | Framework CSS utilitaire | +| [Recharts](https://recharts.org/) | 3.3.0 | BibliothĂšque de graphiques React | +| [React PDF](https://react-pdf.org/) | 4.3.1 | GĂ©nĂ©ration de documents PDF | +| [date-fns](https://date-fns.org/) | 4.1.0 | Manipulation de dates | + +### **Backend** +| Technologie | Description | +|-------------|-------------| +| [FastAPI](https://fastapi.tiangolo.com/) | Framework Python moderne et performant | +| [SQLModel](https://sqlmodel.tiangolo.com/) | ORM basĂ© sur SQLAlchemy et Pydantic | +| [MySQL](https://www.mysql.com/) | Base de donnĂ©es relationnelle | +| [PyMySQL](https://pymysql.readthedocs.io/) | Connecteur MySQL pour Python | +| [Uvicorn](https://www.uvicorn.org/) | Serveur ASGI haute performance | +| [Typer](https://typer.tiangolo.com/) | CLI moderne pour Python | +| [Rich](https://github.com/Textualize/rich) | Rendu de texte enrichi dans le terminal | +| [Passlib](https://passlib.readthedocs.io/) | Hachage sĂ©curisĂ© de mots de passe (bcrypt) | + +--- + +## 📩 Installation + +### PrĂ©requis + +Avant de commencer, assurez-vous d'avoir installĂ© : +- **Node.js** (v16 ou supĂ©rieur) - [TĂ©lĂ©charger](https://nodejs.org/) +- **Python** (v3.8 ou supĂ©rieur) - [TĂ©lĂ©charger](https://www.python.org/) +- **MySQL** (v5.7 ou supĂ©rieur) - [TĂ©lĂ©charger](https://www.mysql.com/) + +### 1ïžâƒŁ Cloner le projet + +```bash +git clone +cd abacus +``` + +### 2ïžâƒŁ Configuration du Backend + +#### Installer les dĂ©pendances Python + +```bash +cd backend +pip install -r requirements.txt +``` + +> **Note** : Il est recommandĂ© d'utiliser un environnement virtuel : +> ```bash +> python -m venv venv +> # Windows +> venv\Scripts\activate +> # Linux/Mac +> source venv/bin/activate +> ``` + +#### Configurer la base de donnĂ©es + +1. **CrĂ©er une base de donnĂ©es MySQL** : + ```sql + CREATE DATABASE abacus CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + ``` + +2. **Configurer les variables d'environnement** : + - Copier le fichier d'exemple : + ```bash + # Linux/Mac + cp .env.example .env + # Windows + copy .env.example .env + ``` + + - Éditer le fichier `.env` et renseigner vos informations MySQL : + ```env + DATABASE_URL=mysql+pymysql://utilisateur:motdepasse@localhost:3306/abacus + ``` + > Remplacez `utilisateur`, `motdepasse` et `localhost:3306` par vos paramĂštres MySQL. + +3. **Initialiser les tables** : + ```bash + python cli.py setup-db + ``` + +### 3ïžâƒŁ Configuration du Frontend + +#### Installer les dĂ©pendances Node.js + +Depuis la racine du projet : + +```bash +npm install +``` + +--- + +## 🚀 Lancement de l'application + +### Mode dĂ©veloppement + +Pour dĂ©velopper avec rechargement automatique, lancez le backend et le frontend dans **deux terminaux sĂ©parĂ©s** : + +#### Terminal 1 : Backend + +```bash +cd backend +python cli.py start +``` + +✅ Le backend sera accessible sur **http://localhost:8000** +- API REST : `http://localhost:8000/api` +- Documentation Swagger : `http://localhost:8000/docs` + +#### Terminal 2 : Frontend + +```bash +npm run dev +``` + +✅ L'application sera accessible sur **http://localhost:9873** + +### Mode production + +Pour lancer l'application complĂšte en production : + +```bash +python run_prod.py +``` + +✅ L'application sera accessible sur **http://0.0.0.0:9874** + +Ce script : +1. Compile le frontend React en version optimisĂ©e +2. Copie les fichiers statiques dans le dossier `backend` +3. Lance le serveur FastAPI en mode production + +--- + +## 📖 Utilisation + +### 1. PremiĂšre connexion + +1. Ouvrez votre navigateur sur `http://localhost:9873` (dev) ou `http://localhost:9874` (prod) +2. **CrĂ©ez un compte** en cliquant sur "Register" +3. Remplissez vos informations (nom d'utilisateur et mot de passe) +4. Connectez-vous avec vos identifiants + +### 2. CrĂ©er votre premiĂšre balance + +1. Sur le dashboard, cliquez sur le bouton **"+ Ajouter une balance"** +2. Remplissez les informations : + - **Nom** : ex. "Compte Principal", "Caisse", "Épargne" + - **Montant initial** : le solde de dĂ©part (peut ĂȘtre 0) +3. Validez + +### 3. Ajouter des opĂ©rations + +1. SĂ©lectionnez une balance dans le carrousel +2. Cliquez sur **"+ Ajouter une opĂ©ration"** +3. Renseignez les dĂ©tails : + - **Type** : Recette ou DĂ©pense + - **Montant** : montant de l'opĂ©ration + - **Date** : date de l'opĂ©ration + - **CatĂ©gorie** : type d'opĂ©ration (salaire, achat, don, etc.) + - **Description** : dĂ©tails complĂ©mentaires +4. Validez + +### 4. Visualiser vos donnĂ©es + +- **Carrousel** : Naviguez entre vos balances avec les flĂšches +- **Graphiques** : Consultez l'Ă©volution de vos revenus/dĂ©penses au fil du temps +- **Tableau** : Visualisez toutes les opĂ©rations en dĂ©tail, triables et filtrables + +### 5. Modifier ou supprimer + +- **OpĂ©rations** : Clic droit sur une opĂ©ration → Modifier ou Supprimer +- **Balances** : Menu contextuel sur chaque carte de balance + +### 6. Exporter en PDF + +1. Cliquez sur le bouton **"Exporter PDF"** dans l'en-tĂȘte +2. Le rapport complet sera gĂ©nĂ©rĂ© et tĂ©lĂ©chargĂ© automatiquement +3. Le PDF contient toutes vos balances et opĂ©rations avec un design professionnel + +--- + +## đŸŽ›ïž Commandes CLI + +Le backend dispose d'un outil CLI (`cli.py`) pour faciliter les tĂąches courantes : + +| Commande | Description | +|----------|-------------| +| `python cli.py start` | DĂ©marre le serveur de dĂ©veloppement FastAPI (avec rechargement automatique) | +| `python cli.py setup-db` | CrĂ©e toutes les tables nĂ©cessaires dans la base de donnĂ©es | +| `python cli.py reset-db` | ⚠ **DANGER** : Supprime et recrĂ©e toutes les tables (perte de donnĂ©es) | + +**Exemples** : + +```bash +# DĂ©marrer le serveur +python cli.py start + +# CrĂ©er les tables (premiĂšre installation) +python cli.py setup-db + +# RĂ©initialiser complĂštement la base (dĂ©veloppement uniquement) +python cli.py reset-db +``` + +--- + +## đŸ—ïž Architecture + +``` +abacus/ +├── backend/ # Backend FastAPI +│ ├── api/ # Routes API +│ ├── models/ # ModĂšles SQLModel +│ ├── database.py # Configuration DB +│ ├── cli.py # Outil CLI +│ ├── .env # Variables d'environnement +│ └── requirements.txt # DĂ©pendances Python +│ +├── components/ # Composants React +│ ├── Dashboard.tsx +│ ├── BalanceCard.tsx +│ ├── OperationsTable.tsx +│ ├── OperationsChart.tsx +│ ├── AddBalanceModal.tsx +│ ├── AddOperationModal.tsx +│ ├── ExportButton.tsx +│ ├── PDFDocument.tsx +│ └── ... +│ +├── public/ # Ressources statiques +├── App.tsx # Composant principal +├── api.ts # Client API +├── types.ts # Types TypeScript +├── index.tsx # Point d'entrĂ©e React +├── vite.config.ts # Configuration Vite +├── package.json # DĂ©pendances Node.js +└── README.md # Ce fichier +``` + +### Flux de donnĂ©es + +1. **Frontend** (React) → HTTP Request → **Backend** (FastAPI) +2. **Backend** → SQL Query → **Database** (MySQL) +3. **Database** → Data → **Backend** → JSON Response → **Frontend** + +### Multi-tenant + +Chaque utilisateur a ses propres donnĂ©es isolĂ©es. Le backend filtre automatiquement toutes les requĂȘtes en fonction de l'utilisateur connectĂ©. + +--- + +## 📄 Licence + +Ce projet est sous licence **CC BY-NC-SA 4.0** (Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les MĂȘmes Conditions). + +**Auteur** : Coodlab, Mallevaey Lino +**Version** : 2025.11.22 + +--- + +## đŸ€ Contribution + +Les contributions sont les bienvenues ! N'hĂ©sitez pas Ă  : +- Signaler des bugs +- Proposer de nouvelles fonctionnalitĂ©s +- Soumettre des pull requests + +--- + +## 📞 Support + +Pour toute question ou problĂšme : +- Ouvrez une issue sur le dĂ©pĂŽt GitHub +- Consultez la documentation Swagger : `http://localhost:8000/docs` + +--- + +
+ +**Fait avec ❀ pour simplifier la comptabilitĂ© associative** + +
diff --git a/api.ts b/api.ts new file mode 100644 index 0000000..c14d51c --- /dev/null +++ b/api.ts @@ -0,0 +1,152 @@ +import { Association, Operation, OperationType, Balance } from './types'; + +const API_URL = '/api'; + +export const api = { + async signup(name: string, password: string, balances: { name: string; amount: string }[]): Promise { + const response = await fetch(`${API_URL}/signup`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ name, password, balances }), + }); + if (!response.ok) { + const error = await response.json(); + throw new Error(error.detail || 'Signup failed'); + } + return response.json(); + }, + + async login(name: string, password: string): Promise { + const response = await fetch(`${API_URL}/login`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ name, password }), + }); + if (!response.ok) { + const error = await response.json(); + throw new Error(error.detail || 'Login failed'); + } + return response.json(); + }, + + async getAssociation(id: string): Promise { + const response = await fetch(`${API_URL}/associations/${id}`); + if (!response.ok) { + throw new Error('Failed to fetch association'); + } + const data = await response.json(); + + const mappedOperations = data.operations.map((op: any) => ({ + ...op, + balanceId: op.balance_id || op.balanceId + })); + + const mappedBalances = data.balances.map((balance: any) => ({ + ...balance, + operations: balance.operations.map((op: any) => ({ + ...op, + balanceId: op.balance_id || op.balanceId + })) + })); + + return { + ...data, + operations: mappedOperations, + balances: mappedBalances + }; + }, + + async createOperation(operation: { + name: string; + description: string; + group: string; + amount: number; + type: OperationType; + date: string; + balance_id: string; + }): Promise { + const response = await fetch(`${API_URL}/operations`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(operation), + }); + if (!response.ok) { + throw new Error('Failed to create operation'); + } + const data = await response.json(); + return { + ...data, + balanceId: data.balance_id, + }; + }, + + async updateOperation(operation: Operation): Promise { + const response = await fetch(`${API_URL}/operations/${operation.id}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + name: operation.name, + description: operation.description, + group: operation.group, + amount: operation.amount, + type: operation.type, + date: operation.date, + balance_id: operation.balanceId + }), + }); + if (!response.ok) { + throw new Error('Failed to update operation'); + } + const data = await response.json(); + return { + ...data, + balanceId: data.balance_id, + }; + }, + + async deleteOperation(id: string): Promise { + const response = await fetch(`${API_URL}/operations/${id}`, { + method: 'DELETE', + }); + if (!response.ok) { + throw new Error('Failed to delete operation'); + } + }, + + async addBalance(name: string, initialAmount: number, associationId: string): Promise { + const response = await fetch(`${API_URL}/balances_add`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ name, initialAmount, association_id: associationId }), + }); + if (!response.ok) { + throw new Error('Failed to add balance'); + } + return response.json(); + }, + + async updateBalance(balance: Balance): Promise { + const response = await fetch(`${API_URL}/balances/${balance.id}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + name: balance.name, + initialAmount: balance.initialAmount, + position: balance.position, + }), + }); + if (!response.ok) { + throw new Error('Failed to update balance'); + } + return response.json(); + }, + + async deleteBalance(balanceId: string): Promise { + const response = await fetch(`${API_URL}/balances/${balanceId}`, { + method: 'DELETE', + }); + if (!response.ok) { + throw new Error('Failed to delete balance'); + } + } +}; diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..e603683 --- /dev/null +++ b/backend/.env.example @@ -0,0 +1 @@ +DATABASE_URL=mysql+pymysql://user:password@host:port/database_name diff --git a/backend/cli.py b/backend/cli.py new file mode 100644 index 0000000..7bd7cba --- /dev/null +++ b/backend/cli.py @@ -0,0 +1,49 @@ +import typer +import uvicorn +from sqlmodel import SQLModel +from database import engine +from models import Association, Balance, Operation +from rich.console import Console +from rich.panel import Panel + +app = typer.Typer() +console = Console() + +@app.command() +def start( + host: str = "127.0.0.1", + port: int = 8000, + reload: bool = True +): + """ + Start the FastAPI server. + """ + console.print(Panel(f"Starting Abacus Backend on http://{host}:{port}", title="Abacus", style="bold green")) + uvicorn.run("main:app", host=host, port=port, reload=reload) + +@app.command() +def setup_db(): + """ + Create database tables. + """ + console.print("[bold yellow]Creating tables...[/bold yellow]") + SQLModel.metadata.create_all(engine) + console.print("[bold green]Tables created successfully.[/bold green]") + +@app.command() +def reset_db(): + """ + Drop and recreate database tables. + """ + confirm = typer.confirm("Are you sure you want to drop all tables?") + if not confirm: + console.print("[bold red]Aborted.[/bold red]") + raise typer.Abort() + + console.print("[bold red]Dropping all tables...[/bold red]") + SQLModel.metadata.drop_all(engine) + console.print("[bold green]Tables dropped.[/bold green]") + setup_db() + +if __name__ == "__main__": + app() diff --git a/backend/database.py b/backend/database.py new file mode 100644 index 0000000..e034fb1 --- /dev/null +++ b/backend/database.py @@ -0,0 +1,16 @@ +from sqlmodel import SQLModel, create_engine, Session +from dotenv import load_dotenv +import os + +load_dotenv() + +DATABASE_URL = os.getenv("DATABASE_URL") + +if not DATABASE_URL: + raise ValueError("DATABASE_URL environment variable is not set") + +engine = create_engine(DATABASE_URL, echo=True) + +def get_session(): + with Session(engine) as session: + yield session diff --git a/backend/main.py b/backend/main.py new file mode 100644 index 0000000..daa0637 --- /dev/null +++ b/backend/main.py @@ -0,0 +1,250 @@ +from fastapi import FastAPI, Depends, HTTPException, status +from fastapi.middleware.cors import CORSMiddleware +from fastapi.staticfiles import StaticFiles +from sqlmodel import Session, select +from typing import List +from datetime import datetime +import os +from passlib.context import CryptContext + +from database import get_session +from models import Association, Balance, Operation, OperationType, AssociationRead, BalanceRead + +app = FastAPI() + +pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") + +def verify_password(plain_password, hashed_password): + return pwd_context.verify(plain_password, hashed_password) + +def get_password_hash(password): + return pwd_context.hash(password) + +origins = [ + "http://localhost:5173", + "http://localhost:3000", + "http://localhost:9873", +] + +app.add_middleware( + CORSMiddleware, + allow_origins=origins, + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +from pydantic import BaseModel + +class BalanceCreate(BaseModel): + name: str + amount: str + +class SignupRequest(BaseModel): + name: str + password: str + balances: List[BalanceCreate] + +class LoginRequest(BaseModel): + name: str + password: str + +class OperationCreate(BaseModel): + name: str + description: str + group: str + amount: float + type: OperationType + date: datetime + balance_id: str + +def association_to_read(association: Association) -> AssociationRead: + all_operations = [] + balance_reads = [] + for balance in association.balances: + ops = balance.operations + all_operations.extend(ops) + balance_reads.append(BalanceRead( + id=balance.id, + name=balance.name, + initialAmount=balance.initialAmount, + position=balance.position, + operations=ops + )) + + return AssociationRead( + id=association.id, + name=association.name, + balances=balance_reads, + operations=all_operations + ) + +@app.post("/api/signup", response_model=AssociationRead) +def signup(request: SignupRequest, session: Session = Depends(get_session)): + statement = select(Association).where(Association.name == request.name) + existing = session.exec(statement).first() + if existing: + raise HTTPException(status_code=400, detail="Association already exists") + + hashed_password = get_password_hash(request.password) + association = Association(name=request.name, password=hashed_password) + session.add(association) + session.commit() + session.refresh(association) + + for b in request.balances: + balance = Balance( + name=b.name, + initialAmount=float(b.amount), + association_id=association.id, + position=0 + ) + session.add(balance) + + session.commit() + session.refresh(association) + return association_to_read(association) + +@app.post("/api/login", response_model=AssociationRead) +def login(request: LoginRequest, session: Session = Depends(get_session)): + statement = select(Association).where(Association.name == request.name) + association = session.exec(statement).first() + if not association or not verify_password(request.password, association.password): + raise HTTPException(status_code=401, detail="Invalid credentials") + + return association_to_read(association) + +@app.get("/api/associations/{association_id}", response_model=AssociationRead) +def get_association(association_id: str, session: Session = Depends(get_session)): + from sqlalchemy.orm import selectinload + statement = select(Association).where(Association.id == association_id).options( + selectinload(Association.balances).selectinload(Balance.operations) + ) + association = session.exec(statement).first() + + if not association: + raise HTTPException(status_code=404, detail="Association not found") + + return association_to_read(association) + +@app.post("/api/operations") +def create_operation(op: OperationCreate, session: Session = Depends(get_session)): + balance = session.get(Balance, op.balance_id) + if not balance: + raise HTTPException(status_code=404, detail="Balance not found") + + operation = Operation( + name=op.name, + description=op.description, + group=op.group, + amount=op.amount, + type=op.type, + date=op.date, + balance_id=op.balance_id + ) + session.add(operation) + session.commit() + session.refresh(operation) + return operation + +@app.delete("/api/operations/{operation_id}") +def delete_operation(operation_id: str, session: Session = Depends(get_session)): + operation = session.get(Operation, operation_id) + if not operation: + raise HTTPException(status_code=404, detail="Operation not found") + session.delete(operation) + session.commit() + session.commit() + return {"ok": True} + +class OperationUpdate(BaseModel): + name: str + description: str + group: str + amount: float + type: OperationType + date: datetime + balance_id: str + +@app.put("/api/operations/{operation_id}") +def update_operation(operation_id: str, op: OperationUpdate, session: Session = Depends(get_session)): + operation = session.get(Operation, operation_id) + if not operation: + raise HTTPException(status_code=404, detail="Operation not found") + + operation.name = op.name + operation.description = op.description + operation.group = op.group + operation.amount = op.amount + operation.type = op.type + operation.date = op.date + operation.balance_id = op.balance_id + + session.add(operation) + session.commit() + session.refresh(operation) + return operation + +@app.post("/api/balances") +def create_balance(balance: BalanceCreate, association_id: str, session: Session = Depends(get_session)): + pass + +class BalanceAddRequest(BaseModel): + name: str + initialAmount: float + association_id: str + +@app.post("/api/balances_add") +def add_balance(request: BalanceAddRequest, session: Session = Depends(get_session)): + statement = select(Balance).where(Balance.association_id == request.association_id).order_by(Balance.position.desc()) + last_balance = session.exec(statement).first() + new_position = (last_balance.position + 1) if last_balance else 0 + + balance = Balance( + name=request.name, + initialAmount=request.initialAmount, + association_id=request.association_id, + position=new_position + ) + session.add(balance) + session.commit() + session.refresh(balance) + return balance + +@app.delete("/api/balances/{balance_id}") +def delete_balance(balance_id: str, session: Session = Depends(get_session)): + balance = session.get(Balance, balance_id) + if not balance: + raise HTTPException(status_code=404, detail="Balance not found") + + session.delete(balance) + session.commit() + return {"ok": True} + +class BalanceUpdate(BaseModel): + name: str + initialAmount: float + position: int + +@app.put("/api/balances/{balance_id}") +def update_balance(balance_id: str, data: BalanceUpdate, session: Session = Depends(get_session)): + balance = session.get(Balance, balance_id) + if not balance: + raise HTTPException(status_code=404, detail="Balance not found") + + balance.name = data.name + balance.initialAmount = data.initialAmount + balance.position = data.position + + session.add(balance) + session.commit() + session.refresh(balance) + return balance + +static_dir = os.path.join(os.path.dirname(__file__), "static") +if os.path.exists(static_dir): + app.mount("/", StaticFiles(directory=static_dir, html=True), name="static") + +@app.get("/health") +def health_check(): + return {"status": "ok"} diff --git a/backend/models.py b/backend/models.py new file mode 100644 index 0000000..221b354 --- /dev/null +++ b/backend/models.py @@ -0,0 +1,52 @@ +from typing import List, Optional +from sqlmodel import Field, Relationship, SQLModel +from enum import Enum +import uuid +from datetime import datetime + +class OperationType(str, Enum): + INCOME = 'income' + EXPENSE = 'expense' + +class Association(SQLModel, table=True): + id: str = Field(default_factory=lambda: str(uuid.uuid4()), primary_key=True) + name: str + password: str + + balances: List["Balance"] = Relationship(back_populates="association") + +class Balance(SQLModel, table=True): + id: str = Field(default_factory=lambda: str(uuid.uuid4()), primary_key=True) + name: str + initialAmount: float + association_id: Optional[str] = Field(default=None, foreign_key="association.id") + + association: Optional[Association] = Relationship(back_populates="balances") + operations: List["Operation"] = Relationship(back_populates="balance") + position: int = Field(default=0) + +class Operation(SQLModel, table=True): + id: str = Field(default_factory=lambda: str(uuid.uuid4()), primary_key=True) + name: str + description: str + group: str + amount: float + type: OperationType + date: datetime + invoice: Optional[str] = None + balance_id: Optional[str] = Field(default=None, foreign_key="balance.id") + + balance: Optional[Balance] = Relationship(back_populates="operations") + +class BalanceRead(SQLModel): + id: str + name: str + initialAmount: float + position: int = 0 + operations: List[Operation] = [] + +class AssociationRead(SQLModel): + id: str + name: str + balances: List[BalanceRead] = [] + operations: List[Operation] = [] diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..f5db2b6 --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,8 @@ +fastapi +uvicorn +sqlmodel +pymysql +python-dotenv +typer +rich +passlib[bcrypt] diff --git a/components/AddBalanceModal.tsx b/components/AddBalanceModal.tsx new file mode 100644 index 0000000..1c500ee --- /dev/null +++ b/components/AddBalanceModal.tsx @@ -0,0 +1,102 @@ +import React, { useState } from 'react'; +import { Balance } from '../types'; + +interface AddBalanceModalProps { + isOpen: boolean; + onClose: () => void; + onAddBalance: (balance: Omit) => void; + onUpdateBalance?: (balance: Balance) => void; + balanceToEdit?: Balance | null; +} + +const AddBalanceModal: React.FC = ({ isOpen, onClose, onAddBalance, onUpdateBalance, balanceToEdit }) => { + const [name, setName] = useState(''); + const [initialAmount, setInitialAmount] = useState(''); + + React.useEffect(() => { + if (balanceToEdit) { + setName(balanceToEdit.name); + setInitialAmount(balanceToEdit.initialAmount.toString()); + } else { + setName(''); + setInitialAmount(''); + } + }, [balanceToEdit, isOpen]); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (!name || !initialAmount) { + alert('Please fill all required fields.'); + return; + } + + if (balanceToEdit && onUpdateBalance) { + onUpdateBalance({ + ...balanceToEdit, + name, + initialAmount: parseFloat(initialAmount), + }); + } else { + onAddBalance({ + name, + initialAmount: parseFloat(initialAmount), + }); + } + resetForm(); + }; + + const resetForm = () => { + setName(''); + setInitialAmount(''); + onClose(); + }; + + if (!isOpen) return null; + + return ( +
+
+
+

{balanceToEdit ? 'Edit Balance' : 'Add New Balance'}

+ +
+
+
+ + setName(e.target.value)} + className="w-full mt-1 px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-gray-800 transition" + placeholder="e.g., Main Account, Savings" + /> +
+ +
+ + setInitialAmount(e.target.value)} + className="w-full mt-1 px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-gray-800 transition" + placeholder="0.00" + /> +
+ +
+ +
+
+
+
+ ); +}; + +export default AddBalanceModal; diff --git a/components/AddOperationModal.tsx b/components/AddOperationModal.tsx new file mode 100644 index 0000000..ba82016 --- /dev/null +++ b/components/AddOperationModal.tsx @@ -0,0 +1,161 @@ + +import React, { useState, useEffect } from 'react'; +import { Balance, Operation, OperationType } from '../types'; + +interface AddOperationModalProps { + isOpen: boolean; + onClose: () => void; + onAddOperation: (operation: Omit) => void; + onUpdateOperation?: (operation: Operation) => void; + operationToEdit?: Operation | null; + balances: Balance[]; +} + +const AddOperationModal: React.FC = ({ isOpen, onClose, onAddOperation, onUpdateOperation, operationToEdit, balances }) => { + const [name, setName] = useState(''); + const [description, setDescription] = useState(''); + const [group, setGroup] = useState(''); + const [amount, setAmount] = useState(''); + const [type, setType] = useState(OperationType.EXPENSE); + const [date, setDate] = useState(new Date().toISOString().split('T')[0]); + const [balanceId, setBalanceId] = useState(''); + const [invoice, setInvoice] = useState(null); + + useEffect(() => { + if (operationToEdit) { + setName(operationToEdit.name); + setDescription(operationToEdit.description); + setGroup(operationToEdit.group); + setAmount(operationToEdit.amount.toString()); + setType(operationToEdit.type); + setDate(new Date(operationToEdit.date).toISOString().split('T')[0]); + setBalanceId(operationToEdit.balanceId); + } else if (balances.length > 0) { + setBalanceId(balances[0].id); + resetFormFields(); + } + }, [balances, operationToEdit, isOpen]); + + const resetFormFields = () => { + setName(''); + setDescription(''); + setGroup(''); + setAmount(''); + setType(OperationType.EXPENSE); + setDate(new Date().toISOString().split('T')[0]); + if (balances.length > 0) setBalanceId(balances[0].id); + setInvoice(null); + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (!name || !amount || !balanceId || !group) { + alert('Please fill all required fields.'); + return; + } + + const operationData = { + balanceId, + name, + description, + group, + amount: parseFloat(amount), + type, + date: new Date(date).toISOString(), + invoice: invoice ? invoice.name : (operationToEdit?.invoice) + }; + + if (operationToEdit && onUpdateOperation) { + onUpdateOperation({ + ...operationData, + id: operationToEdit.id + }); + } else { + onAddOperation(operationData); + } + resetForm(); + }; + + const resetForm = () => { + resetFormFields(); + onClose(); + }; + + if (!isOpen) return null; + + return ( +
+
+
+

{operationToEdit ? 'Edit Operation' : 'Add New Operation'}

+ +
+
+
+ + +
+ +
+ + +
+ +
+ + setName(e.target.value)} className="w-full mt-1 px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-gray-800 transition" /> +
+ +
+ + setGroup(e.target.value)} className="w-full mt-1 px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-gray-800 transition" /> +
+ +
+
+ + setAmount(e.target.value)} className="w-full mt-1 px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-gray-800 transition" /> +
+
+ + setDate(e.target.value)} className="w-full mt-1 px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-gray-800 transition" /> +
+
+ +
+ + +
+ + {type === OperationType.EXPENSE && ( +
+ + setInvoice(e.target.files ? e.target.files[0] : null)} className="w-full mt-1 text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-gray-100 file:text-gray-700 hover:file:bg-gray-200" /> +
+ )} + +
+ + +
+
+
+
+ ); +}; + +export default AddOperationModal; diff --git a/components/BalanceCard.tsx b/components/BalanceCard.tsx new file mode 100644 index 0000000..93b09db --- /dev/null +++ b/components/BalanceCard.tsx @@ -0,0 +1,131 @@ + +import React, { useMemo } from 'react'; +import { Balance, Operation, OperationType } from '../types'; + +interface BalanceCardProps { + balance: Balance; + operations: Operation[]; + isSelected: boolean; + onClick: () => void; + onEdit: (balance: Balance) => void; + onDelete: (balanceId: string) => void; +} + +const BalanceCard: React.FC = ({ balance, operations, isSelected, onClick, onEdit, onDelete }) => { + const { totalIncome, totalExpenses, currentBalance } = useMemo(() => { + const totalIncome = operations + .filter(op => op.type === OperationType.INCOME) + .reduce((sum, op) => sum + op.amount, 0); + + const totalExpenses = operations + .filter(op => op.type === OperationType.EXPENSE) + .reduce((sum, op) => sum + op.amount, 0); + + const currentBalance = balance.initialAmount + totalIncome - totalExpenses; + + return { totalIncome, totalExpenses, currentBalance }; + }, [balance, operations]); + + const formatCurrency = (amount: number) => { + return new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR' }).format(amount); + }; + + const [openMenu, setOpenMenu] = React.useState(false); + const menuRef = React.useRef(null); + const [menuPosition, setMenuPosition] = React.useState<{ top: number; left: number } | null>(null); + + React.useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (menuRef.current && !menuRef.current.contains(event.target as Node)) { + setOpenMenu(false); + } + }; + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); + + const toggleMenu = (e: React.MouseEvent) => { + e.stopPropagation(); + if (openMenu) { + setOpenMenu(false); + return; + } + + const rect = e.currentTarget.getBoundingClientRect(); + setMenuPosition({ + top: rect.bottom, + left: rect.right - 128, + }); + setOpenMenu(true); + }; + + return ( +
+
+

+ {balance.name} +

+ +
+ +

{formatCurrency(currentBalance)}

+
+
+

Income

+

{formatCurrency(totalIncome)}

+
+
+

Expenses

+

{formatCurrency(totalExpenses)}

+
+
+ + {openMenu && menuPosition && ( +
e.stopPropagation()} + > +
+ + +
+
+ )} +
+ ); +}; + +export default BalanceCard; diff --git a/components/ConfirmationModal.tsx b/components/ConfirmationModal.tsx new file mode 100644 index 0000000..fb6561a --- /dev/null +++ b/components/ConfirmationModal.tsx @@ -0,0 +1,81 @@ +import React from 'react'; + +interface ConfirmationModalProps { + isOpen: boolean; + onClose: () => void; + onConfirm: () => void; + title: string; + message: string; + confirmText?: string; + cancelText?: string; + isDanger?: boolean; +} + +const ConfirmationModal: React.FC = ({ + isOpen, + onClose, + onConfirm, + title, + message, + confirmText = 'Confirm', + cancelText = 'Cancel', + isDanger = false, +}) => { + if (!isOpen) return null; + + return ( +
+
+ + + + +
+
+
+
+ {isDanger ? ( + + ) : ( + + + + )} +
+
+ +
+

+ {message} +

+
+
+
+
+
+ + +
+
+
+
+ ); +}; + +export default ConfirmationModal; diff --git a/components/Dashboard.tsx b/components/Dashboard.tsx new file mode 100644 index 0000000..1e4eb40 --- /dev/null +++ b/components/Dashboard.tsx @@ -0,0 +1,362 @@ + +import React, { useState, useMemo } from 'react'; +import { Association, Balance, Operation, OperationType } from '../types'; +import { api } from '../api'; +import Header from './Header'; +import BalanceCard from './BalanceCard'; +import OperationsChart from './OperationsChart'; +import OperationsTable from './OperationsTable'; +import AddOperationModal from './AddOperationModal'; +import AddBalanceModal from './AddBalanceModal'; +import { format, subDays, startOfMonth, endOfMonth } from 'date-fns'; + +interface DashboardProps { + association: Association; + onLogout: () => void; + onUpdateAssociation: (association: Association) => void; +} + +const Dashboard: React.FC = ({ association, onLogout, onUpdateAssociation }) => { + const today = new Date(); + const [dateRange, setDateRange] = useState<{ start: Date, end: Date }>({ start: startOfMonth(today), end: endOfMonth(today) }); + const [selectedBalanceId, setSelectedBalanceId] = useState(association.balances[0]?.id ?? null); + const [isModalOpen, setIsModalOpen] = useState(false); + const [isAddBalanceModalOpen, setIsAddBalanceModalOpen] = useState(false); + + const [balanceToEdit, setBalanceToEdit] = useState(null); + const [draggedBalanceIndex, setDraggedBalanceIndex] = useState(null); + + const [operationToEdit, setOperationToEdit] = useState(null); + const scrollContainerRef = React.useRef(null); + + const scroll = (direction: 'left' | 'right') => { + if (scrollContainerRef.current) { + const scrollAmount = 320; + const newScrollLeft = scrollContainerRef.current.scrollLeft + (direction === 'right' ? scrollAmount : -scrollAmount); + scrollContainerRef.current.scrollTo({ + left: newScrollLeft, + behavior: 'smooth' + }); + } + }; + + const filteredOperations = useMemo(() => { + return association.operations.filter(op => { + const opDate = new Date(op.date); + return opDate >= dateRange.start && opDate <= dateRange.end; + }); + }, [association.operations, dateRange]); + + const selectedBalance = useMemo(() => { + return association.balances.find(b => b.id === selectedBalanceId) ?? null; + }, [association.balances, selectedBalanceId]); + + const operationsForSelectedBalance = useMemo(() => { + return filteredOperations.filter(op => op.balanceId === selectedBalanceId); + }, [filteredOperations, selectedBalanceId]); + + const incomesForSelectedBalance = operationsForSelectedBalance.filter(op => op.type === OperationType.INCOME); + const expensesForSelectedBalance = operationsForSelectedBalance.filter(op => op.type === OperationType.EXPENSE); + + const handleAddOperation = async (newOperationData: Omit) => { + try { + const newOperation = await api.createOperation({ + ...newOperationData, + balance_id: newOperationData.balanceId, + date: newOperationData.date, + }); + + const updatedAssociation = { + ...association, + operations: [...association.operations, newOperation] + }; + onUpdateAssociation(updatedAssociation); + setIsModalOpen(false); + } catch (error) { + console.error("Failed to add operation", error); + alert("Failed to add operation"); + } + }; + + const handleUpdateOperation = async (updatedOperation: Operation) => { + try { + const result = await api.updateOperation({ + ...updatedOperation, + balanceId: updatedOperation.balanceId, + }); + + const updatedAssociation = { + ...association, + operations: association.operations.map(op => op.id === result.id ? result : op) + }; + onUpdateAssociation(updatedAssociation); + setIsModalOpen(false); + setOperationToEdit(null); + } catch (error) { + console.error("Failed to update operation", error); + alert("Failed to update operation"); + } + }; + + const handleDeleteOperation = async (operationId: string) => { + if (window.confirm('Are you sure you want to delete this operation?')) { + try { + await api.deleteOperation(operationId); + const updatedAssociation = { + ...association, + operations: association.operations.filter(op => op.id !== operationId) + }; + onUpdateAssociation(updatedAssociation); + } catch (error) { + console.error("Failed to delete operation", error); + alert("Failed to delete operation"); + } + } + }; + + const handleEditOperation = (operation: Operation) => { + setOperationToEdit(operation); + setIsModalOpen(true); + }; + + const handleCloseModal = () => { + setIsModalOpen(false); + setOperationToEdit(null); + }; + + const handleAddBalance = async (newBalanceData: Omit) => { + try { + const newBalance = await api.addBalance(newBalanceData.name, newBalanceData.initialAmount, association.id); + const updatedAssociation = { + ...association, + balances: [...association.balances, newBalance] + }; + onUpdateAssociation(updatedAssociation); + setIsAddBalanceModalOpen(false); + setSelectedBalanceId(newBalance.id); + } catch (error) { + console.error("Failed to add balance", error); + alert("Failed to add balance"); + } + }; + + const handleEditBalance = (balance: Balance) => { + setBalanceToEdit(balance); + setIsAddBalanceModalOpen(true); + }; + + const handleDeleteBalance = async (balanceId: string) => { + if (window.confirm('Are you sure you want to delete this balance? All associated operations will be lost.')) { + try { + await api.deleteBalance(balanceId); + const updatedAssociation = { + ...association, + balances: association.balances.filter(b => b.id !== balanceId) + }; + onUpdateAssociation(updatedAssociation); + if (selectedBalanceId === balanceId) { + setSelectedBalanceId(updatedAssociation.balances[0]?.id ?? null); + } + } catch (error) { + console.error("Failed to delete balance", error); + alert("Failed to delete balance"); + } + } + }; + + const handleUpdateBalance = async (updatedBalance: Balance) => { + try { + const result = await api.updateBalance(updatedBalance); + const updatedAssociation = { + ...association, + balances: association.balances.map(b => b.id === result.id ? result : b) + }; + onUpdateAssociation(updatedAssociation); + setIsAddBalanceModalOpen(false); + setBalanceToEdit(null); + } catch (error) { + console.error("Failed to update balance", error); + alert("Failed to update balance"); + } + }; + + const handleDragStart = (index: number) => { + setDraggedBalanceIndex(index); + }; + + const handleDragOver = (e: React.DragEvent) => { + e.preventDefault(); + }; + + const handleDrop = async (dropIndex: number) => { + if (draggedBalanceIndex === null || draggedBalanceIndex === dropIndex) return; + + const newBalances = [...association.balances]; + const [draggedBalance] = newBalances.splice(draggedBalanceIndex, 1); + newBalances.splice(dropIndex, 0, draggedBalance); + + const updatedBalances = newBalances.map((b, index) => ({ + ...b, + position: index + })); + + onUpdateAssociation({ + ...association, + balances: updatedBalances + }); + + try { + await Promise.all(updatedBalances.map(b => api.updateBalance(b))); + } catch (error) { + console.error("Failed to update balance positions", error); + alert("Failed to save new order"); + } + setDraggedBalanceIndex(null); + }; + + return ( +
+ {/* ... Header ... */} +
+
+
+

Balances

+
+ + +
+
+ +
+ + +
+ {association.balances + .sort((a, b) => (a.position - b.position)) + .map((balance, index) => ( +
handleDragStart(index)} + onDragOver={handleDragOver} + onDrop={() => handleDrop(index)} + > + op.balanceId === balance.id)} + isSelected={selectedBalanceId === balance.id} + onClick={() => setSelectedBalanceId(balance.id)} + onEdit={handleEditBalance} + onDelete={handleDeleteBalance} + /> +
+ ))} +
+ + +
+ +
+
+

Balances Variation

+ +
+ + {selectedBalance && ( + <> +
+

+ Operations for {selectedBalance.name} +

+
+ + +
+
+ + )} +
+ +
+ + { + setIsAddBalanceModalOpen(false); + setBalanceToEdit(null); + }} + onAddBalance={handleAddBalance} + onUpdateBalance={handleUpdateBalance} + balanceToEdit={balanceToEdit} + /> +
+ ); +}; + +export default Dashboard; diff --git a/components/ExportButton.tsx b/components/ExportButton.tsx new file mode 100644 index 0000000..e116f1b --- /dev/null +++ b/components/ExportButton.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { PDFDownloadLink } from '@react-pdf/renderer'; +import PDFDocument from './PDFDocument'; +import { Operation, Balance } from '../types'; +import { format } from 'date-fns'; + +interface ExportButtonProps { + operations: Operation[]; + balances: Balance[]; + dateRange: { start: Date; end: Date }; + associationName: string; +} + +const ExportButton: React.FC = ({ operations, balances, dateRange, associationName }) => { + return ( + + } + fileName={`abacus_export_${format(dateRange.start, 'yyyy-MM-dd')}_${format(dateRange.end, 'yyyy-MM-dd')}.pdf`} + className="text-sm font-medium text-gray-600 hover:text-gray-900 transition flex items-center space-x-1" + > + {({ blob, url, loading, error }) => ( + <> + + + + {loading ? 'Loading...' : 'Export PDF'} + + )} + + ); +}; + +export default ExportButton; diff --git a/components/Header.tsx b/components/Header.tsx new file mode 100644 index 0000000..d9e32d0 --- /dev/null +++ b/components/Header.tsx @@ -0,0 +1,70 @@ + +import React from 'react'; +import { format, parseISO } from 'date-fns'; + +import { Operation, Balance } from '../types'; +import ExportButton from './ExportButton'; + +interface HeaderProps { + associationName: string; + onLogout: () => void; + dateRange: { start: Date, end: Date }; + setDateRange: (range: { start: Date, end: Date }) => void; + operations: Operation[]; + balances: Balance[]; +} + +const Header: React.FC = ({ associationName, onLogout, dateRange, setDateRange, operations, balances }) => { + + const handleDateChange = (field: 'start' | 'end', value: string) => { + const newDate = parseISO(value); + if (field === 'start' && newDate < dateRange.end) { + setDateRange({ ...dateRange, start: newDate }); + } + if (field === 'end' && newDate > dateRange.start) { + setDateRange({ ...dateRange, end: newDate }); + } + }; + + return ( +
+
+
+
+ Abacus + | +

{associationName}

+
+
+
+ handleDateChange('start', e.target.value)} + className="px-3 py-1.5 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-1 focus:ring-gray-800" + /> + - + handleDateChange('end', e.target.value)} + className="px-3 py-1.5 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-1 focus:ring-gray-800" + /> +
+ + +
+
+
+
+ ); +}; + +export default Header; diff --git a/components/LoginScreen.tsx b/components/LoginScreen.tsx new file mode 100644 index 0000000..96a8539 --- /dev/null +++ b/components/LoginScreen.tsx @@ -0,0 +1,157 @@ + +import React, { useState } from 'react'; +import { Association, Balance } from '../types'; +import { api } from '../api'; + +interface LoginScreenProps { + onLogin: (association: Association) => void; +} + +const LoginScreen: React.FC = ({ onLogin }) => { + const [isLoginView, setIsLoginView] = useState(true); + const [associationName, setAssociationName] = useState(''); + const [password, setPassword] = useState(''); + const [initialBalances, setInitialBalances] = useState<{ name: string; amount: string }[]>([{ name: '', amount: '' }]); + const [error, setError] = useState(''); + + const handleAddBalance = () => { + setInitialBalances([...initialBalances, { name: '', amount: '' }]); + }; + + const handleRemoveBalance = (index: number) => { + setInitialBalances(initialBalances.filter((_, i) => i !== index)); + }; + + const handleBalanceChange = (index: number, field: 'name' | 'amount', value: string) => { + const newBalances = [...initialBalances]; + newBalances[index][field] = value; + setInitialBalances(newBalances); + }; + + const validateSignup = () => { + if (!associationName.trim() || !password.trim()) { + setError('Association name and password are required.'); + return false; + } + if (initialBalances.some(b => !b.name.trim() || !b.amount.trim() || isNaN(parseFloat(b.amount)))) { + setError('All balance fields must be filled correctly.'); + return false; + } + return true; + }; + + const handleSignup = async () => { + setError(''); + if (!validateSignup()) return; + + try { + const newAssociation = await api.signup(associationName.trim(), password, initialBalances.map(b => ({ name: b.name.trim(), amount: b.amount }))); + onLogin(newAssociation); + } catch (err: any) { + setError(err.message || 'Signup failed'); + } + }; + + const handleLogin = async () => { + setError(''); + if (!associationName.trim() || !password.trim()) { + setError('Please enter association name and password.'); + return; + } + + try { + const association = await api.login(associationName.trim(), password); + onLogin(association); + } catch (err: any) { + setError(err.message || 'Login failed'); + } + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (isLoginView) { + handleLogin(); + } else { + handleSignup(); + } + } + + return ( +
+
+

Abacus

+

Simplified accounting for your association.

+
+

{isLoginView ? 'Login' : 'Create Account'}

+ +
+ setAssociationName(e.target.value)} + className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-gray-800 transition" + /> + setPassword(e.target.value)} + className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-gray-800 transition" + /> + + {!isLoginView && ( +
+

Initial Balances

+
+ {initialBalances.map((balance, index) => ( +
+ handleBalanceChange(index, 'name', e.target.value)} + className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm" + /> + handleBalanceChange(index, 'amount', e.target.value)} + className="w-40 px-3 py-2 border border-gray-300 rounded-lg text-sm" + /> + {initialBalances.length > 1 && ( + + )} +
+ ))} +
+ +
+ )} + + {error &&

{error}

} + + +
+ +

+ {isLoginView ? "Don't have an account?" : 'Already have an account?'} + +

+
+
+
+ ); +}; + +export default LoginScreen; diff --git a/components/OperationsChart.tsx b/components/OperationsChart.tsx new file mode 100644 index 0000000..c6fc9de --- /dev/null +++ b/components/OperationsChart.tsx @@ -0,0 +1,85 @@ + +import React, { useMemo } from 'react'; +import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; +import { Balance, Operation } from '../types'; +import { format, eachDayOfInterval, isBefore, isEqual } from 'date-fns'; + +interface OperationsChartProps { + balances: Balance[]; + allOperations: Operation[]; + dateRange: { start: Date, end: Date }; +} + +const OperationsChart: React.FC = ({ balances, allOperations, dateRange }) => { + + const chartData = useMemo(() => { + const days = eachDayOfInterval(dateRange); + + const balanceColors = ['#1f2937', '#6b7280', '#ef4444', '#10b981', '#3b82f6', '#a855f7']; + + const data = days.map(day => { + const entry: { date: string, [key: string]: number | string } = { + date: format(day, 'MMM dd'), + }; + + balances.forEach(balance => { + const balanceAtStartOfRange = balance.initialAmount + allOperations + .filter(op => op.balanceId === balance.id && isBefore(new Date(op.date), dateRange.start)) + .reduce((acc, op) => acc + (op.type === 'income' ? op.amount : -op.amount), 0); + + const dailyTotal = allOperations + .filter(op => op.balanceId === balance.id && isBefore(new Date(op.date), day) && isEqual(new Date(op.date), day)) + .reduce((acc, op) => acc + (op.type === 'income' ? op.amount : -op.amount), balanceAtStartOfRange); + + const operationsInPeriodUntilDay = allOperations + .filter(op => { + const opDate = new Date(op.date); + return op.balanceId === balance.id && + opDate >= dateRange.start && + opDate <= day; + }) + .reduce((acc, op) => acc + (op.type === 'income' ? op.amount : -op.amount), 0); + + entry[balance.name] = balanceAtStartOfRange + operationsInPeriodUntilDay; + }); + + return entry; + }); + + return { data, colors: balanceColors }; + }, [balances, allOperations, dateRange]); + + + if (!balances.length) { + return

No balances to display.

+ } + + return ( +
+ + + + + new Intl.NumberFormat('fr-FR', { notation: 'compact', compactDisplay: 'short' }).format(value as number)}/> + new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR' }).format(value as number)} + /> + + {balances.map((balance, index) => ( + + ))} + + +
+ ); +}; + +export default OperationsChart; diff --git a/components/OperationsTable.tsx b/components/OperationsTable.tsx new file mode 100644 index 0000000..9d04b73 --- /dev/null +++ b/components/OperationsTable.tsx @@ -0,0 +1,207 @@ +import React, { useMemo, useState, useRef, useEffect } from 'react'; +import { Operation } from '../types'; +import { format } from 'date-fns'; +import ConfirmationModal from './ConfirmationModal'; + +interface OperationsTableProps { + title: 'Income' | 'Expenses'; + operations: Operation[]; + onEdit: (operation: Operation) => void; + onDelete: (operationId: string) => void; +} + +const OperationsTable: React.FC = ({ title, operations, onEdit, onDelete }) => { + const [openMenuId, setOpenMenuId] = useState(null); + const menuRef = useRef(null); + const [menuPosition, setMenuPosition] = useState<{ top: number; left: number } | null>(null); + const [operationToDelete, setOperationToDelete] = useState(null); + const [collapsedGroups, setCollapsedGroups] = useState>({}); + + const toggleGroup = (group: string) => { + setCollapsedGroups(prev => ({ + ...prev, + [group]: !prev[group] + })); + }; + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (menuRef.current && !menuRef.current.contains(event.target as Node)) { + setOpenMenuId(null); + setMenuPosition(null); + } + }; + const handleScroll = () => { + if (openMenuId) { + setOpenMenuId(null); + setMenuPosition(null); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + window.addEventListener('scroll', handleScroll, true); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + window.removeEventListener('scroll', handleScroll, true); + }; + }, [openMenuId]); + + const toggleMenu = (id: string, e: React.MouseEvent) => { + e.stopPropagation(); + if (openMenuId === id) { + setOpenMenuId(null); + setMenuPosition(null); + } else { + const rect = e.currentTarget.getBoundingClientRect(); + const spaceBelow = window.innerHeight - rect.bottom; + const menuHeight = 100; + + let top = rect.bottom; + if (spaceBelow < menuHeight) { + top = rect.top - menuHeight; + } + + setMenuPosition({ + top: top, + left: rect.right - 128, + }); + setOpenMenuId(id); + } + }; + + const handleDeleteClick = (op: Operation) => { + setOperationToDelete(op); + setOpenMenuId(null); + setMenuPosition(null); + }; + + const confirmDelete = () => { + if (operationToDelete) { + onDelete(operationToDelete.id); + setOperationToDelete(null); + } + }; + + const groupedOperations = useMemo(() => { + return operations.reduce((acc, op) => { + (acc[op.group] = acc[op.group] || []).push(op); + return acc; + }, {} as Record); + }, [operations]); + + const formatCurrency = (amount: number) => { + return new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR' }).format(amount); + }; + + const titleColor = title === 'Income' ? 'text-green-600' : 'text-red-600'; + + return ( +
+

{title}

+
+ {Object.keys(groupedOperations).length > 0 ? ( + Object.keys(groupedOperations).map((group) => { + const ops = groupedOperations[group]; + return ( +
+
toggleGroup(group)} + > +
{group}
+ + + +
+ {!collapsedGroups[group] && ( +
+ {ops.map(op => ( +
+
+
+

{op.name}

+

{format(new Date(op.date), 'MMM dd, yyyy')}

+
+

{op.description}

+
+ +
+
+ {formatCurrency(op.amount)} +
+
+ +
+
+
+ ))} +
+ )} +
+ ); + }) + ) : ( +
+

No {title.toLowerCase()} for this period.

+
+ )} +
+ + {/* Fixed Menu Portal */} + {openMenuId && menuPosition && ( +
+
+ + +
+
+ )} + + setOperationToDelete(null)} + onConfirm={confirmDelete} + title="Delete Operation" + message={`Are you sure you want to delete the operation "${operationToDelete?.name}"? This action cannot be undone.`} + confirmText="Delete" + isDanger={true} + /> +
+ ); +}; + +export default OperationsTable; diff --git a/components/PDFDocument.tsx b/components/PDFDocument.tsx new file mode 100644 index 0000000..fe0c9dc --- /dev/null +++ b/components/PDFDocument.tsx @@ -0,0 +1,291 @@ +import React from 'react'; +import { Page, Text, View, Document, StyleSheet } from '@react-pdf/renderer'; +import { format } from 'date-fns'; +import { Operation, Balance, OperationType } from '../types'; + +const styles = StyleSheet.create({ + page: { + flexDirection: 'column', + backgroundColor: '#F9FAFB', + padding: 35, + fontFamily: 'Helvetica', + }, + header: { + marginBottom: 25, + paddingBottom: 15, + paddingTop: 20, + paddingLeft: 25, + paddingRight: 25, + backgroundColor: '#1F2937', + borderRadius: 8, + }, + title: { + fontSize: 28, + fontWeight: 'bold', + color: '#FFFFFF', + marginBottom: 8, + letterSpacing: 0.5, + }, + subtitle: { + fontSize: 12, + color: '#D1D5DB', + fontWeight: 'normal', + }, + balanceSection: { + marginBottom: 20, + }, + balanceTitle: { + fontSize: 18, + fontWeight: 'bold', + color: '#1F2937', + marginTop: 15, + marginBottom: 12, + paddingBottom: 8, + paddingLeft: 10, + borderLeftWidth: 4, + borderLeftColor: '#3B82F6', + backgroundColor: '#EFF6FF', + paddingTop: 8, + paddingRight: 10, + }, + table: { + display: 'flex', + width: 'auto', + borderRadius: 6, + overflow: 'hidden', + borderStyle: 'solid', + borderWidth: 1, + borderColor: '#D1D5DB', + backgroundColor: '#FFFFFF', + }, + tableRow: { + margin: 'auto', + flexDirection: 'row', + }, + tableColHeader: { + width: '25%', + borderStyle: 'solid', + borderWidth: 1, + borderLeftWidth: 0, + borderTopWidth: 0, + borderColor: '#E5E7EB', + backgroundColor: '#F3F4F6', + padding: 8, + }, + tableCol: { + width: '25%', + borderStyle: 'solid', + borderWidth: 1, + borderLeftWidth: 0, + borderTopWidth: 0, + borderColor: '#E5E7EB', + padding: 8, + backgroundColor: '#FFFFFF', + }, + tableCellHeader: { + margin: 'auto', + fontSize: 10, + fontWeight: 'bold', + color: '#1F2937', + textTransform: 'uppercase', + letterSpacing: 0.5, + }, + tableCell: { + margin: 'auto', + fontSize: 10, + color: '#4B5563', + }, + tableCellIncome: { + margin: 'auto', + fontSize: 10, + color: '#059669', + fontWeight: 'bold', + }, + tableCellExpense: { + margin: 'auto', + fontSize: 10, + color: '#DC2626', + fontWeight: 'bold', + }, + tableCellBalance: { + margin: 'auto', + fontSize: 10, + color: '#1F2937', + fontWeight: 'bold', + }, + totalRow: { + flexDirection: 'row', + marginTop: 12, + justifyContent: 'flex-end', + backgroundColor: '#EFF6FF', + padding: 10, + borderRadius: 6, + }, + totalText: { + fontSize: 13, + fontWeight: 'bold', + color: '#1F2937', + }, + emptyMessage: { + fontSize: 11, + color: '#9CA3AF', + fontStyle: 'italic', + padding: 20, + textAlign: 'center', + backgroundColor: '#F9FAFB', + borderRadius: 6, + }, + summaryCard: { + backgroundColor: '#FFFFFF', + padding: 15, + borderRadius: 8, + marginBottom: 15, + borderLeftWidth: 4, + borderLeftColor: '#3B82F6', + } +}); + +interface PDFDocumentProps { + operations: Operation[]; + balances: Balance[]; + dateRange: { start: Date; end: Date }; + associationName: string; +} + +const PDFDocument: React.FC = ({ operations, balances, dateRange, associationName }) => { + + const summaryData = balances.map(balance => { + const balanceOps = operations.filter(op => op.balanceId === balance.id); + + const previousOps = balanceOps.filter(op => new Date(op.date) < dateRange.start); + const initialIncome = previousOps.filter(op => op.type === OperationType.INCOME).reduce((sum, op) => sum + op.amount, 0); + const initialExpense = previousOps.filter(op => op.type === OperationType.EXPENSE).reduce((sum, op) => sum + op.amount, 0); + const startBalance = balance.initialAmount + initialIncome - initialExpense; + + const periodOps = balanceOps.filter(op => { + const opDate = new Date(op.date); + return opDate >= dateRange.start && opDate <= dateRange.end; + }); + + const periodIncome = periodOps.filter(op => op.type === OperationType.INCOME).reduce((sum, op) => sum + op.amount, 0); + const periodExpense = periodOps.filter(op => op.type === OperationType.EXPENSE).reduce((sum, op) => sum + op.amount, 0); + const endBalance = startBalance + periodIncome - periodExpense; + + return { + balance, + startBalance, + endBalance, + periodIncome, + periodExpense, + periodOps: periodOps.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()) + }; + }); + + return ( + + {/* Summary Page */} + + + {associationName} + + Report Period: {format(dateRange.start, 'MMM dd, yyyy')} - {format(dateRange.end, 'MMM dd, yyyy')} + + + + Summary + + + + Balance + + + Start + + + Income + + + Expense + + + End + + + {summaryData.map((data) => ( + + + {data.balance.name} + + + {data.startBalance.toFixed(2)} € + + + +{data.periodIncome.toFixed(2)} € + + + -{data.periodExpense.toFixed(2)} € + + + {data.endBalance.toFixed(2)} € + + + ))} + + + + {/* Detailed Pages */} + {summaryData.map((data) => ( + + + {data.balance.name} - Details + + {data.periodOps.length > 0 ? ( + + + + Date + + + Name + + + Type + + + Amount + + + {data.periodOps.map((op) => ( + + + {format(new Date(op.date), 'MMM dd, yyyy')} + + + {op.name} + + + {op.type} + + + + {op.type === OperationType.EXPENSE ? '-' : '+'}{op.amount.toFixed(2)} € + + + + ))} + + ) : ( + No operations for this period. + )} + + + End Balance: {data.endBalance.toFixed(2)} € + + + + ))} + + ); +}; + +export default PDFDocument; diff --git a/index.html b/index.html new file mode 100644 index 0000000..47f4bd9 --- /dev/null +++ b/index.html @@ -0,0 +1,29 @@ + + + + + + + + Abacus - Coodlab + + + + + + +
+ + + + \ No newline at end of file diff --git a/index.tsx b/index.tsx new file mode 100644 index 0000000..aaa0c6e --- /dev/null +++ b/index.tsx @@ -0,0 +1,16 @@ + +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; + +const rootElement = document.getElementById('root'); +if (!rootElement) { + throw new Error("Could not find root element to mount to"); +} + +const root = ReactDOM.createRoot(rootElement); +root.render( + + + +); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..8017588 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2759 @@ +{ + "name": "abacus", + "version": "2025.11.22", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "abacus", + "version": "2025.11.22", + "dependencies": { + "@react-pdf/renderer": "^4.3.1", + "date-fns": "^4.1.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "recharts": "^3.3.0" + }, + "devDependencies": { + "@types/node": "^22.14.0", + "@vitejs/plugin-react": "^5.0.0", + "typescript": "~5.8.2", + "vite": "^6.2.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@react-pdf/fns": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@react-pdf/fns/-/fns-3.1.2.tgz", + "integrity": "sha512-qTKGUf0iAMGg2+OsUcp9ffKnKi41RukM/zYIWMDJ4hRVYSr89Q7e3wSDW/Koqx3ea3Uy/z3h2y3wPX6Bdfxk6g==", + "license": "MIT" + }, + "node_modules/@react-pdf/font": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@react-pdf/font/-/font-4.0.3.tgz", + "integrity": "sha512-N1qQDZr6phXYQOp033Hvm2nkUkx2LkszjGPbmRavs9VOYzi4sp31MaccMKptL24ii6UhBh/z9yPUhnuNe/qHwA==", + "license": "MIT", + "dependencies": { + "@react-pdf/pdfkit": "^4.0.4", + "@react-pdf/types": "^2.9.1", + "fontkit": "^2.0.2", + "is-url": "^1.2.4" + } + }, + "node_modules/@react-pdf/image": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@react-pdf/image/-/image-3.0.3.tgz", + "integrity": "sha512-lvP5ryzYM3wpbO9bvqLZYwEr5XBDX9jcaRICvtnoRqdJOo7PRrMnmB4MMScyb+Xw10mGeIubZAAomNAG5ONQZQ==", + "license": "MIT", + "dependencies": { + "@react-pdf/png-js": "^3.0.0", + "jay-peg": "^1.1.1" + } + }, + "node_modules/@react-pdf/layout": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@react-pdf/layout/-/layout-4.4.1.tgz", + "integrity": "sha512-GVzdlWoZWldRDzlWj3SttRXmVDxg7YfraAohwy+o9gb9hrbDJaaAV6jV3pc630Evd3K46OAzk8EFu8EgPDuVuA==", + "license": "MIT", + "dependencies": { + "@react-pdf/fns": "3.1.2", + "@react-pdf/image": "^3.0.3", + "@react-pdf/primitives": "^4.1.1", + "@react-pdf/stylesheet": "^6.1.1", + "@react-pdf/textkit": "^6.0.0", + "@react-pdf/types": "^2.9.1", + "emoji-regex-xs": "^1.0.0", + "queue": "^6.0.1", + "yoga-layout": "^3.2.1" + } + }, + "node_modules/@react-pdf/pdfkit": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@react-pdf/pdfkit/-/pdfkit-4.0.4.tgz", + "integrity": "sha512-/nITLggsPlB66bVLnm0X7MNdKQxXelLGZG6zB5acF5cCgkFwmXHnLNyxYOUD4GMOMg1HOPShXDKWrwk2ZeHsvw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.13", + "@react-pdf/png-js": "^3.0.0", + "browserify-zlib": "^0.2.0", + "crypto-js": "^4.2.0", + "fontkit": "^2.0.2", + "jay-peg": "^1.1.1", + "linebreak": "^1.1.0", + "vite-compatible-readable-stream": "^3.6.1" + } + }, + "node_modules/@react-pdf/png-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@react-pdf/png-js/-/png-js-3.0.0.tgz", + "integrity": "sha512-eSJnEItZ37WPt6Qv5pncQDxLJRK15eaRwPT+gZoujP548CodenOVp49GST8XJvKMFt9YqIBzGBV/j9AgrOQzVA==", + "license": "MIT", + "dependencies": { + "browserify-zlib": "^0.2.0" + } + }, + "node_modules/@react-pdf/primitives": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@react-pdf/primitives/-/primitives-4.1.1.tgz", + "integrity": "sha512-IuhxYls1luJb7NUWy6q5avb1XrNaVj9bTNI40U9qGRuS6n7Hje/8H8Qi99Z9UKFV74bBP3DOf3L1wV2qZVgVrQ==", + "license": "MIT" + }, + "node_modules/@react-pdf/reconciler": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@react-pdf/reconciler/-/reconciler-1.1.4.tgz", + "integrity": "sha512-oTQDiR/t4Z/Guxac88IavpU2UgN7eR0RMI9DRKvKnvPz2DUasGjXfChAdMqDNmJJxxV26mMy9xQOUV2UU5/okg==", + "license": "MIT", + "dependencies": { + "object-assign": "^4.1.1", + "scheduler": "0.25.0-rc-603e6108-20241029" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@react-pdf/reconciler/node_modules/scheduler": { + "version": "0.25.0-rc-603e6108-20241029", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0-rc-603e6108-20241029.tgz", + "integrity": "sha512-pFwF6H1XrSdYYNLfOcGlM28/j8CGLu8IvdrxqhjWULe2bPcKiKW4CV+OWqR/9fT52mywx65l7ysNkjLKBda7eA==", + "license": "MIT" + }, + "node_modules/@react-pdf/render": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@react-pdf/render/-/render-4.3.1.tgz", + "integrity": "sha512-v1WAaAhQShQZGcBxfjkEThGCHVH9CSuitrZ1bIOLvB5iBKM14abYK5D6djKhWCwF6FTzYeT2WRjRMVgze/ND2A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.13", + "@react-pdf/fns": "3.1.2", + "@react-pdf/primitives": "^4.1.1", + "@react-pdf/textkit": "^6.0.0", + "@react-pdf/types": "^2.9.1", + "abs-svg-path": "^0.1.1", + "color-string": "^1.9.1", + "normalize-svg-path": "^1.1.0", + "parse-svg-path": "^0.1.2", + "svg-arc-to-cubic-bezier": "^3.2.0" + } + }, + "node_modules/@react-pdf/renderer": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@react-pdf/renderer/-/renderer-4.3.1.tgz", + "integrity": "sha512-dPKHiwGTaOsKqNWCHPYYrx8CDfAGsUnV4tvRsEu0VPGxuot1AOq/M+YgfN/Pb+MeXCTe2/lv6NvA8haUtj3tsA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.13", + "@react-pdf/fns": "3.1.2", + "@react-pdf/font": "^4.0.3", + "@react-pdf/layout": "^4.4.1", + "@react-pdf/pdfkit": "^4.0.4", + "@react-pdf/primitives": "^4.1.1", + "@react-pdf/reconciler": "^1.1.4", + "@react-pdf/render": "^4.3.1", + "@react-pdf/types": "^2.9.1", + "events": "^3.3.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "queue": "^6.0.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@react-pdf/stylesheet": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@react-pdf/stylesheet/-/stylesheet-6.1.1.tgz", + "integrity": "sha512-Iyw0A3wRIeQLN4EkaKf8yF9MvdMxiZ8JjoyzLzDHSxnKYoOA4UGu84veCb8dT9N8MxY5x7a0BUv/avTe586Plg==", + "license": "MIT", + "dependencies": { + "@react-pdf/fns": "3.1.2", + "@react-pdf/types": "^2.9.1", + "color-string": "^1.9.1", + "hsl-to-hex": "^1.0.0", + "media-engine": "^1.0.3", + "postcss-value-parser": "^4.1.0" + } + }, + "node_modules/@react-pdf/textkit": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@react-pdf/textkit/-/textkit-6.0.0.tgz", + "integrity": "sha512-fDt19KWaJRK/n2AaFoVm31hgGmpygmTV7LsHGJNGZkgzXcFyLsx+XUl63DTDPH3iqxj3xUX128t104GtOz8tTw==", + "license": "MIT", + "dependencies": { + "@react-pdf/fns": "3.1.2", + "bidi-js": "^1.0.2", + "hyphen": "^1.6.4", + "unicode-properties": "^1.4.1" + } + }, + "node_modules/@react-pdf/types": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@react-pdf/types/-/types-2.9.1.tgz", + "integrity": "sha512-5GoCgG0G5NMgpPuHbKG2xcVRQt7+E5pg3IyzVIIozKG3nLcnsXW4zy25vG1ZBQA0jmo39q34au/sOnL/0d1A4w==", + "license": "MIT", + "dependencies": { + "@react-pdf/font": "^4.0.3", + "@react-pdf/primitives": "^4.1.1", + "@react-pdf/stylesheet": "^6.1.1" + } + }, + "node_modules/@reduxjs/toolkit": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.10.1.tgz", + "integrity": "sha512-/U17EXQ9Do9Yx4DlNGU6eVNfZvFJfYpUtRRdLf19PbPjdWBxNlxGZXywQZ1p1Nz8nMkWplTI7iD/23m07nolDA==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^10.2.0", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.47.tgz", + "integrity": "sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", + "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", + "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", + "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", + "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", + "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", + "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", + "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", + "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", + "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", + "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", + "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", + "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", + "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", + "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", + "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", + "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", + "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", + "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", + "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", + "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", + "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", + "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, + "node_modules/@swc/helpers": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", + "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", + "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.1.tgz", + "integrity": "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.1.tgz", + "integrity": "sha512-WQfkSw0QbQ5aJ2CHYw23ZGkqnRwqKHD/KYsMeTkZzPT4Jcf0DcBxBtwMJxnu6E7oxw5+JC6ZAiePgh28uJ1HBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.5", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.47", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/abs-svg-path": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/abs-svg-path/-/abs-svg-path-0.1.1.tgz", + "integrity": "sha512-d8XPSGjfyzlXC3Xx891DJRyZfqk5JU0BJrDQcsWomFIV1/BIzPW5HDH5iDdWpqWaav0YVIEzT1RHTwWr0FFshA==", + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.30", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.30.tgz", + "integrity": "sha512-aTUKW4ptQhS64+v2d6IkPzymEzzhw+G0bA1g3uBRV3+ntkH+svttKseW5IOR4Ed6NUVKqnY7qT3dKvzQ7io4AA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "node_modules/brotli": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", + "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.1.2" + } + }, + "node_modules/browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "license": "MIT", + "dependencies": { + "pako": "~1.0.5" + } + }, + "node_modules/browserslist": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", + "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.8.25", + "caniuse-lite": "^1.0.30001754", + "electron-to-chromium": "^1.5.249", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.1.4" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001756", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001756.tgz", + "integrity": "sha512-4HnCNKbMLkLdhJz3TToeVWHSnfJvPaq6vu/eRP0Ahub/07n484XHhBF5AJoSGHdVrS8tKFauUQz8Bp9P7LVx7A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, + "node_modules/dfa": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz", + "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.259", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.259.tgz", + "integrity": "sha512-I+oLXgpEJzD6Cwuwt1gYjxsDmu/S/Kd41mmLA3O+/uH2pFRO/DvOjUyGozL8j3KeLV6WyZ7ssPwELMsXCcsJAQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex-xs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz", + "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==", + "license": "MIT" + }, + "node_modules/es-toolkit": { + "version": "1.42.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.42.0.tgz", + "integrity": "sha512-SLHIyY7VfDJBM8clz4+T2oquwTQxEzu263AyhVK4jREOAwJ+8eebaa4wM3nlvnAqhDrMm2EsA6hWHaQsMPQ1nA==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fontkit": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-2.0.4.tgz", + "integrity": "sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==", + "license": "MIT", + "dependencies": { + "@swc/helpers": "^0.5.12", + "brotli": "^1.3.2", + "clone": "^2.1.2", + "dfa": "^1.2.0", + "fast-deep-equal": "^3.1.3", + "restructure": "^3.0.0", + "tiny-inflate": "^1.0.3", + "unicode-properties": "^1.4.0", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/hsl-to-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hsl-to-hex/-/hsl-to-hex-1.0.0.tgz", + "integrity": "sha512-K6GVpucS5wFf44X0h2bLVRDsycgJmf9FF2elg+CrqD8GcFU8c6vYhgXn8NjUkFCwj+xDFb70qgLbTUm6sxwPmA==", + "license": "MIT", + "dependencies": { + "hsl-to-rgb-for-reals": "^1.1.0" + } + }, + "node_modules/hsl-to-rgb-for-reals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/hsl-to-rgb-for-reals/-/hsl-to-rgb-for-reals-1.1.1.tgz", + "integrity": "sha512-LgOWAkrN0rFaQpfdWBQlv/VhkOxb5AsBjk6NQVx4yEzWS923T07X0M1Y0VNko2H52HeSpZrZNNMJ0aFqsdVzQg==", + "license": "ISC" + }, + "node_modules/hyphen": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/hyphen/-/hyphen-1.10.6.tgz", + "integrity": "sha512-fXHXcGFTXOvZTSkPJuGOQf5Lv5T/R2itiiCVPg9LxAje5D00O0pP83yJShFq5V89Ly//Gt6acj7z8pbBr34stw==", + "license": "ISC" + }, + "node_modules/immer": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", + "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/is-arrayish": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", + "license": "MIT" + }, + "node_modules/is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", + "license": "MIT" + }, + "node_modules/jay-peg": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/jay-peg/-/jay-peg-1.1.1.tgz", + "integrity": "sha512-D62KEuBxz/ip2gQKOEhk/mx14o7eiFRaU+VNNSP4MOiIkwb/D6B3G1Mfas7C/Fit8EsSV2/IWjZElx/Gs6A4ww==", + "license": "MIT", + "dependencies": { + "restructure": "^3.0.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/linebreak": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz", + "integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==", + "license": "MIT", + "dependencies": { + "base64-js": "0.0.8", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/linebreak/node_modules/base64-js": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz", + "integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/media-engine": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/media-engine/-/media-engine-1.0.3.tgz", + "integrity": "sha512-aa5tG6sDoK+k70B9iEX1NeyfT8ObCKhNDs6lJVpwF6r8vhUfuKMslIcirq6HIUYuuUYLefcEQOn9bSBOvawtwg==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-svg-path": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/normalize-svg-path/-/normalize-svg-path-1.1.0.tgz", + "integrity": "sha512-r9KHKG2UUeB5LoTouwDzBy2VxXlHsiM6fyLQvnJa0S5hrhzqElH/CH7TUGhT1fVvIYBIKf3OpY4YJ4CK+iaqHg==", + "license": "MIT", + "dependencies": { + "svg-arc-to-cubic-bezier": "^3.0.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/parse-svg-path": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/parse-svg-path/-/parse-svg-path-0.1.2.tgz", + "integrity": "sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/queue": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", + "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", + "license": "MIT", + "dependencies": { + "inherits": "~2.0.3" + } + }, + "node_modules/react": { + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", + "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", + "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.0" + } + }, + "node_modules/react-is": { + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.0.tgz", + "integrity": "sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA==", + "license": "MIT", + "peer": true + }, + "node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/recharts": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.4.1.tgz", + "integrity": "sha512-35kYg6JoOgwq8sE4rhYkVWwa6aAIgOtT+Ob0gitnShjwUwZmhrmy7Jco/5kJNF4PnLXgt9Hwq+geEMS+WrjU1g==", + "license": "MIT", + "workspaces": [ + "www" + ], + "dependencies": { + "@reduxjs/toolkit": "1.x.x || 2.x.x", + "clsx": "^2.1.1", + "decimal.js-light": "^2.5.1", + "es-toolkit": "^1.39.3", + "eventemitter3": "^5.0.1", + "immer": "^10.1.1", + "react-redux": "8.x.x || 9.x.x", + "reselect": "5.1.1", + "tiny-invariant": "^1.3.3", + "use-sync-external-store": "^1.2.2", + "victory-vendor": "^37.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, + "node_modules/restructure": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/restructure/-/restructure-3.0.2.tgz", + "integrity": "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==", + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", + "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.53.3", + "@rollup/rollup-android-arm64": "4.53.3", + "@rollup/rollup-darwin-arm64": "4.53.3", + "@rollup/rollup-darwin-x64": "4.53.3", + "@rollup/rollup-freebsd-arm64": "4.53.3", + "@rollup/rollup-freebsd-x64": "4.53.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", + "@rollup/rollup-linux-arm-musleabihf": "4.53.3", + "@rollup/rollup-linux-arm64-gnu": "4.53.3", + "@rollup/rollup-linux-arm64-musl": "4.53.3", + "@rollup/rollup-linux-loong64-gnu": "4.53.3", + "@rollup/rollup-linux-ppc64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-musl": "4.53.3", + "@rollup/rollup-linux-s390x-gnu": "4.53.3", + "@rollup/rollup-linux-x64-gnu": "4.53.3", + "@rollup/rollup-linux-x64-musl": "4.53.3", + "@rollup/rollup-openharmony-arm64": "4.53.3", + "@rollup/rollup-win32-arm64-msvc": "4.53.3", + "@rollup/rollup-win32-ia32-msvc": "4.53.3", + "@rollup/rollup-win32-x64-gnu": "4.53.3", + "@rollup/rollup-win32-x64-msvc": "4.53.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/svg-arc-to-cubic-bezier": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/svg-arc-to-cubic-bezier/-/svg-arc-to-cubic-bezier-3.2.0.tgz", + "integrity": "sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g==", + "license": "ISC" + }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "license": "MIT" + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicode-properties": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz", + "integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.0", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/unicode-trie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", + "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", + "license": "MIT", + "dependencies": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + } + }, + "node_modules/unicode-trie/node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", + "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/victory-vendor": { + "version": "37.3.6", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", + "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, + "node_modules/vite": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-compatible-readable-stream": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/vite-compatible-readable-stream/-/vite-compatible-readable-stream-3.6.1.tgz", + "integrity": "sha512-t20zYkrSf868+j/p31cRIGN28Phrjm3nRSLR2fyc2tiWi4cZGVdv68yNlwnIINTkMTmPoMiSlc0OadaO7DXZaQ==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yoga-layout": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", + "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==", + "license": "MIT" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..c783690 --- /dev/null +++ b/package.json @@ -0,0 +1,27 @@ +{ + "name": "abacus", + "private": false, + "version": "2025.11.22", + "description": "Abacus is a web application for association accounting.", + "author": "Coodlab, Mallevaey Lino", + "license": "CC BY-NC-SA 4.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@react-pdf/renderer": "^4.3.1", + "date-fns": "^4.1.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "recharts": "^3.3.0" + }, + "devDependencies": { + "@types/node": "^22.14.0", + "@vitejs/plugin-react": "^5.0.0", + "typescript": "~5.8.2", + "vite": "^6.2.0" + } +} \ No newline at end of file diff --git a/public/abacus.svg b/public/abacus.svg new file mode 100644 index 0000000..6b93224 --- /dev/null +++ b/public/abacus.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/public/icon.png b/public/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..19b958452e5450714e9d6bfb334572272c0eb962 GIT binary patch literal 71257 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%0Dnb{dhA=^R6;c51eDy>}CsGBus!p}MM6 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 literal 0 HcmV?d00001 diff --git a/run_prod.py b/run_prod.py new file mode 100644 index 0000000..8cb371b --- /dev/null +++ b/run_prod.py @@ -0,0 +1,42 @@ +import subprocess +import shutil +import os +import sys + +def run_command(command, cwd=None): + print(f"Running: {command}") + result = subprocess.run(command, shell=True, cwd=cwd) + if result.returncode != 0: + print(f"Error running command: {command}") + sys.exit(1) + +def main(): + root_dir = os.path.dirname(os.path.abspath(__file__)) + backend_dir = os.path.join(root_dir, "backend") + static_dir = os.path.join(backend_dir, "static") + + print("Building Frontend...") + run_command("npm run build", cwd=root_dir) + + print("Copying build artifacts to backend/static...") + if os.path.exists(static_dir): + shutil.rmtree(static_dir) + + dist_dir = os.path.join(root_dir, "dist") + if not os.path.exists(dist_dir): + print("Error: dist directory not found. Build failed?") + sys.exit(1) + + shutil.copytree(dist_dir, static_dir) + print("Artifacts copied.") + + print("Starting Production Server...") + os.environ["PYTHONPATH"] = backend_dir + run_command("python cli.py start --host 0.0.0.0 --port 9874 --no-reload", cwd=backend_dir) + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + print("\nShutting down...") + sys.exit(0) diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..2c6eed5 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "target": "ES2022", + "experimentalDecorators": true, + "useDefineForClassFields": false, + "module": "ESNext", + "lib": [ + "ES2022", + "DOM", + "DOM.Iterable" + ], + "skipLibCheck": true, + "types": [ + "node" + ], + "moduleResolution": "bundler", + "isolatedModules": true, + "moduleDetection": "force", + "allowJs": true, + "jsx": "react-jsx", + "paths": { + "@/*": [ + "./*" + ] + }, + "allowImportingTsExtensions": true, + "noEmit": true + } +} \ No newline at end of file diff --git a/types.ts b/types.ts new file mode 100644 index 0000000..1171cbb --- /dev/null +++ b/types.ts @@ -0,0 +1,32 @@ + +export enum OperationType { + INCOME = 'income', + EXPENSE = 'expense', +} + +export interface Operation { + id: string; + balanceId: string; + name: string; + description: string; + group: string; + amount: number; + type: OperationType; + date: string; + invoice?: string; +} + +export interface Balance { + id: string; + name: string; + initialAmount: number; + position: number; +} + +export interface Association { + id: string; + name: string; + password: string; + balances: Balance[]; + operations: Operation[]; +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..f7a49f9 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,29 @@ +import path from 'path'; +import { defineConfig, loadEnv } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig(({ mode }) => { + const env = loadEnv(mode, '.', ''); + return { + server: { + port: 9873, + host: '0.0.0.0', + proxy: { + '/api': { + target: 'http://localhost:8000', + changeOrigin: true, + } + } + }, + plugins: [react()], + define: { + 'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY), + 'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY) + }, + resolve: { + alias: { + '@': path.resolve(__dirname, '.'), + } + } + }; +});