From f22a25ce468bd397ef5b1d58d3b3287ffcf2c99d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=BCller?= Date: Tue, 28 Apr 2026 10:29:02 +0200 Subject: [PATCH] Initial commit --- .dockerignore | 8 + .gitignore | 4 + README.md | 79 +- check-ui.js | 26 + index.html | 13 + logo.png | Bin 0 -> 76732 bytes package-lock.json | 1771 +++++++++++++++++++++++++++++++++ package.json | 23 + public/.gitkeep | 1 + public/favicon.png | Bin 0 -> 6305 bytes public/logo.png | Bin 0 -> 76732 bytes src/App.tsx | 37 + src/api/baserow.ts | 104 ++ src/components/GanttChart.tsx | 457 +++++++++ src/components/Sidebar.tsx | 76 ++ src/index.css | 783 +++++++++++++++ src/main.tsx | 13 + src/pages/Processes.tsx | 307 ++++++ src/pages/Servers.tsx | 105 ++ src/pages/Timeline.tsx | 84 ++ src/vite-env.d.ts | 1 + start.bat | 5 + test-browser.js | 10 + test-time.js | 9 + tsconfig.json | 21 + vite.config.ts | 15 + 26 files changed, 3951 insertions(+), 1 deletion(-) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 check-ui.js create mode 100644 index.html create mode 100644 logo.png create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 public/.gitkeep create mode 100644 public/favicon.png create mode 100644 public/logo.png create mode 100644 src/App.tsx create mode 100644 src/api/baserow.ts create mode 100644 src/components/GanttChart.tsx create mode 100644 src/components/Sidebar.tsx create mode 100644 src/index.css create mode 100644 src/main.tsx create mode 100644 src/pages/Processes.tsx create mode 100644 src/pages/Servers.tsx create mode 100644 src/pages/Timeline.tsx create mode 100644 src/vite-env.d.ts create mode 100644 start.bat create mode 100644 test-browser.js create mode 100644 test-time.js create mode 100644 tsconfig.json create mode 100644 vite.config.ts diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..6217a27 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +Dockerfile +.dockerignore +node_modules +npm-debug.log +README.md +.next +.git +.env diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..01473fa --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules +dist +.env +*.local diff --git a/README.md b/README.md index 858278d..f465737 100644 --- a/README.md +++ b/README.md @@ -1 +1,78 @@ -AZion +# AZion Scheduler – High-Performance Process Dashboard + +AZion is a specialized Vite + React SPA designed for high-performance visualization and management of server-side processes, using **Baserow** as a headless backend. + +## πŸš€ Quick Start + +1. **Install dependencies**: `npm install` +2. **Setup environment**: Rename `.env.example` to `.env` and provide your Baserow API details. +3. **Run Dev server**: `npm run dev` + +--- + +## πŸ›  Baserow & n8n Integration Guide + +This section outlines the exact internal logic the AZion dashboard uses to parse scheduling parameters. When inserting or updating records via an automation runtime like **n8n**, your JSON payload sent to the Baserow API must structurally respect these dependencies. + +### 1. Global / Universal Fields +These fields are required or heavily impact the UI regardless of the execution type. + +- **`Name`** *(Single-line text)*: The display name of the process. +- **`Server`** *(Linked row)*: Reference ID linking back to the Server Table. n8n payload syntax: `[123]` (Array of IDs). +- **`Sortierung`** *(Number)*: The rendering order integer. `1` is pushed to the top of the timeline track. +- **`Dauer`** *(Single-line text or Number)*: **Always specified in SECONDS**. Defines the length of the visual block. *(e.g., passing `"1800"` renders a 30-minute block).* +- **`Farbe`** *(Single-line text)*: The hex code for the visual block. *(e.g., `"#3b82f6"`).* +- **`Status`** *(Single select)*: E.g., `aktiv`, `inaktiv`, `fehler`. +- **`Sichtbarkeit`** *(Single select)*: Declares semantic zoom constraints (`Immer`, `Max 6 Stunden`, `Max 1 Tag`, `Max 1 Woche`, `Max 1 Monat`). + +### 2. Dynamic Scheduling via `Wiederholung` +The logic engine evaluates time solely based on what string is stored inside the **`Wiederholung`** *(Single select)* column. + +#### Pattern A: Standard Execution +**Applies to**: `TAEGLICH`, `WOECHENTLICH`, `WERKTAGE`, `WOCHENENDE`, `MONATLICH_FESTER_TAG` +These patterns expect a singular, definitive time of day. + +* **n8n Setup**: + * `Wiederholung`: `"TAEGLICH"` + * `Start`: `"HH:MM:SS"` *(e.g., `"08:30:00"`).* + * `Dauer`: `"60"` *(60 seconds).* + +#### Pattern B: Hourly Execution (`STUENDLICH`) +* **n8n Setup**: + * `Wiederholung`: `"STUENDLICH"` + * `Start Minute`: `45` *(Integer 0–59).* + * `Dauer`: `"120"` *(2 minutes).* + +#### Pattern C: Custom Interval (`INTERVALL`) +Recurrent blocks bounded between two timestamps. + +* **n8n Setup**: + * `Wiederholung`: `"INTERVALL"` + * `Erste AusfΓΌhrung`: `"HH:MM:SS"` *(Start bounds).* + * `AusfΓΌhrung bis`: `"HH:MM:SS"` *(End bounds).* + * `Intervall zwischen`: `"3600"` *(Spacing in SECONDS).* + * `Dauer`: `"300"` *(Duration in SECONDS).* + +--- + +### 3. Example JSON Payload (n8n / API) + +```json +{ + "Name": "API Heartbeat Checker", + "Server": [45], + "Wiederholung": "INTERVALL", + "Erste AusfΓΌhrung": "08:00:00", + "AusfΓΌhrung bis": "17:00:00", + "Intervall zwischen": "300", + "Dauer": "15", + "Status": "aktiv", + "Farbe": "#22c55e", + "Sortierung": 8 +} +``` + +### 🧠 Performance & Logic Notes +- **Block Merging**: The Gantt Chart automatically merges overlapping blocks from the same process to prevent DOM flooding when zoomed out. +- **Max View Filter**: High-frequency tasks (like 1-min intervals) are hidden in Monthly/Weekly views unless `Sichtbarkeit` is set to "Immer" to maintain UI responsiveness. +- **Auto-Sort**: The frontend automatically finds the next highest `Sortierung` index when creating new tasks via the GUI. diff --git a/check-ui.js b/check-ui.js new file mode 100644 index 0000000..15a4ce1 --- /dev/null +++ b/check-ui.js @@ -0,0 +1,26 @@ +const puppeteer = require('puppeteer'); +(async () => { + const browser = await puppeteer.launch(); + const page = await browser.newPage(); + + page.on('pageerror', err => console.log('Runtime Error:', err.message)); + page.on('console', msg => console.log('Console:', msg.text())); + + await page.goto('http://localhost:3001'); + await page.waitForTimeout(2000); + + // Evaluate if Gantt DOM structural integrity exists + const ganttMetrics = await page.evaluate(() => { + const blocks = document.querySelectorAll('.gantt-block'); + const rows = document.querySelectorAll('.gantt-row'); + return { + blockCount: blocks.length, + rowCount: rows.length, + blocksWidth: Array.from(blocks).slice(0,5).map(b => b.style.width), + blocksLeft: Array.from(blocks).slice(0,5).map(b => b.style.left) + }; + }); + console.log("Metrics:", JSON.stringify(ganttMetrics, null, 2)); + + await browser.close(); +})(); diff --git a/index.html b/index.html new file mode 100644 index 0000000..9aede9f --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + + + AZion Scheduler – AZ INTEC GmbH + + +
+ + + diff --git a/logo.png b/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..59f0bea99e2735b20028d772597ce5c7786331cb GIT binary patch literal 76732 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 literal 0 HcmV?d00001 diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..7d58cae --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1771 @@ +{ + "name": "azion-scheduler", + "version": "2.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "azion-scheduler", + "version": "2.0.0", + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.23.1" + }, + "devDependencies": { + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "typescript": "^5.5.3", + "vite": "^5.3.4" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@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.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@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.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@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.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "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.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "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/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "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.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "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/@remix-run/router": { + "version": "1.23.2", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz", + "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz", + "integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz", + "integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz", + "integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz", + "integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz", + "integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz", + "integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz", + "integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz", + "integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz", + "integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz", + "integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz", + "integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz", + "integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz", + "integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz", + "integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz", + "integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz", + "integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz", + "integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz", + "integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz", + "integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz", + "integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz", + "integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz", + "integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz", + "integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz", + "integrity": "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.2.tgz", + "integrity": "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "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/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/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.23", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.23.tgz", + "integrity": "sha512-xwVXGqevyKPsiuQdLj+dZMVjidjJV508TBqexND5HrF89cGdCYCJFB3qhcxRHSeMctdCfbR1jrxBajhDy7o29g==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "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.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001791", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz", + "integrity": "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==", + "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/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/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "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/electron-to-chromium": { + "version": "1.5.344", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.344.tgz", + "integrity": "sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "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/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/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/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/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.38", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz", + "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", + "dev": true, + "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/postcss": { + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz", + "integrity": "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==", + "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/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz", + "integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz", + "integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.2", + "react-router": "6.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/rollup": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz", + "integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==", + "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.60.2", + "@rollup/rollup-android-arm64": "4.60.2", + "@rollup/rollup-darwin-arm64": "4.60.2", + "@rollup/rollup-darwin-x64": "4.60.2", + "@rollup/rollup-freebsd-arm64": "4.60.2", + "@rollup/rollup-freebsd-x64": "4.60.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", + "@rollup/rollup-linux-arm-musleabihf": "4.60.2", + "@rollup/rollup-linux-arm64-gnu": "4.60.2", + "@rollup/rollup-linux-arm64-musl": "4.60.2", + "@rollup/rollup-linux-loong64-gnu": "4.60.2", + "@rollup/rollup-linux-loong64-musl": "4.60.2", + "@rollup/rollup-linux-ppc64-gnu": "4.60.2", + "@rollup/rollup-linux-ppc64-musl": "4.60.2", + "@rollup/rollup-linux-riscv64-gnu": "4.60.2", + "@rollup/rollup-linux-riscv64-musl": "4.60.2", + "@rollup/rollup-linux-s390x-gnu": "4.60.2", + "@rollup/rollup-linux-x64-gnu": "4.60.2", + "@rollup/rollup-linux-x64-musl": "4.60.2", + "@rollup/rollup-openbsd-x64": "4.60.2", + "@rollup/rollup-openharmony-arm64": "4.60.2", + "@rollup/rollup-win32-arm64-msvc": "4.60.2", + "@rollup/rollup-win32-ia32-msvc": "4.60.2", + "@rollup/rollup-win32-x64-gnu": "4.60.2", + "@rollup/rollup-win32-x64-msvc": "4.60.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "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/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/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "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/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.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", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "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" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..668b80a --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "azion-scheduler", + "private": true, + "version": "2.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.23.1" + }, + "devDependencies": { + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "typescript": "^5.5.3", + "vite": "^5.3.4" + } +} diff --git a/public/.gitkeep b/public/.gitkeep new file mode 100644 index 0000000..490f444 --- /dev/null +++ b/public/.gitkeep @@ -0,0 +1 @@ +# Empty Gitkeep File diff --git a/public/favicon.png b/public/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..984cfb6a5d7b07b1362abe91ce27af0d05a07251 GIT binary patch literal 6305 zcmeAS@N?(olHy`uVBq!ia0y~yU}6Aa4mJh`hA$OYelajGFct^7J29*~C-ahlfq^C6 z(btiIVPik{pF~y$1_sUokH}&M20djEW~^9hU&g?|AX(xXQ4*Y=R#Ki=l*$m0n3-3i z=jR%tV5(=RXK2{PA$E>|fni5#glC$krxpVT0|NsqgA^kx10w?igBJq>gEW*K#K6Fy z!N?32XJ%kvFl1z65M*Frh+<%1$ZTf;i{~&vK-^hIh8N5X3@{qqRt5%U28Idj3@i)` z3vp_8pJ3DCI&_ZCa}sNOA7`j2BQJXi=WfjX zB4L&C=%|+b$qSz4_vM5X9IR9tRf498g#HqbkZUx^VYwn?HsM=i>EpDe`QPK7&nrHE z?|F?~{@yVAug>f5e}A*JZa*lzK<*gD#Du_t)ww^J3d$y5{2?j$v8Tgg?)$Ynas@vw zUbV7*O1@9FkyL(vN4o2gvj6uCjBh(LGNh_4{4h#MJk2%>_E!(FRKj&JC zNnNGOpWOgL@*ndruL=J3vg3#6J`SJU@T*s*AN^I% zkdQq2;tx^5!g7~AM)#Q|CfJ4szmNUj%*1d`=`YJ(F+u&4-x=6a%CBCzzC~PwVMF7| zj6bV7cDR3HXUhw}dgc1FZY~Ck=8}i+JM#Y>IPf<2|C{^E7#e)4Ua9|Nb}mo7dS&;g zqorI78J;Kq7r5*(0olMD8XP}ef14(Q!pxpy>pi#sji30Qk>`YH?5{!%h6m5v|9dMx zTGvq#xsTmI;d-d=-;XsPeMA`6?3wb%-|OFj1F?aoU#I?jE(=eyqS7i4A7PB)VJ@8%+N z=?S~Uj@7G{-J7Xjwv-`ZsgK)Zt=J#cwIEw}uU=+1L4R5R!v>>E5BG}+u0Q*of$hmG zsGD&{-pbI0Kfl&K2#_4KpaKi3B{Y%mG>WdG;` zJ6m4;)hpMZcW++Ckf7<~_E>!BllO<@4N{7KKbs%SU{Im?bG;`hie^7I|38PR!O>gx zU!Uud*Pm<~8ZGwTJZN`0fWaVQ<@W!DZ)+PHZyWcQ|9ifOAz{KLkNc)4LAgR*enz54NYyto+Hlx@EnUlNztr7iw- zbNy_l21k#R_dVhhUVIDlt&QEcgM*8qN`LEj+lO=Q4GN~8uUhgxO@rZusd8aLY$3yf=~4Ip$o@MpA@^j${7emo7jsK)*Xt_9&416xgcIvtT+aR9tHJQ1^~!CKz>9TjXZ?EoGf7^P zA!5h1g!%VDQT(XHB_`JN>${r8>qQuJLZsdlZ-2tia|aZ~&%0}vF(f2LHP@=tC#>*W zKL4+{)s1v1E(S3rs6SV?eF6JZclxCNv4S6W)$k`cR2hTSY`cNMjQu|Gn@``_m-`wcaUa!G0WBHUn z{j*-N|235C{coa_*WZ!;ygDZ%|LT?bC&b(R7!SlODgSp|rT#_#lt1r#I^xfLw^)4H zSrVL@ji*=rmcM*Y{MX{Cf96kqzj2{0Ok!%)@AZ-|x&JEu6t|vh^(S_AzK$>}gR!w> zZ~TnB-`@W&2v5HFLszh{{YiXE_|+@ypN^(3W=K%GbpD?^@3+Q(GkT8IPy3a>lFFE&1_?8@bwn|_pb1^{4ZtI|IT^8#dj{sdbRiGqfJ2!8#Fb4%6E3$Kl#1ksK?3w zdhPWS@1CxC_})i^!AE!NcU#S$&h<-f|7)-LtiLJwrv9I~olFfYmprMTe*51AdCi~q z6@Q9Ho>H@q)nL$=HtB!i$^9qzKe5hOI_1y%4_DTo=u6H0bh(wOL22!i{8jPg?aGhj zKe^7B`sLjH;QdCw!%RW>@Is{5zwAl%6O#oC16^v2?>9txwEtH;x&Oq{(`MjQV3oP| z^YtgocULPvTEBW(osE*)Pqmydt3O-6>uz42^-B8F(a(ge?D3j#842na{GVJ`+vIcNDJ17ntuKNMB0FR?UVoB zKjox%rCr_s^SKB^j>gw-_F{tTPkz5~Az_Wd7g&toP9=x=MPVHrs!` zSQPwD3hmp3s}!P)c!x7y;5-CMuwew#VvkLmA3y{MD-AKt$k%J+YBFvEwIu*veu zwY#n51m&mvnJIp`zi;$^PT%P=dA<=>gXpwL{{>I(KQUeT5p!s8y*?;LPR383Z&bZ%**?Sg zCvQX;w6#?K3-kR~`tE*&H8i+h`F=xWhHAZU-LA=hmM>w5a9sQ3|E`*TUR$l7#{Mrq z?Dzeouk*m@$ zpXW2O9^lFV$4Rw`(!FrguiHQK&S;(TCx7yv?l)H+=9h^uc}KR|sN`x;ox=9}&fB^5 z6Wf&@9a^<=KPZ}(JgGnNFh0fo>Xquxu4&GU8&ZNk*}s`xKau@Xt9|g_8Qq{eol{zYOekB=KkDo`=VVKH?Ukvn4dhUe&TQCM|0hN8R@T`|5x?@ zJH2c7%3o_Ru&pb-4N7#%nm@1qWIN+z^~dz5=H}g5uV#O8edf++knnQb|H8Mm-E+D+ zw(ov>e$Ns$d$(WHKAx>-6k%YVI_W>>{Xg9{QiAWlpPgT6c&S|L`j2P(CAbs{W`CvdF?_5i5b@tdv`ajFdFC@%A8qC1s^6TdRCAa^% z%Jp@0^L$H;>-jSG)Ys79_<8!9zKSp~FWCD&&xe_ne}T`&BUWY3|~T|-aOt`KU04hcRky01M#)r_VN9H_bt@-Z~0H9?&S3_&I_g`NB&bV6r$)EbCyJl0?tJt4Pe-|;l=q$N?KViO6yvND^ z8*7-;7Jl8l|DfI7xBVUc&$_?#Gc90`lKmI+qGf!?t0g}b+dlG zTD8*ux%8KQri6n*?fXrA>$>iU3yQz^aD0!%OY5nxl^#8+nfNb=LFYn>N!`S6`;N+) z-e|8hx{H?MH~+5`-`DXy%=hnHkOQyY14UuN)hp9I{~cb!a3g6gcm4jk?>8<9UqRzEV=z}QvFO&!&m7i|7@m&?j=v^Zy?>{j84c1kf65m>Tz`XK`O$vmN14oaf3DB% z>qvJyviVc5s!0cvg6e-zIaK6*^@`OWT|w3ZGE1M-FF!nA^wMvp1*SLLKfTZMzk22R zlWv~b)(pjyCjA#|xBueq^>5anDLaoo+&@Qzq4HVU+`TY5Rr@a{s zk~M$If4{>2Yq9C2a?y8*XHK2f`=e{ldVt5*?eYDt4&iyf9P5`nsrUG#U$h|WRsYj& z9dXtJJzLm*SG;1kojB=#?xgyO(yKwen3SIy3~35qzu7O_tN+FQ_3ZzfKiis>7$}uJ z>DEzaJ)pL9{$KxJ&-njNob_kN+xTaoND22{oBgyafFZ}}*UkSO`Ts7+ug(8AslMD? z;?3qN5r*JdJ;$Ceee!;;{g;-5cELB}O}{Q>`@Kev^?*oJb8Xz@_l(_!lD+rWZLi-r zd(|?t8|ki^3|vdU?JNHcvIf*rNd+}p3-_IP6~G`e;m^YQpilen2Jcte{-rjJ`SWsy z7aFSd@Bb`xztp<_cI*%BP43n4PK+0Pvp4kL`*%TD^Jn(^=wk`;^A<2zv`_iN`>j#V zv;BYWt>0odwSQh-#$eI?<=p=j-1QgaU#9=}-}xc@iQ4@?Cp8(&Cb<1txAO!0--(m< zPu!RK&Gm@tPbCe8a|){eg=hV7s0Sr@=~c__tvtz}zKG$3#2e91ZXZ+OnjNKQ1J$rvBzI?vHjdnc| z2K)d1&wjp~{a^lB^?{^}_tsPYh6ca?aCW|4*gggiP#^A(<34tsV<{$e?ceqt&Fkuz z`%@_=Whr;V$&5d*Umm(I|GfG@(vtFj+jo5kPszV}Mcn(}l!%+4WM}>N4gX(IOxJzW zFWRhT?~|;`-7w|Pbx5lcT(_;8^(*i6$w_wSw!4UV6! z?-Z^b%HZSnINtl@{qz4W2(JZIn&p%1XKyoNb@2MyMFVs%XkBsPK zQrP=BzPrO5t4(sCSR`!$dF`+s)tUYM2j{?E*rOZ6Dk z7Jpp7?8*9*{9jzW{_XoYeWxBM^=**!j%EP0hP*)uOwE2$eL4H;W&46A%ctdPax=WG zU05FlYG1tLlX`P_k4`M(gn7T_UUm>mmh)x;f?}#!xeB3|AKXXUq}2&@y36WF^m)L{gf}gEzhvbds9&l?&p_YGbzOWuzr^QUy0xCi1q4a|LXP~eB|TyxZmxF?@y}(JU*ce zU%m-Dc>Qaa{pV^YFZjOn$M;LYpIIARU!^imnDyt!v-oHKN`BO9-)CrudC%1l_R0Lx z_T9gg9x+d{XNdi}ieX02F@2Bwe`2S-ddDqTn0WO{|5Is(4HNH)FsyyD{&0MGd`|~| zsPEtA8fJ!b%fwk7vN!Y}-oN|2>yh0xpV`Fcs7! zeRuE6x&PKb*%(T;ZUCiqQ&6)r=u`P^`3B!e55@^rf7WJx3Vw3$%h~_Cf3h)5x%G)j z!T0OuE9=ipzhwR2>OMn5W={gaKsC;K#4!D+_*HR@2X0tEY7D@gaGJ&j#O_6hJ+M({sGq_dnr35OZcLgTbY8 zt#}Xn-~SEnGc-ibjAop`_FHE9pYApM-+q?uV`SKDGM(vw$NiOe=Fj@$@sHum-7}!< z$ooyRIKS#u`UZZ6=%i(=4zIWEpZ|AvwSkh|qiyyK(V1zi4F~Olf96QNc|6;mAv#M9 zl#6cto>qAxL7qV@^)jo&s}JQqljkeV@9g0I@{K(q&@2>`B-CSmXn#tOdZYZ4d4oZ) zIK#^ipltc9IwzxcL;qpF1y{wO zSP@&eg5kowa;^SlPu{PTXV`mjCqo7(?d|&Q1#!~eODjRK916;~<=>}%x$^%0Ctik# zEyZFClm3VN(^S&CbX&e*pH(&}^BBIZ?Y0pTeD3{^;mgf7kXPJg{<-dZbx5AUx!@Sn z0@F*^&;Pq?DAWFT}Y(ImWkcSG51X z{}b~W9O7*Q8MZ6~6*slIr9XbV?PFx9-Ddmm{|9@mpW>AkO84SSzg`C!xBKnni$7Ea zKUSXHe;gwe0mG#r zi$>87&UbIWzVo^I{`-;LbMu~@pZ59u{e88+g&DAd4>_~{!6X_qs`UQHEcz3=lY!wt z^PlIxu2>znpThqAYH{Z2$N$#p!PGI>-P}KIy|gDYTmXgfp{DIPBZ>%&qrh zC5+%Q4h%RmLxXQ8Oxc0;7m6*X_2=^O!bCu{L%?&>u(`?qyJnnR&kYj5#PiPMOjtxk z@SauAi)uayo=rpvX^vNWj;$(wQ{FK%?*piK!nBv+fh$gb5tqr`zsB@E^`E7Tl9vQ- zRQ85Fzwute^zH+cpg_3bKsOmdO`MaeLFJDBti>o{0d>-R0V+EQIrdjQFVgwH(ozWF zO9lpp#<|xsO8fqH243d9ha!RC>^VVM?0<>rd&=?lZ9hsJID|Z(wffvf_Y<>d7WozS zw$qb2@BZOIc!hz10h;Opo0m!NL5Tu{s)~t}m9R;#cgo1K}MS?YhayAR>j5=;_8AIK&n{~;yz4?4EflPe2?6(Pn!Nl0Rky(#Zq zULyh(L!&+X$oZPB+i$ zf}03pKvR3yzsHw?<#9Au{`6Cp-1qsNfBNIW>DgW&3*i`Ub7jyJ1-K{*Mmg zlN?egtLc7#!y@r)C3cIgUdA@#Xga7+Xz^o^kB^_VM78);Ofo2o-*>P2j@@1c2Nnt~ z{tL3WfA(h-iv_M!_J%&cAzGXN@-B|%8-pP3_Brl!F5rTLUz~%|T54Eu z4xGndq2R^10E2oo-~2@n3&SRk$q0*pGNH4 zritQRSTZ-Wk1h(tv=by!VTW_*r2%JtPY6~5=>_5W;2!y98I&;*Sjn1U=UkKuQiX{r zwbwox)Pzx7K6m%=jl#u=60BS-I~dzyVhUA_tu^)f z6wMFhFsZXHVxN|JI;-ycoPbyJe}8=W1B*Qz3Z1YqqXY9Lcm65=J?S=DH#(wze%SS# zq~a!TRb5@C|+-9#+fhBpc1+Wx;_`2SE6!(B-!32fY z-oJg~={>s#%4i+uuiqm}&#IaH*>O4=r40hM5=0yzYfw}LR^=S{ZdkN){||$HSmFbj z$-uyJ^?v$Q)7ck3eAdfH8ODJ~gGdVfyZ`)&r}yf9Dx;bXOXPow_3}AEwqRni>?J1G z)q`Dh^D$a?dd+_S)!AknA70D9j<_CH4%1XkGa&u;Cyn@8H} z&p%y@J?bBjGX>-z1gqK}%r`FDxn2HSMI6WzaNP9jHz>)O{jrPf=7TDc z{5l(Tn1Nv05t`I%^rxdVf54JpqJW&%6{61kknE=ST|E9rg&o*57y&EIf85ing^8ik zl!kLQD1Q5o2cyIyydaCz+ly))j7OHWxEnc00ntA3^j_Pek27I5foTp{LeH6wqyJ7} z_}8qOczW;Le?OAJ*1-sPI*-t^MF}970tT|e0g=pqh$v65w*D$84>u50LofJM7Z$s@ zlXqk6FYM(xh2^Ti)e}$e`Bfi5&C@IX*{xo8v!(5PaVCxiHCd$_yz+8jQJ((0+gPp; zB_ugk?LWUN+iYRu>&NlPfq=wj!acrDL<8YKr<>mQZyV(mQLTk0sK&cbU6HIpVv-fJ z#H8>!2~UdhQf_O% z0LVHxhP5LOtS_hxg^QvvDC`>4Z3VT_*H_q}HbP;w$%p(MdyG)*gL5dXw*POLczW;O z6UUWNItQ>~oq7M^r&qB@FwIi^pIA^72==?9^h)4KZeO!EiUd3$7$}T_f1rZB_Vi;x z6#v5Nlg9Tif^gVNR)<9eW=(@VsQsc}X@lAwa{T&t4Jeme99)Vz)eHA8g%Q6S6z`WG zqt0W&%h=%9Qz*dzx0S+tU9%08&Y5lI|3j=00-4=%^?v(R)7e)XM5a#y@i8%l8GC*z zs2gVg52ZiE0FT`jda2m`OS5FYu{Y-3+XL=JC_aVtMjr&nc%fuSxGD<6|AvRKI>pw@o%x|>JXejI)(3-=!igRH?Y`VOM`rQ zr8d|a41%%}tl@n!sJ-xPB1(S;-d=d{JoX++O9Ej%S^mXrQT;o9Ztr7{Jm)|LRu;xP z49FstD&Ipbr?VKa~qUiVCS{prJLv`OYw>o=aVoxO>1-}#5vz}8?88|=v#o_`Pm z8!M@qH?b&IuFMj3vdQsl`kJEi%Q70~m+r8~uo5irhMZwYMD*6DEs3%J@!;tkuwgKQ z7Cvuqa=juq0;s^&MzSt^@u`uD-KbQ>x*7!46oV54RD* zpfF39OKV=|-}fm9C3YELshnZop@*v=_Mnp#`ZqpzNzC;1J7iFb@`eMyjHAHeeE(qq zj+te$k`?a7Jn%~GK)ST%^`{f3WAvWgw7=(^Vtb%2gKNr}vSQ{#eeaalpE^zlq9kWT zLg(VcG00D0LjM!4{B&2%lOk9)hs1Bwt87r)A%`LE;zCh~5IRX#1i7DYLoqeYaO1raR!7pP_zBXalZDoR^=|EZR z?%6)ED3-5a?{8nYM?egYwf2XnR-2VF3mjeO2@=4>WCb%3>HI^S+o@8{nx7~E0h+~x zq;rK;&YLkE0}`Pu!#m_CKi$Q$Cj_P20`Cztlow{84w`^$1Y^o-(~i_7G1DEUccG*y z4p_syBE0<>iVWCB5Rp(%NuAlSw|B~Gp$9>lAfwX$f~>y zaIU(ydBf)^BGU)`QBvs@SJZuO8El{12CqrBUpFNW2atx-AWY0 z5w%*)yu-REVFb1lL{OOC-Wvz4J3T%297?wf-Y{o~e_jv`vIP^9Rm#8%b6ovi`{b$B zX0FT!zFXZvS(k~hpR!#2Lq_@OuY=8c&ruu6uu6o%{&hh;!Y*VMh5kJcYLp&4t%(we z@Vfdyw&fQT8HCMb)grhXvha}y{5@iZirjV_Q>PRrxPDNhrTz3ugbzVGd0?&c195z{ z*aL#XTD%_AMiZHiw%$4<9+civnGf9K`%4RJarJwljdO-amL({|p@={sE85|s3-1$_ z#MpOKYNB-J5p9+uL$oppS3ng1L;clU8bUvwg9 zc+f}G;EB=};qyXErg~aeZkA}f)nJtT-~iLtobA=G-1n6FxrKeLp8oyyh7VXH$Kk&S z&P|kE$FXW*P+&NUb4RfTv_-4XH0#qPn`B`f6n`|ny}o?v=ZIEb1|Iu5lwum8OrYTr z>OyC@6o*;*8+ge zLl6pxU`b!Csed*+XBt{V8=m~)6F1=KSyLD+8K7V}adr|)j^)VNZ?|^NajhJN2i82> zP~3!Y{oi1mEjkUH0p_L%UB^%#?pv8@m$Vsm47Tak?Q}>!w>hYQJvtaD^l<&vDbt_L z&zXjj5Rej}zo`d~NvU7UaqcV(z!?-hPS7!$f3s(O`t9nEpVl-Hq5d=^a6u} zKAR_s6g;FU%yFT4BXgpwB_3P5X{C`$KmW1tK~ zTK};5>XhkejJN&oqm;D_3*TNpKJ|0NQCYM=FUdqyX(n?MJv(09L zDDjI(aR>a(JWv`baBCST3+KA4Q>L3A&R2qD;2$ORQJ{@CB`@XAUf3+b$iT2`b3D7= z^Nn2s3=i(J?MB#&%p$MUT`T~}LJQYd`;({(0$J(4y!cY5D)&ebV0? z!+_!td+9gS%=Uy!zyrmgY_P)@A6%bTrz=-M2Ec?dB0g!YBw2{?q6^=3t{-XtW%$7zXpW@A8IJ9Kll%!3K{@limXr| zB8mQ)H|x`-KZnn1A*_9ub*i}dXVvHI$)FW+HuvgeH_SP%WyH{sF8KtZ3hXKvL0KwU z4@xEad0~h#W|Sye!2P@QM3LO;8w?Edawi~?63lWi{ly<=-(vyJ=7dBmv~}_x)L3Dk z9g6TCIG=pFI!8U5nSr5V&t7mCm@zc$Y^~G#%G=K-rRLI3+x#%0f~mJbCb6+ zGB6;;!arUkXj2EQ3_(!lf*+k(r%umK&;MtPFcB>f{#qqi_J^i0Fnr`Og48((r4SZ{ zPT=>CT-srq55_Vqjp9%iqrz zaV}X@l7V4=(gqw`Dk*cp@|B=|^(_`e`bR0NkZQCK)@;$VsMLStf!bV&!iYh&1F^o9 zb;W0940eYSx)DKz%p$8@LwIlDMBQ@?5g0Gy?;TdixFipit<)qXTLOLqg)gx@9)z)7L+4V@lsCA#shfwPVfic-?752pcMZUd zbNqmL2DB+~K>nm+-dTBhF+?xrgI@cNx*tEX&UIe}*Tirq#QC6Zc18FIcbA?KXO9Js z3_N`F+IN`B%ZefDe~{bEDbx4RDSd z8#F{h2Nvz4`3kee3jTbV3pbFVW|DKv?Edx7_C&}qFgS>x`#oXhwAdU5hMuHJI5v9Y zZmsd)EM#~R#30?O1Lr3>-I&^`=Q+e)dl?W@nlUGZ8GSym03$%x<$if`}k! z62Z(W2mYJiL^SIVfy2PSKv_C1ek!m%ZHF_`$iw@hj_l75ug(z%^(tN*s+2dnx~ZF& zf#Khtlj~3{hI4Q?850|01ePnEfD{vfTQ|x~L<%_+#Q#1_p-MTeBDb{SO`O->1{Qqi)B?taIIa znL*t=P~~!I-(eO8hJUG(S*clY5H3<`KzT;bYK<5J!vgQ$wJ$ z^XS%{g4!L08`D9BY;|S&%&eQPYzz$huciD4g%KvEtbnQa=PS(q_b72H0|Ud|o3|T- zb(e$2H9FJKBeV;lcG;Zy>{w7_PxR+RBEr^TTGq z4e7}|I1g$V@XLWZq8H}=wJTWlR0MilV@*6?VXofZS)Y^Mf_g#Uznz0->v_48pkasZ z7Rm~ibju@ae>NWmtv%hf`98bf);UU$jMMP`wBow6OT&xf*2sbemg4S3LYe~(^=dN_ zg#fZ^C`{MiqWKDcZ)iIV8n__VE&tUdX_3e51NjQG_kDXP&&0ssF#X%_3oEAu_dvY* zA=dK9*Tb!t;c}otZ6-C{LO7GzJAz#KOtFH2p+WEM_k|c~`s+h+oojrc3S9raPO$Fo z6ATOqHIsX=#~wx1LxUiw6_YL7#Kyqz;@myHIa!uRp#x&~r#i>X<_5K9EI~~=a5}no zFNukP;qh$JM_!0%clRi??=X#jETF@{;86bU_koquf}x$r54Dy@w(k4_>H&aD$lIW- zu4l%;knrmxj`dRwcxna0O^2!}&Ua>QU^g=dF(3d*a!`_@GN)pf%%g9KN1Q?99d~cOXI>uVCkRQc2fl;adY3?L zJ=CPqV4u8+FDa^EO}4Mw!D$68RQ~my#IduByktwTz0(k{+rGn8 z`7ry%>#(%|GER&P3@VlNy4Ewa(cbJLC$=4l(7TBM_1vU=@!;iL;VzkI; z|D*T{v+JHJvM?|-ynFK&l)8~hrmZ_c{oAR`3=Aa?pUzEP_4KW-8Uw=tbDwR{bO%jK zaEhW9*`M1*9nqh)`Mg0D=eat0i>pOl&{F{R>9_ANn;v(za$^~|g0BWu@JHDg82*T> zMI$=IaCdtr7NdUyy+zNhkd2;q^EKNDphUHkJf zF%jIPeeb+H%Fhs5IF*A6r$es~>qnq9Lm2kXP{C19Qk16efGV=Zhe1bW?Ap8?R8FEg zWwoV#7#{<}h1Tii(^gK~zI_G*1Ff8bGC)$2vA=&#mL+s-iQzt4{jnERec-Wp1T<&}aHJ~{l<=cbbpa%3yP*;>4G{FT9nAj8sh65+mUP7V~ zIh%vn6txr&>?rEE-L7BWA;ZA1!1{OXgOJmjP9Rem7=G-Nd2}nL%JlisTZ{}0a_>P! zV5+zz1H*^OK9^`v>NFIBx}>LN9rne{p8DX0{v0QCqftGhWF7)T!Rc~Jl6j^U)GJA_y-a5i!5 zlrV~9@ot*BaP9`lnK=ykFGSC@ICh5D)ZQ|1fq5U?S^~?6OEOM{#KL%<;Rn_0zJZF8eFlhMB-h2j*JsDfh z<2o4jMFXzg>K7WJ%Q8R6nLaf&+1$;}z_7sb_TGm-t-4{^vC{OZs)+5OY9mn1d;RyO zbj$eXYDNqU4gb>&aBNf0n2*y!#&#S_gBLJEr%4`EUzu{9Uw%(FGYM`<=Y57_s^D!g z&Uko$>pG4S*gBa%pkS%FkoSdwfx%bn`?BY+bzo^d{pytI{H*;S=JkO3vtJ+GdkPMe z_-q3lM`Mu}D15lqShGVHQXW_z7#!#P@a_sm28LN7?|jc+-`NRmP0kAqj&nNr_%OQ< zBpH3vV`O01cN51Y0u1Ci5Eg5arcX^pZuNpjXe?FDY~r7*K^q1YVZN1|;D7=Zu$=K_ z`fovNd}{VPVlP1%F4*BtBZPxve{gWzVflA%oD2*X<`m^u>^tWR>#psY^+~8p?xC$D zsBn5c^~Qp`-V6*4-z8J9*D4H@B^_har>1`%<&`inFr3x=-uC?U9T#Y$`TS~4{e>?C z?@ivw%)qc`-d>O^*m#|=52-bLv&6Nul!zkc->PMM>VAC811+*8!WDcx(D^P%A&MX= zbj6Rw%l5PxzB$Ikz;IznQGV6FbH1=%#+q55J}rEJ8W?=MPT1RGlfb z{w~kRz_7sc>Af#Cb7#ZG^CCgzpN(D3fs{843=Hej-wQD?JYbh-!LiDNqC{iw2y#cU zxD5k?L$Oy5DDfhNlMoju@q&gug!jFPyS1Bzf#HWFn0wWaPwkKqEDR< z!s5iX8nlMhK~gO3lH z)xe1c;y{}u185Babr+nXD6&9{P)xUM;ARE29*C)kK1?~Nf<2%X)Z(sgaAz*)djALI zt2Ng@R4xZ4SNus=)hy$?5CcO)e5wJC8DYu->nL>FA&PhkZ7ryeeeY>g2U7F z=~w5ScP9|%iao~^5UYsb2?NBSEG+avVe$E{1IQJajU_t~8(2Gcb2+H1kj=XYdsxt_ zBKqGy>yuPnt+@jOLxbAX>dT)?^N<^y0Uv&RU_@#*Fw8RSz!6Tjp5tuY9>CdAfUW5J z=jU6=`+twL1hMtd0@da%pxD90lx3oI%l5R%zhz?~vN6XHW#)mSGfYW%G|XSSY)>2G z8~a){(0#C4-={r)eWwLhxM8;D7oYS#UuoS@Vz1a7=QM<;AnFWb}7wj$(;U<{gAQ<2#Q9coy23lLUO5-u zfm$$s_8zQ(TaCh?$Q>2yLE&-xC@UyD@O8~@f||As|1%66P%MXYD5?YQEnK#zE+a*p zA5^xT0u6PT!=|o2XPxrasA~C+)Y4~=Gm}8wA`5rL7D@(O?t^+n{P$cLv8GV;9udQj zqioFByUR4Hi4MF6Wg+&rZv0psh?#}<-%NM_4;mB(MP)FA35El}0u*Is(C9{+ z{JUeI<^kbh1%?*~6d;Qv!8Rik66vFyz-fntA737a%b*IzfbZTl`vbD4QyW^<1arR_fPsHb8SNu zHv@yi)NQXXJg$TfJXTMgviw3p{b%N8@Jzrw@Q%F;3;a;8zJmKMz#ivl8Y9F5aHVjD z19KH*E$93dOKjTD$NhC?U|?8rPK;gde6k?43cIs-GN%=X`1AGmf=g|) z(jpvVH@|QWWbxwcTuNPlEOPq6qWaXb=9v~~ZOW^h>y5`hN5DqYzH4jxFMKKY>L7TI zCcm4x3N#?WFn{4>r2Rs0uQ4z%T)|mxXW(vgG{k|I89Q`)ohtsZCiDUW1H;vu>zMnW zZ|r~$jJyjEn&))c_6l;e9N1rqW1RuR*K)%>vaq!PILD}^aE@J>ED#4TIu6)9>C>NQ z!eO8xl92S>d^YorD?w}b9r2!(T)y7}_^*Lx5i0J1hFux*ORs@ikH`T6Wq+l_6|*OO zl5vXzO(zB{eQWilLarM&eio>nO9?{LRz zP`s_$65EcEkblpXmw-$O?*S!ahJTaJf%feny9~-+P=`C);2afzW$by&me{mU3{zxe zUO<3up*EvY6y@yw(Vmrj%4sPK*k-X{J7_%VK&;hl>@`UPMH%}lC_|kL1$Cx~$x!;2 zBXQgeM@hCma6Tkx-u*4{y0Q!m3#@LJKKN)O25S`l^*R-tv8%mm18Dy1_-D`tZH6DY zR;zIgcTy4Y|kK|K@LwAXhnP5*_UIvaG6&#DjouO~p7bbo5*%Ho)`C9B?s7QgoHo|SyP z;j2Nd2fR(XxO_H9W%NM&wGPUWM2L8!BuI9E9TK*h71U|O*S3sHJg^>Ad*27oas1e7 z^_!XnF|_OWZx5)0q`x+dlTfbSd#EA)dXi0lKDg=kr*1BeDSk?t3=RL+g4!-yqNEAt ztMvvCu5Xn%|2P`lFh76!B=+Vv16ff;u&4WBJ}6!HuQmmBRq(gS4R8)6UCRrzK>2Ml=qN`9`~woG37j?qHdody zv1#{@XywA%*g_hZxTnqYqg40U=O1FA8T$k2ubx2nLBazC${;JE2v#Fm!9nv5pI8m* z-w+s|02P|qhVzbZ2RD&^On=9SBY`v2PsX_n6VFfzB)?Vc0uAWIw6cMclrDHw3ALve zzzQILIs;Mi}RBttj9Smp;N^9nm3fP=o^{Y2EddYr$>u;v7*MGkM#ui@F zH}LUzf$EY6;inY!;_dQ6LE93G?XDkKDU&Y>=@%aOThtN!^+nj(#o$5advUCD);nt* zUkM&reD`S+j>`|o8%J*j-^cZ1W>JUt*FRYgL2GS)#W*i7+H)0JR(?OF=oc?{XI14E z@EFYP)eWCZJ)T;C2aOJ#|Fns!%VuykJlWa6_mveamw6OqyFJQ)fx%(2-SrDAW%5m- zfwmVEXb-+UWp>L3Esoo>m&+o)J)|$V6%^HfwDf1=Xx@;wL{0-Va@Zh0(K%*S{k|v- z&~&C<>4T8I?NXo~5(C47ILjj|s|r>>y9jE3&e{;J`26*RQ-!O*+U`G1{S0yyCMIvX z4iYvW!k-9Ce|0>Zn}vZPZ&-f|Cpb!;W~q!c}s2;c>X11(Kq`#m2lxOK~jU zrYJ>~gHqJ>)vb&S3?|QudFPz^7zQfb85kJu=(k&h_UB!-i2|(!e*8A{z)rq7u&`NP zm5Du~7|5HsTLA7T{8Mb-Q4@I#JVtZ&XAJZ5qCMaQ%)r3#{j{Rr)y2`K{g8qBYSDsy zd@E&^bAjS9$wqNz83uo-9SZx&@xlDM?YkraH&0(%-%cwDQGrzEy$N@#tQN!3FDO9);NM2>qM^ zYJ~dUSUmw;M1e~%hx2i_aLkEOkn2KXf504oos$jD^|m%qNY2~S>?Vx zOKvS_Z^FlK`iw|9-~OccZHSxENlFsb7VY*OArHe#nMliO_BXwMqdNn_r^qp&tj4)( z6=++K<@{_=nF!8H3=9wIKxJad>Sq@rvxV!K&RtiW`Zxq!Ed1F#y%u6QI!Wd-pedmG z>w}Nwk)>SQR&jw=OU=)Ifm%SnJl%{v-#@rteG|u0HnP?+;8`>Usek7ww(kgW3ckZc=Vc=#91H&Edc8gH{ym-3=EcGu#{n^v|(8CMDCvUM3q;2xU7F0kTT&=~x zz%c9cN>FJD&P$-u@|2?ARZysb8lV?i_HtQ*D_L+x{E!^?4adMBc?QH8q@)|?ky6L!1kRflc03>-o zF6uZv-5osIL!in>ZniP(zjyi^sD6Va6=ag4Wb+!7Z2H5pK@E^g&!w!UK3)rMVK6Z4 z2EaaPD~K(J|4ul?6;-w|RD83vkcXJz`!1hy7{_!i{{8-1j+&Ja#! zV4$eHS`P}scJRQD$@5@#P`v_bGBPkQyqoGgXB9iBUIB0AsunE*w^JZ#{b!sNq#2Ct z5D=TZ5HtWcZy)5>pH|enDixN^NLq_B_nA6O}~ z92__cYV~EY2M#4M^+m6JN6pW!)u2PPX5nuEXg#Qr4YKqD_wF3#$5m0kIr74nuW;qQ zORG{qE7xn+Eey82mj&u%GB7k$KNVQ6&yU>L{%N(OXpR<0y21Xi_j$}n0*R0pQ8*{Q zp`G5S>CQ2$)^E@f#abP&tSb2Zi4j|;_lNP`FQCRU$Yp4lf|}*RESX0^u5q9XxG&7{ zmj(5gkcz8S;685=C`8|co&X0LBuxLk``UnGaQXm$5jh)P!S&pS63Zh?yLM=Swiw}W zH=$H>4f%1~u%~V^7a2kvV*+x_7A;PKb?$nmbD(~2Ft`P9p#D)RXmJEu9D%sxB`8SG zc7HHm;m=PWR|kRHBY0ce$a#(7f4u%|YIc|)#o~{tpfb8}H7L|)ec9QDRz~l$0d+*c zQ^?!Hp>2O~*I>u#(`wYLbaAdFk(B^94*r1JzEyQw3qVO2vru?-@a<{#9xR>ShWSrZ zaa?CfNhFq^Qq;Rzyc%?P9SJpCL;TZJ)SJsdi3yA;ag4oE`;L(RHCqEfy9#U8H3nPW z1DA*l3=O}Z3M_whaqTR3$S~4h?*mAAzi#^Uv)EG5@CzycJxW`c9fs?;^p zm_bY7_j3Kce_gTtM|>8f4sDR1>^x_m&4$p=8D~Lf?1lfYpAPQ&Lk4%YJXs2wu>!jR zl^|~%g23qHf%%|*czoU0*Px>{T37xOnjHnn@DGkZ5%@p(Kc9J&G3bP_x1o?U1xa#I z-!7qExe0eFS$)k$N6@w0AHe;T>9JQ4-PjN7K_jH?pdK$90|Q#$&B6a)`&?_cNg|$$ z7_8b7#k8C{WSX=Kg!7IC?qF)?5M&h1&22SJo^$8s%(t)dlwR4MO)k>1{k#2-btb4B zggY6|aQKVs&^rfa+%n+h1rEEtPI><*T)hJlcpxL^d7TQbxO2$H0(1<=>$mb^bKW1W zkcAxk*f6d15+pf*R3hVsUpVg$W5api8yk2TcthEgDbqhBhkZo&&SvVAa8q$i#hyOMS%L@3FL_EMTa949!g+6%6wc!%p)0Rng#^Xf>Fn!5Wby|qm)Nu( z*FDp`nUR5E&pkP4IDluP8s4w_{SHU}yFm=Me{st|Lc(gwl<6B@Xd$|24a+Be`tq=n z`||_PvcC1(+gYFgHi$b8Ik>06{`DQ~B_sn`AwejN?yX$1r{!oE(oNuyc&hzz1FQ4) zU)zbu5{Os=EltOHSBMGD)2E>e!S^_OR&sW)LE5icp{42nu#o%n1JHryp*Ids`dllq z{;>#nQ0IXC<()WoC|)7o4S(cRpQ_e;)Iw}Y1;vwy+;#*0bD)EttA4}c3EaqSkiWbW z$HqWP93cgA#Kl*jGh3j6>#zD$(XpReCGJdhEWgNMZRS9cJJ8>#_9k(F2= zt4{XCc~<`X;2yplF_C~09q1``{`%r%h^6S{3Y?c7X5c&xJOeUs`lC?wsp_AJS0hmh zvCHkxb~77b$id9MJPpf$bvdtCmb$dBu553$NXTvwx<5W`lz|k(FM?oW^mc^ z_V97G<)F)L_S))L+;9JA1wC65--;aaT~QBmh4X4qX$JK&B=?}8bv{Eb0FBWlCyucF(U9$oN)cNJv1C3XUFZqv%&_~4Vi=!3uw!c(76p( z+;D$^oW<22J%c?R78%D|kH5A6uN`3snD3VcSBb(1zVQz>mv0TXrcRmuK{#w7j;0BErm4ognueSpf&@ga*D3Fa zN>R`>A;dpu>Gkj7CP+AdYdnVk*Y+mhXqr4=C%cjM;4R1%g`$XU4rs2h)v-XzH3!t^ zu7)@kour`n_@D;LHCC%9Ak2g$33y{Q@gSrK0}lc;>|b51iBO8nqM)*=TR!QN(7rlR z(3BU%NKioh`mm?95?iul_`m)fXr>gR6osToW%FYyDBJ7`MY0LfDv-A`vyf$AIPm%{ zY9Ot@v=elUACh_)lY+L1J*c3suPX%|n+!1yrGXUCYO7-juJ9lQ{fEsZdWhl}q5(>h zRkVQf&4F+&P5p=G*YY6-5I=+j%{%m1?AgJa;NtE7h6gqB$6y8hf%TVm;@G@PNk}YO zvZv*LZ7E{SI7&!-sH%=A=;Q8(Rl5!Mmljt-y^cmvkY@j_oAl|^!^NwaaTH@{MJYr6 zB~NPhE5J>~hX2bzA+f~@adH*@jFW%O6Gt&dR(VdKbJOr&LsS2u#A>8#+aYZe9Gx46 ztV^Cak}O%y1BcOr-=HLWeEmP9jr|Z;U~i-~%qqQvJ)$UR7W}XSl^`2e|3I|T&`Md9 zApwRAPjwtCP{>cQ4P~Hu_i7cej|KpX+-2ruBO zjwss4|F{%-d~X9Sh9wSIgGO#nXd#+MXvIA0hycTnbgS=(I7K*|fq{~oYYWP`k3vNd z<{<~f1GEtVhCegT;YcjxHBG?1-MW3Cy2d+fIim2!-m|&yBKQlo763Ay#qgtQ?riL# zLO~X)**ocz(7zY2&Y*+}?rzS9&!u^YCIdp#2mP{l_uR^oIhkKHDO}L&HIrlsPGHk- zIho^>$i>s7#=GlFOS(+yr4UBK76}YaNws^&+|Iy(G7Kb&*5B(f@@qs zfe}}!F#$aI(omnL{Z;Va^5xLA!JzQkbIWV)q2sD&nn4!^nk4eJ&sl%C;$1uBpo9nC zU#EZ+VZmgD5_lN<&)&6H+7KErhc}%?Tbtw!0hl>Tw$DdH?%}*2#gnYp-b5#2vB$-41x*cJyJ!=dTUs z{Z@pmSk4GmH%GA?&dI2iBSZU%+L<+IOK&oqPXaNM)k zRHk4b-{YO~knKbbGFFxdwQv_OfR^~+&IjZsT7}(fuek4c6pS?Tv3^_7@(+bOe{MYZ zih+UQ-aRQki}?1Bclg0ulShqoIL;`aDQ>n=CxOj zpZz!w>3nKPdFnlb9dub>MRm5tgBsam#rDt>dHzg2hht=r{6z6!Zq8D>LxuC05Sanw zh`Zq23+le3=iUd=rDli<033QSf~?{ccV+`sIt}+TwZ971{S8JsTOE>UapfF_|6#b! zbs@i`{h*z@)b7ywYNX-F2Uo*hiP-5S%B=w{>fy|`cmj?p=oaNa8gqRSAqop15bf|A zw~uk=>_*TbNe9kvnfi<4|DVf;h|N4dBCTd)kE@1nI0xnkGy@vsK{4`g=W@_u50Hl# z7#Ps1K2w>3K6W{H)%Rg_i5ZT|Hpxmc;0pNvwXj!@pVilbw&y@y0jdr)Yhn)BM1Ure zG16`Ms~jA2V&qqT`>ukTL@zFXf;s^-%9Fd)&wT!L1AeS6q6+o7IEH-4%EAO12p__? z1^pNPclq)rRQEl6T=Y2^YqiL5FH&6=p%|G(etG+2{@N>=I*&7v1{v1p-HXMQceX5< zj2N;(b^*f{oHM9|%G&wa+NE66;}6;VK-!%30d(Y$Lg&|KQjnIK8Q8K5oBXQ43=D$& z@}s72?G??KGS1Hr&Li^f0nk}P4Jy|PdM)F!ZN59-09)vwHrE891}p?6$VwsLHuH&0 z?b2O4ZwNhe`~x+Mfq`K=^kBJ+^HP2`;HC%kTEv^BLWs##gn9-B@(T@xc98#5cJ92P zkOw#M!R5PqcK_K`2{}pV!0+h8oRH=R=ukWch6k5Rgb-aixGvBIWMmay_zTwy%hq1e zl&RtTtPlmasv*Cs{O8lRstgPc!q*B;KzcTiX339|yUX?A3Q-wGxLeG)`(B_z%|Uuw z(DH%{s%M%t5GeqBYMMt1Z}?1bpJoF@=HHslIEFRI&!+b-hrJTv+tyMkfG7bPz=t4u zt}(wp1I7CN=MXpRqWGTtL|VZgy)xM1gwQibq;VDSxgIJT+p^DmjC#CN{tzT#{z$j_ zjlFgyKZ!oLe%otqUfoNfXO2iEG|o&?v;Q0)Bq~t6&%nSyehB>d3F;GgPn#WY)q(Kd zj}r6udi(S4#Z{Gq?t}Put>6$iC7uA!IWasq{`w8l_zSXY$g0=yC(#4_+oxK!)|YU8 z2A{$Jwe9|wcb`t*2Hm!zw&vjkNMQ=;_8vHY{iY#Q6&gi;VSYbfyOity387~PXCa~h zdp@DX2*i))plGt!O_W=MxG@S=n9t5Wb3GAWm^18;SI0g?%RqiU{dX_yl}KF%=jR0Q zx@<_WV$UcQ_H%LEm_b1*-Mse7%QRJJCk0|V!;V@=Deh2xt>6(Pc|sE90^YfEplZ-4 z@?8KLau5mIaA!@*k1w`}6mSP$ZPH*?T7*3&$SX4N*CsWyK=oZE=jR9gh>ZMT_PyA; zO3)dq7g|hZiok;rkbxP76ALFp@(WrRF_7QnaVU#k8NB0>(6fV}yLX`GLW=XwH4n8Q znH19Ep~Yasrkh@K4^5qB!0&{p!?Cxw4y3<$f^GN{bdoYTqnMzY^ujJs_$P9HRzMp1 zdk?y$Yf{8v(`OZHKi=U_gd~TWWq$9Vp@T*dZ0`9X907Q!)}vXOBM>u zU@{K!IMu?{sCZ(5XS1FyBNI!2>bx!G+wCu#zOO9ZZhiUg{aLM8>Td9ufP?q8pyjh4 z>m|yyAiA0byQ?!ncOxyZ&9?Xm?qsGwBoExT0-xE2=57#|?2grkGoUueo~O@tG=q*z z0hz_X&=6j}x%}sjn|rr_tDrw*)skNW&%@Kw&fCgw#4CaAO*p zygn?p(nsU~B+JPhqX(t#4>Lg(P$?(`5C+}=ov!PVw0Gw*a1v>O^tcaHzubdkwFOyG z0~)wG;0_*c*H4r~S|OGIN}&p!rZLHV*QL(?6@Zj14Dw4qBlSHIu4P~#n7fb_L)nmF z@As+NrMrHvP(9PkjtJQucOi-M!aUPC72u-P2r}~Vz_>IH$LekJ-m?o0VWMs%S!-+!2DwH`~p2WclOx8fc~0u7v1tX_LX^Un*R zX9trJH4vU$V*GLmA~%8D1jl4K4xCJNL6Z^dRnIgZhZMCSBXQ)CGghDlfFK2EnEc_g zKe^haT>TqcDnCRZ%=`wa1M=4yB4^SIll&09K7?Y>P7iV?XAZmuWs;}U4fy{d-8}LJ zbW%7*cVLm=e?xhX-ZfFWm!mXVF_}4LMewh-8B70`kM( z5|l@hPM>vU|A>hG_3zT&e=6Jzsn8z$T>G#E+z^=nUQx-wK;@wa{EakAP(7mLJ%c?E z;XXV$Wc5oGT-#L1ZKiC=)Gp;>k3VG7ff(>Q0FEG~>>a02%JJ2&+m=HzgCH3@Rq!O z)GXa6(y+Av+=2`bUV}z$+anIyAl>?fGq*78i+>K*#nx@Jt_3e z5vj44e`|Nm4bZhL8Hc3!EWw2;WQirif#0w9;8+bycGKg-KTykOq4x~-^N73%X?jdF zjY&mGvHjO8lTm^M&LJA0hk_M>R`y)Sk<>j;dI*D>Nh~Daf zx9^sMFWUXK=HU@YVGfOTo8^8u*2t0F_B&t?ih%jkW~)otAR2W98eR_4R+f6KI8uP;mw}y=enuf z48T4N0ZK4nmoP9iTq_kq>PJ9DK@{2fq#*}Xu`E?R(+s+r6J!(v0|PjlK-npmJt3o0 zP(FepJ8wj7n`(7erXFR)q2V^P;D(ytS#pTlKAi??!|XVH_9HvejuDhb7SwKqiGDMn zOf-tD%!q$EPDL7MDF2?&GsgymW$>B;Ds*D)WE?XnWLFP1lR=X>>71Vx5WO-6aMcO5 z^H21197~7EuDBoI91DQjdEo!+J&;Kwr~rZ@+oLt0*{YhmLeCtL)`Nz{G*;FZSR#p@IBVb~@~p$iHmP&ksI=?LZ?QSe6u_2|>9SY3#@3xz@*~-fmmO zz$L(PV^zXR*8u|V{rpiTi%Ho=pS5(-EJhR@(!P{4w7@fOnwCzgiqSG;re z*2ZnHn4|+~+`!!cWhCI-@2r4x z<-dS~m=eR03*NbhyJZgV=|HS_hnfkZ9(irTmJt~k8h+wj=){0?1dSoGgTY{VT$Z$$ zeOsj<)?-HY^sC@Fb9x5OMXVh7!?gIt(rt+slV^w{ZN32c4T2B+_S%FktSRu~2b;ZD zz9p?po*{k_Yiz}VWJ<#|OT9tiztB$j`&qUoYMEQ1Q5deiFKEV1AuTku8{k zL6DuO?Bn&f^2S&3e17x>bcoLP-y0stD}jov2l@LgQHrP!E}mH9gn@yq@&i;Z*TsM= zFXH+92(%ZXfBSv$nDxcN;I8xfFG2rHP;QNR&^ZZd;RkXEkX>lh%#X{e{@9p2Lp+_8 zfuUm0-+d3N=FUc1{|vQdM&u+Mr}U6rLDXOO&OIzIqi?stLK%E4(0A7BukW}ZhIXJ< zJE(Zhf-=!4vO*ny33LC|(rpjt_a(;7;bCA%;QC#A@~f5kMvM$65IG4)$DQ2ZcyRY? z(Cvbz{+ zit?9tam>#!2;#1q@m3-(pj4nOcX-baP!GW7UR`{_-gCZ4bJAe1F)(~s`vgYQL+RstOh zr}^f)>h;%mj$mX*275JJC+Coz_D_Q9y!R5%TtS`T{_XwX(11+OLIVO!)vbFn56r+I z$W8<5FPCmhoSQsD95hpoC-uEnHOCAIkTb}x=xdh6Wl77`^L$oPU|?v#odoRSif~-Q zN>&n>(8_VZ{pHea;{8@_m4Y1j3SNf!`nU#2$xZklR)d=8SC4nQ>w-qna2GWXDktGs zmPJ-6hqrUoFn{UvTW#yD+A0fk7#JFGr~LENQ&8Fip!kPia=R=)_E)XU{qe%^O!s3Z z1_qmZc6vMFo~uE|93dt{$Qk~Vz-8; zb@PA8^jmHJLBVZ9{Wv&yM;t;%n|O#4lk7HI7jN z3Cj7SfNO3QPuE$2TTlU1{>8nKc;*V4*~eYzSE%m9G2%~Fc${!z=~y`ZR-3qGTcw~W z-s101_{pVEPohy|l|c?-N(bhED!M-s&s?{Fx*Eu>eH0r$)T>Sh^*~Sr!5p%yoAcMb zbM>d$v{eeK;`g-9Nmc6R_XD551l`_n2Kxbw{5Ew@Rm^dvAG$>JX;yl_|K&*nx9y0 zr?&e1$*c>D^{&4C!NkCTCyOSCDjm4K`gUDed{OB$@1KicYXORdm>C!jd^lBnaPi$p z%y#+#53x)P2ZIF;{K4H3z#Ubg9Srl==l#BE`{mWMC!jLuu=aV@{<8Fmn2A(5dI@GI zf~_Vis=^eRYJBc~`+SsnU7p`L9tH-3J)dGeWR#0x*2D&rU2&|ABCBBv3i|mgZ`YOm zFW6h*P!z_%(C~2T^CtJ*n=w1)4KsCdoi{^PEr~ZK_J?i%`^K)Iw7AF#)KHr8e3JX_ z&5%u5;BbZ#4IW}Rj>RQA4cLQHV*RdNmX?wX3 z!@F18=HKO9m*@AsPMLwBfpg09F8AG=FQ6IEz|c^>*#qmSGXr_u#5GPV_k8bu+g$y0 z_3X*=tRP?G%yYY=@8LK#gh8MY*RhOv##%RU33jZ>`~CCL*L8V*b)Ysc?p*gLrx3@O z0FGjcfq{X5)l2ez-`sod>!YueK(o_r;q$oV-k!tkm^1v?dKAZ{Nn};fxH}06qDlsv zcfC#D{_pBp&jSn$48a=bRr|}*F$abi{>(hO7b9(e1;}#K0WFsVwbI+?^X{&mJ=uYg zfuSw@7pVBdVsp)zM>uwzk(>4nHi7(n@9J4kP`?~^5~$gC)EBcj26>unKdXTJJa_f% z$)M22lLD%`rLmX!N4`EX zIJuDlRK(#b@GJPcah$h8{dB))IVd6?g`2C}^WtmlFxy7V zkel>-Kz$qC%DoX*okDo6wmbc35~hPdB4k;Odlqa1m*BDB?SJ1)+qKKGlNU59^mOMF ztc`7keWxDb*t$fPn-WBo{#1kN{m#|1Cxe#a;cn}Eh!vZTStWx)gWMwj;qR-t_Ph1> z?y~G;#Tya-mLC0zJ|VzND5d^OiT z-sS_ruC>7?SHuMvDE5;Z)1a>P=IR4qA2ptwi8s3^sC9z|%TO$ZaL5X8yltI7GeKoO zf4I52Ix7Ri45v!H$}QI2nBzax?sTsfcz7Mu%3@!a=jX=(8fmzs52}2z*i7}V^#@SL z=KJ#1vnNl++un63>29HJy1y3-s`d|FJ^S)*J_`ebLF1>Gj~V4+SXM8+Pn(EX)d-1v zC`o2B>xA{n+jaXYK%FZ5U2BFtyPr~b&U>{$=NeG|_TJY=rISG$&vB2XF?`s36vt{8 z>ZkpQS90xlU*EaQl2Zn6p-*I|`(N_&zCx9&lQbIo1cdaC8Aq~2Cur{6e1|hSL2!n| zj*9r?9+&o_sJlN`Z>l(G8Fyi3tmB<6sB_(TPAtl3^*DQNT~Fd)%QJf$7#SG!-b;$h z^u1NY91&vpVclDeJ>B9tz?2E+Fj!+)$CZ?~rzEb;J$#2-gn{9~r+Lp=+mow1uyn}& z?LEee-D=#2x#BzAQ`==-4#?`+hwpF;;J12T+~Z}?EP_S_;9TlB0cY9hu|QNQd}CQ^ z&BjwQ&*Bmo7#OC1x6!wdx$TTO0L<{=uN1Dia@-eE;W<)mt-w#s?C@hBmOmF?{ROm} ze8-;Wl?NZ*ki#+})Nnrg4vs-M+(&@pIlmL&`N*Hei&h?-84l(7L zSiUw^@^JWbgYSLZ3=9R8=Rbpdjk#cqfuS_(3HH_@o@-I?2e|sRbv^oaX6-x6eHa)T z@c8<~!ik8TUoa1YY23%2<1Oe;XoC`eMcv1Z_dsjR_3|xoWxNT&%Hd#}FbJ{&9Oq&o zp^VlO^`NLwd}i>SkClNTpjA}|+*d%i_}#*Z#^_=YKAC}0;SUOoN5wOK@4nB$z_5Vn zbhqg8&jvEL^*kWvB9lAX-OeHN5o|Kcz(=;AwBLO$@f~C?fJ-OwsOCBw9JVia!*OZ~ znYJhSf&89?@Vj?Jp%wq(jc=W>XRSEwN$UNKk?K=wt!;VJPJ&#J{+LCYo z#d3mp!~82d>>&1`ljPdJ71Rc>pA-M5ao;x3#RWSSs_yv&aexN89SjT&+L^eHFCncF z67){~KLZ0dlcx*x6v76)ZMXk(w`wQ<;D2WD-4uL0!vDxaoXbEZ_B$-)#(}A(m#{aO z@m#@x-}-a6rW)2H@)g$~-OI?p@Z;#GM>3ZyCHiv9v9wqgsP*C)Y9O<{e&l}iZlgB- zGl}okZveH0_HS*epYy1_J^A)rEQb=Th*ZYW+aouBhJ$L148G$3!k{kDhwy119=^2U zIlj#r3$G%XXm&vC4^1 z>(D4NZPz@qRXe%DWdDzd{|ieQ7#J$<3+=N6mk!&p3?9E*hHHRI*<8sNUP$lJEPmUa~K@JQ`#lDsH%+jcegOd94W3 zy;!UVRn~W7v6MRh;vT1=Ita!iuK~CBmG+ug$#|Q2hws#9fPKHcB_G^YxSNa3dMXbd zR8I|$0HwUo-ThzXqu3c38ouj3oDC@pu?7D6>pP&QRKUCqqp6)G?7wos%Bh2dv+9q! zUK~^YvzW`zC9%& zcQ*0eU(hNGy?jn?zru$PZ_lK{4=4OP z3CfO#7ymqZyvm+~kl!8VO6|n%cX+h_xqMD`wgCrgB7>MeO{p)ih?(E(FAHy^}BY$`X<}9rr-u#yQ!=!zuupI+~gW$9cPfBFk zYVSgCSwMsz0|UbZXI*Sbnt_2qgRBvmJ^mHj{z~}GJbZ^!8Z;a^Z8Gcf#|Cd+t05ih z0&_saoUEN#5;W*GnV&e+HayVPvw4&K2T~z6+_&9>l6>PY vaUYAnG7odzCfISOaBkwlU6n0ez3LUK*7j3OmZg`$%UwNP{an^LB{Ts58@J7^ literal 0 HcmV?d00001 diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..1ea9acd --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,37 @@ +import { useState, useEffect } from 'react' +import { Routes, Route } from 'react-router-dom' +import Sidebar from './components/Sidebar' +import Timeline from './pages/Timeline' +import Servers from './pages/Servers' +import Processes from './pages/Processes' + +export default function App() { + const [now, setNow] = useState(new Date()) + + useEffect(() => { + const timer = setInterval(() => setNow(new Date()), 1000) + return () => clearInterval(timer) + }, []) + + return ( +
+ +
+
+
+ + {now.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' })} {now.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit', second: '2-digit' })} +
+ AZ INTEC GmbH +
+
+ + } /> + } /> + } /> + +
+
+
+ ) +} diff --git a/src/api/baserow.ts b/src/api/baserow.ts new file mode 100644 index 0000000..de4eb71 --- /dev/null +++ b/src/api/baserow.ts @@ -0,0 +1,104 @@ +const BASE = import.meta.env.VITE_BASEROW_URL; +const TOKEN = import.meta.env.VITE_BASEROW_TOKEN; +const SERVERS_TABLE = import.meta.env.VITE_SERVERS_TABLE_ID; +const PROCESSES_TABLE = import.meta.env.VITE_PROCESSES_TABLE_ID; + +const headers = { + Authorization: `Token ${TOKEN}`, + 'Content-Type': 'application/json', +}; + +/* ── Types ─────────────────────────────────────────── */ + +export interface Server { + id: number; + Name: string; + Beschreibung: string; + Sortierung?: number; + Prozesse: { id: number; value: string }[]; +} + +export interface Process { + id: number; + Name: string; + Server?: { id: number; value: string }[]; + Status?: { id: number; value: string; color: string }; + Wiederholung?: { id: number; value: string; color: string }; + Farbe?: string; + Start?: string; + Ende?: string; + 'Start Minute'?: number | null; + Dauer?: string; + 'Erste AusfΓΌhrung'?: string; + 'AusfΓΌhrung bis'?: string; + 'Intervall zwischen'?: string; + Wochentage?: string; + Sichtbarkeit?: { id: number; value: string; color: string }; + Sortierung?: number; +} + +interface BaserowList { + count: number; + next: string | null; + previous: string | null; + results: T[]; +} + +/* ── Generic helpers ───────────────────────────────── */ + +async function listRows(tableId: string): Promise { + const res = await fetch( + `${BASE}/api/database/rows/table/${tableId}/?user_field_names=true&size=200`, + { headers } + ); + if (!res.ok) throw new Error(`Baserow GET failed: ${res.status}`); + const data: BaserowList = await res.json(); + return data.results; +} + +async function createRow(tableId: string, fields: Record) { + const res = await fetch( + `${BASE}/api/database/rows/table/${tableId}/?user_field_names=true`, + { method: 'POST', headers, body: JSON.stringify(fields) } + ); + if (!res.ok) throw new Error(`Baserow POST failed: ${res.status}`); + return res.json(); +} + +async function updateRow(tableId: string, rowId: number, fields: Record) { + const res = await fetch( + `${BASE}/api/database/rows/table/${tableId}/${rowId}/?user_field_names=true`, + { method: 'PATCH', headers, body: JSON.stringify(fields) } + ); + if (!res.ok) throw new Error(`Baserow PATCH failed: ${res.status}`); + return res.json(); +} + +async function deleteRow(tableId: string, rowId: number) { + const res = await fetch( + `${BASE}/api/database/rows/table/${tableId}/${rowId}/`, + { method: 'DELETE', headers } + ); + if (!res.ok) throw new Error(`Baserow DELETE failed: ${res.status}`); +} + +/* ── Public API ────────────────────────────────────── */ + +export const api = { + // Servers + getServers: () => listRows(SERVERS_TABLE), + createServer: (name: string, description: string) => + createRow(SERVERS_TABLE, { Name: name, Beschreibung: description }), + deleteServer: (id: number) => deleteRow(SERVERS_TABLE, id), + + // Processes + getProcesses: () => listRows(PROCESSES_TABLE), + createProcess: (fields: Record) => + createRow(PROCESSES_TABLE, fields), + updateProcess: (id: number, fields: Record) => { + // If fields.Sichtbarkeit is undefined, it won't be sent, which protects against the field missing. + const cleanFields = Object.fromEntries(Object.entries(fields).filter(([_, v]) => v !== undefined)); + return updateRow(PROCESSES_TABLE, id, cleanFields); + }, + deleteProcess: (id: number) => deleteRow(PROCESSES_TABLE, id), +}; diff --git a/src/components/GanttChart.tsx b/src/components/GanttChart.tsx new file mode 100644 index 0000000..f7f0862 --- /dev/null +++ b/src/components/GanttChart.tsx @@ -0,0 +1,457 @@ +import React, { useState, useEffect } from 'react' +import type { Process, Server } from '../api/baserow' + +const SERVER_ICON = ( + + + +) + +type ViewMode = '2H' | '6H' | 'Arbeitszeit' | 'Nachts' | 'Tag' | 'Woche' | 'Monat' + +function getViewBounds(mode: ViewMode, referenceDate: Date): { start: Date, end: Date, spanHours: number } { + const d = new Date(referenceDate) + let start = new Date(d) + let end = new Date(d) + let spanHours = 0 + + if (mode === '2H') { + start = new Date(d.getTime() - 1 * 3600_000) + end = new Date(d.getTime() + 1 * 3600_000) + spanHours = 2 + } else if (mode === '6H') { + start = new Date(d.getTime() - 3 * 3600_000) + end = new Date(d.getTime() + 3 * 3600_000) + spanHours = 6 + } else if (mode === 'Arbeitszeit') { + start.setHours(6, 0, 0, 0) + end.setHours(18, 0, 0, 0) + spanHours = 12 + } else if (mode === 'Nachts') { + if (d.getHours() < 6) d.setDate(d.getDate() - 1) + start = new Date(d) + start.setHours(18, 0, 0, 0) + end = new Date(start.getTime() + 12 * 3600_000) + spanHours = 12 + } else if (mode === 'Tag') { + start.setHours(0, 0, 0, 0) + end = new Date(start.getTime() + 24 * 3600_000) + spanHours = 24 + } else if (mode === 'Woche') { + const day = start.getDay() + const diff = start.getDate() - day + (day === 0 ? -6 : 1) + start.setDate(diff) + start.setHours(0, 0, 0, 0) + end = new Date(start.getTime() + 7 * 24 * 3600_000) + spanHours = 168 + } else if (mode === 'Monat') { + start.setDate(1) + start.setHours(0, 0, 0, 0) + end = new Date(start.getTime()) + end.setMonth(end.getMonth() + 1) + spanHours = (end.getTime() - start.getTime()) / 3600_000 + } + + return { start, end, spanHours } +} + +function parseTime(tStr: string | null | undefined): [number, number, number] { + if (!tStr) return [12, 0, 0] + const [h, m, s] = tStr.split(':').map(Number) + return [isNaN(h) ? 12 : h, isNaN(m) ? 0 : m, isNaN(s) ? 0 : s] +} + +export default function GanttChart({ processes, servers, onRunningChange }: { processes: Process[], servers?: Server[], onRunningChange?: (count: number) => void }) { + const [view, setView] = useState('Tag') + const [now, setNow] = useState(new Date()) + + useEffect(() => { + const interval = setInterval(() => setNow(new Date()), 60_000) + return () => clearInterval(interval) + }, []) + + // Calculate Running Processes whenever now or processes change + useEffect(() => { + if (!onRunningChange) return + + let count = 0 + const nowMs = now.getTime() + + for (const proc of processes) { + if (proc.Status?.value !== 'aktiv') continue + + // Minimal version of renderBlocks logic to find if any block overlaps 'now' + const rep = proc.Wiederholung?.value + let isRunning = false + + // We only need to check today's blocks for 'now' + const iter = new Date(now) + iter.setHours(0, 0, 0, 0) + + const check = (stMs: number, durSecs: number) => { + const edMs = stMs + (durSecs * 1000) + return nowMs >= stMs && nowMs <= edMs + } + + const dur = parseInt(proc.Dauer || '0') || 60 + const dayStrings = ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'] + const currentDayStr = dayStrings[iter.getDay()] + + // Weekday filter + if (proc.Wochentage && proc.Wochentage.trim() !== '') { + if (!proc.Wochentage.includes(currentDayStr) && rep !== 'WOCHENENDE' && rep !== 'WERKTAGE') { + continue + } + } + + if (rep === 'TAEGLICH') { + const [h, m, s] = parseTime(proc.Start) + const d = new Date(iter) + d.setHours(h, m, s) + if (check(d.getTime(), dur)) isRunning = true + } else if (rep === 'STUENDLICH') { + const d = new Date(iter) + d.setHours(now.getHours(), proc['Start Minute'] || 0, 0) + if (check(d.getTime(), dur)) isRunning = true + } else if (rep === 'WOECHENTLICH') { + const [h, m, s] = parseTime(proc.Start) + const d = new Date(iter) + d.setHours(h, m, s) + if (check(d.getTime(), dur)) isRunning = true + } else if (rep === 'WERKTAGE') { + const day = iter.getDay() + if (day >= 1 && day <= 5) { + const [h, m, s] = parseTime(proc.Start) + const d = new Date(iter) + d.setHours(h, m, s) + if (check(d.getTime(), dur)) isRunning = true + } + } else if (rep === 'WOCHENENDE') { + const day = iter.getDay() + if (day === 0 || day === 6) { + const [h, m, s] = parseTime(proc.Start) + const d = new Date(iter) + d.setHours(h, m, s) + if (check(d.getTime(), dur)) isRunning = true + } + } else if (rep === 'INTERVALL') { + const [fh, fm, fs] = parseTime(proc['Erste AusfΓΌhrung']) + const [wh, wm, ws] = parseTime(proc['AusfΓΌhrung bis'] || '23:59:59') + const interval = Math.max(1, parseInt(proc['Intervall zwischen'] || '0') || 60) + + const dayStart = new Date(iter) + dayStart.setHours(fh, fm, fs) + const dayEnd = new Date(iter) + dayEnd.setHours(wh, wm, ws) + + // Find if any interval in this day contains 'now' + if (nowMs >= dayStart.getTime() && nowMs <= dayEnd.getTime()) { + const diffSeconds = (nowMs - dayStart.getTime()) / 1000 + const remainder = diffSeconds % interval + if (remainder <= dur) isRunning = true + } + } else if (rep === 'MONATLICH_FESTER_TAG') { + if (iter.getDate() === 1) { + const [h, m, s] = parseTime(proc.Start) + const d = new Date(iter) + d.setHours(h, m, s) + if (check(d.getTime(), dur)) isRunning = true + } + } + + if (isRunning) count++ + } + + onRunningChange(count) + }, [now, processes, onRunningChange]) + + const { start: viewStart, end: viewEnd, spanHours } = getViewBounds(view, now) + const spanMs = viewEnd.getTime() - viewStart.getTime() + + // Generate Hour Markers + const markers = [] + let mkIter = new Date(viewStart) + + // Dynamic Snapping + if (view === '2H') { + mkIter.setMinutes(Math.floor(mkIter.getMinutes() / 15) * 15, 0, 0) + } else if (view === '6H') { + mkIter.setMinutes(Math.floor(mkIter.getMinutes() / 30) * 30, 0, 0) + } else if (view === 'Monat') { + mkIter.setHours(0, 0, 0, 0) + } else if (view === 'Woche') { + mkIter.setHours(Math.floor(mkIter.getHours() / 6) * 6, 0, 0, 0) + } else { + mkIter.setMinutes(0, 0, 0) + } + + while (mkIter <= viewEnd) { + const pct = ((mkIter.getTime() - viewStart.getTime()) / spanMs) * 100 + let label = '' + + if (view === 'Monat') { + label = mkIter.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit' }) + } else if (view === 'Woche') { + if (mkIter.getHours() === 0) { + label = mkIter.toLocaleDateString('de-DE', { weekday: 'short', day: '2-digit', month: '2-digit' }) + } else { + label = mkIter.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' }) + } + } else { + label = mkIter.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' }) + } + + if (pct >= 0 && pct <= 100) { + markers.push({ key: mkIter.getTime(), pct, label }) + } + + // Dynamic Increment + if (view === '2H') mkIter.setMinutes(mkIter.getMinutes() + 15) + else if (view === '6H') mkIter.setMinutes(mkIter.getMinutes() + 30) + else if (view === 'Woche') mkIter.setHours(mkIter.getHours() + 6) + else if (view === 'Monat') mkIter.setDate(mkIter.getDate() + 1) + else mkIter.setHours(mkIter.getHours() + 1) + } + + // Now line + const nowPct = ((now.getTime() - viewStart.getTime()) / spanMs) * 100 + const isNowVisible = nowPct >= 0 && nowPct <= 100 + + // Group processes by server + const serverGroups = new Map() + for (const p of processes) { + // Only render active processes + if (p.Status?.value && p.Status.value !== 'aktiv') continue + + // APPLY VISIBILITY MAX VIEW FILTER (CRITICAL MEMORY PRESERVATION) + const maxView = p.Sichtbarkeit?.value || 'Immer' + if (maxView === 'Max 6 Stunden') { + if (view === 'Tag' || view === 'Woche' || view === 'Monat' || view === 'Arbeitszeit' || view === 'Nachts') continue + } else if (maxView === 'Max 1 Tag') { + if (view === 'Woche' || view === 'Monat') continue + } + + const sName = p.Server?.[0]?.value || 'Ohne Server' + if (!serverGroups.has(sName)) serverGroups.set(sName, []) + serverGroups.get(sName)!.push(p) + } + + const renderBlocksForProcess = (proc: Process) => { + const rawBlocks: { stMs: number; edMs: number }[] = [] + const rep = proc.Wiederholung?.value + + let iter = new Date(viewStart) + iter.setHours(0, 0, 0, 0) + // Add one day padding + iter.setDate(iter.getDate() - 1) + const iterEnd = new Date(viewEnd) + iterEnd.setDate(iterEnd.getDate() + 1) + + const pushRawBlock = (startTimeStr: Date, durationSecs: number) => { + const stMs = startTimeStr.getTime() + const edMs = stMs + (durationSecs * 1000) + if (edMs < viewStart.getTime() || stMs > viewEnd.getTime()) return + rawBlocks.push({ stMs, edMs }) + } + + while (iter < iterEnd) { + // Apply weekday filter globally + const dayStrings = ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'] + const currentDayStr = dayStrings[iter.getDay()] + if (proc.Wochentage && proc.Wochentage.trim() !== '') { + if (!proc.Wochentage.includes(currentDayStr) && rep !== 'WOCHENENDE' && rep !== 'WERKTAGE') { + iter.setDate(iter.getDate() + 1) + continue + } + } + + if (rep === 'TAEGLICH') { + const [h, m, s] = parseTime(proc.Start) + const d = new Date(iter) + d.setHours(h, m, s) + pushRawBlock(d, parseInt(proc.Dauer || '0') || 60) + } else if (rep === 'STUENDLICH') { + for (let hh = 0; hh < 24; hh++) { + const d = new Date(iter) + d.setHours(hh, proc['Start Minute'] || 0, 0) + pushRawBlock(d, Math.max(1, parseInt(proc.Dauer || '0') || 60)) + } + } else if (rep === 'WOECHENTLICH') { + const [h, m, s] = parseTime(proc.Start) + const d = new Date(iter) + d.setHours(h, m, s) + pushRawBlock(d, parseInt(proc.Dauer || '0') || 60) + } else if (rep === 'WERKTAGE') { + const day = iter.getDay() + if (day >= 1 && day <= 5) { + const [h, m, s] = parseTime(proc.Start) + const d = new Date(iter) + d.setHours(h, m, s) + pushRawBlock(d, parseInt(proc.Dauer || '0') || 60) + } + } else if (rep === 'WOCHENENDE') { + const day = iter.getDay() + if (day === 0 || day === 6) { + const [h, m, s] = parseTime(proc.Start) + const d = new Date(iter) + d.setHours(h, m, s) + pushRawBlock(d, parseInt(proc.Dauer || '0') || 60) + } + } else if (rep === 'INTERVALL') { + const [fh, fm, fs] = parseTime(proc['Erste AusfΓΌhrung']) + const [wh, wm, ws] = parseTime(proc['AusfΓΌhrung bis'] || '23:59:59') + const interval = Math.max(1, parseInt(proc['Intervall zwischen'] || '0') || 60) + const dur = parseInt(proc.Dauer || '0') || 60 + + const dayStart = new Date(iter) + dayStart.setHours(fh, fm, fs) + const dayEnd = new Date(iter) + dayEnd.setHours(wh, wm, ws) + + for (let t = dayStart.getTime(); t <= dayEnd.getTime(); t += interval * 1000) { + pushRawBlock(new Date(t), dur) + } + } else if (rep === 'MONATLICH_FESTER_TAG') { + if (iter.getDate() === 1) { + const [h, m, s] = parseTime(proc.Start) + const d = new Date(iter) + d.setHours(h, m, s) + pushRawBlock(d, parseInt(proc.Dauer || '0') || 60) + } + } else { + const [h, m, s] = parseTime(proc.Start || '12:00:00') + const d = new Date(iter) + d.setHours(h, m, s) + pushRawBlock(d, parseInt(proc.Dauer || '0') || 30) + } + iter.setDate(iter.getDate() + 1) + } + + // Sort blocks by start time + rawBlocks.sort((a, b) => a.stMs - b.stMs) + + // Merge blocks that are overlapping or too close to render individually (e.g. gap < 0.2% of screen) + const mergedBlocks: { stMs: number; edMs: number }[] = [] + // 0.2% of total span ms is threshold for merging + const mergeThreshold = spanMs * 0.002 + + for (const b of rawBlocks) { + if (mergedBlocks.length === 0) { + mergedBlocks.push({ ...b }) + } else { + const prev = mergedBlocks[mergedBlocks.length - 1] + // If start is before or overlaps with prev.end + visual threshold + if (b.stMs <= prev.edMs + mergeThreshold) { + prev.edMs = Math.max(prev.edMs, b.edMs) + } else { + mergedBlocks.push({ ...b }) + } + } + } + + // Now map them to standard JSX layout + return mergedBlocks.map(b => { + const left = Math.max(0, ((b.stMs - viewStart.getTime()) / spanMs) * 100) + const rightBounds = Math.min(viewEnd.getTime(), b.edMs) + let width = ((rightBounds - Math.max(viewStart.getTime(), b.stMs)) / spanMs) * 100 + + if (width < 0.2) width = 0.2 + + return ( +
+ {width > 5 && {proc.Name}} +
+ ) + }) + } + + return ( +
+ + {/* Zoom controls */} +
+

Timeline Setup

+
+ {(['2H', '6H', 'Arbeitszeit', 'Nachts', 'Tag', 'Woche', 'Monat'] as ViewMode[]).map(v => ( + + ))} +
+
+ +
+ {/* Header markers */} +
+ {markers.map(m => ( +
+ {m.label} +
+ ))} +
+ + {/* Chart body */} +
+ {isNowVisible && ( +
+
+
+ )} + + {Array.from(serverGroups.entries()) + .sort(([sNameA], [sNameB]) => { + if (!servers) return 0 + const sA = servers.find(s => s.Name === sNameA) + const sB = servers.find(s => s.Name === sNameB) + return (sA?.Sortierung ?? 9999) - (sB?.Sortierung ?? 9999) + }) + .map(([serverName, procs]) => { + const sortedProcs = [...procs].sort((a, b) => (a.Sortierung ?? 9999) - (b.Sortierung ?? 9999)) + + return ( +
+
+
+ {SERVER_ICON} + {serverName} +
+ {sortedProcs.length > 0 && ( +
+ {sortedProcs.map(p => ( + + + {p.Name} + + ))} +
+ )} +
+
+ {sortedProcs.map(p => ( + + {renderBlocksForProcess(p)} + + ))} +
+
+ )})} + + {serverGroups.size === 0 && ( +
+ Keine aktiven Prozesse in dieser Ansicht sichtbar. +
+ )} +
+
+
+ ) +} diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx new file mode 100644 index 0000000..e146a4f --- /dev/null +++ b/src/components/Sidebar.tsx @@ -0,0 +1,76 @@ +import { useLocation, Link } from 'react-router-dom' +import { useState, useEffect } from 'react' + +const navItems = [ + { + href: '/', + label: 'Timeline', + icon: ( + + + + ), + }, + { + href: '/servers', + label: 'Server', + icon: ( + + + + ), + }, + { + href: '/processes', + label: 'Prozesse', + icon: ( + + + + ), + }, +] + +export default function Sidebar() { + const { pathname } = useLocation() + const [theme, setTheme] = useState(() => localStorage.getItem('theme') || 'dark') + + useEffect(() => { + document.documentElement.setAttribute('data-theme', theme) + localStorage.setItem('theme', theme) + }, [theme]) + + const toggleTheme = () => setTheme(t => (t === 'dark' ? 'light' : 'dark')) + + return ( + + ) +} diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..072fb21 --- /dev/null +++ b/src/index.css @@ -0,0 +1,783 @@ +/* ══════════════════════════════════════════════════════ + AZion Scheduler β€” Design System + Dark + Light themes, vanilla CSS + ══════════════════════════════════════════════════════ */ + +/* ── Reset & Base ──────────────────────────────────── */ +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +:root { + --font: 'Corbel', 'Segoe UI', system-ui, -apple-system, sans-serif; + --radius: 8px; + --radius-lg: 12px; + --transition: 150ms ease; + + /* ── Dark theme (default) ── */ + --bg: hsl(220 16% 10%); + --bg-card: hsl(220 16% 13%); + --bg-sidebar: hsl(220 18% 8%); + --bg-topbar: hsl(220 18% 9%); + --bg-input: hsl(220 14% 16%); + --bg-hover: hsl(220 14% 18%); + + --fg: hsl(210 20% 93%); + --fg-muted: hsl(215 16% 55%); + --fg-dim: hsl(215 10% 40%); + + --border: hsl(220 14% 20%); + --border-light: hsl(220 14% 16%); + + --primary: hsl(4 87% 47%); + --primary-fg: #fff; + --primary-dim: hsl(4 87% 47% / 0.15); + + --green: hsl(142 71% 45%); + --yellow: hsl(38 92% 50%); + --blue: hsl(217 91% 60%); + + color-scheme: dark; +} + +/* ── Light theme ───────────────────────────────────── */ +[data-theme="light"] { + --bg: hsl(220 14% 96%); + --bg-card: hsl(0 0% 100%); + --bg-sidebar: hsl(0 0% 100%); + --bg-topbar: hsl(0 0% 99%); + --bg-input: hsl(220 14% 93%); + --bg-hover: hsl(220 10% 90%); + + --fg: hsl(220 25% 12%); + --fg-muted: hsl(215 16% 47%); + --fg-dim: hsl(215 10% 65%); + + --border: hsl(220 14% 85%); + --border-light: hsl(220 14% 90%); + + color-scheme: light; +} + +html { + font-family: var(--font); + font-size: 14px; +} + +body { + background: var(--bg); + color: var(--fg); + min-height: 100vh; + transition: background var(--transition), color var(--transition); + -webkit-font-smoothing: antialiased; +} + +a { + color: inherit; + text-decoration: none; +} + +button { + font: inherit; + cursor: pointer; + border: none; + background: none; + color: inherit; +} + +input, +select, +textarea { + font: inherit; + color: var(--fg); + background: var(--bg-input); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 8px 12px; + outline: none; + transition: border-color var(--transition); +} + +input:focus, +select:focus, +textarea:focus { + border-color: var(--primary); +} + +/* ── Scrollbar ─────────────────────────────────────── */ +::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +::-webkit-scrollbar-track { + background: var(--bg); +} + +::-webkit-scrollbar-thumb { + background: var(--border); + border-radius: 3px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--primary); +} + +/* ── App Layout ────────────────────────────────────── */ +.app-layout { + display: flex; + min-height: 100vh; +} + +/* ── Sidebar ───────────────────────────────────────── */ +.sidebar { + width: 220px; + flex-shrink: 0; + display: flex; + flex-direction: column; + background: var(--bg-sidebar); + border-right: 1px solid var(--border); +} + +.sidebar-logo { + padding: 16px 20px; + border-bottom: 1px solid var(--border); + display: flex; + flex-direction: column; + gap: 0px; +} + +.sidebar-logo a { + display: flex; + align-items: center; + gap: 0px; +} + +.sidebar-logo img { + height: 28px; + width: auto; +} + +.sidebar-logo .logo-suffix { + font-size: 42px; + font-weight: 700; + letter-spacing: -0.02em; +} + +.sidebar-logo .subtitle { + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.15em; + color: var(--fg-muted); + font-weight: 500; + padding-left: 2px; +} + +.sidebar-nav { + flex: 1; + padding: 12px 8px; + display: flex; + flex-direction: column; + gap: 2px; +} + +.sidebar-nav a { + display: flex; + align-items: center; + gap: 10px; + padding: 9px 12px; + border-radius: var(--radius); + font-size: 13px; + font-weight: 1000; + color: var(--fg-muted); + transition: all var(--transition); + border-left: 3px solid transparent; +} + +.sidebar-nav a:hover { + background: var(--bg-hover); + color: var(--fg); +} + +.sidebar-nav a.active { + background: var(--primary-dim); + color: var(--primary); + border-left-color: var(--primary); +} + +.sidebar-nav a svg { + width: 16px; + height: 16px; + flex-shrink: 0; +} + +.sidebar-footer { + padding: 12px 16px; + border-top: 1px solid var(--border); + display: flex; + flex-direction: column; + gap: 10px; +} + +.sidebar-footer .theme-row { + display: flex; + align-items: center; + justify-content: space-between; + font-size: 11px; + color: var(--fg-muted); + font-weight: 500; +} + +.sidebar-footer .version { + display: flex; + align-items: center; + gap: 6px; + font-size: 10px; + color: var(--fg-dim); + font-family: monospace; +} + +.sidebar-footer .version img { + height: 14px; + opacity: 0.6; +} + +/* ── Theme Toggle ──────────────────────────────────── */ +.theme-toggle { + width: 40px; + height: 22px; + border-radius: 11px; + background: var(--border); + position: relative; + transition: background var(--transition); + cursor: pointer; +} + +.theme-toggle::after { + content: ''; + position: absolute; + top: 3px; + left: 3px; + width: 16px; + height: 16px; + border-radius: 50%; + background: var(--primary); + transition: transform var(--transition); +} + +[data-theme="light"] .theme-toggle::after { + transform: translateX(18px); +} + +/* ── Topbar ────────────────────────────────────────── */ +.topbar { + position: sticky; + top: 0; + z-index: 40; + display: flex; + align-items: center; + justify-content: space-between; + padding: 8px 24px; + border-bottom: 1px solid var(--border); + background: var(--bg-topbar); + backdrop-filter: blur(12px); +} + +.topbar .status { + display: flex; + align-items: center; + gap: 8px; + font-size: 16px; + color: var(--fg-muted); +} + +.topbar .status-dot { + width: 7px; + height: 7px; + border-radius: 50%; + background: var(--green); + box-shadow: 0 0 6px hsl(142 71% 45% / 0.7); + animation: pulse 2s infinite; +} + +@keyframes pulse { + + 0%, + 100% { + opacity: 1; + } + + 50% { + opacity: 0.5; + } +} + +.topbar .badge { + font-size: 12px; + font-family: monospace; + color: var(--fg-dim); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 3px 8px; +} + +/* ── Main Content ──────────────────────────────────── */ +.main { + flex: 1; + overflow: auto; + min-height: 100vh; +} + +.main-content { + padding: 24px; +} + +/* ── Page Header ───────────────────────────────────── */ +.page-header { + display: flex; + align-items: flex-end; + justify-content: space-between; + margin-bottom: 24px; +} + +.page-header h1 { + font-size: 24px; + font-weight: 700; + letter-spacing: -0.02em; +} + +.page-header p { + font-size: 13px; + color: var(--fg-muted); + margin-top: 2px; +} + +/* ── Buttons ───────────────────────────────────────── */ +.btn { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 8px 16px; + border-radius: var(--radius); + font-size: 16px; + font-weight: 600; + transition: all var(--transition); +} + +.btn-primary { + background: var(--primary); + color: var(--primary-fg); + box-shadow: 0 1px 3px hsl(4 87% 47% / 0.3); +} + +.btn-primary:hover { + filter: brightness(1.1); +} + +.btn-ghost { + background: var(--primary-dim); + color: var(--primary); + border: 1px solid hsl(4 87% 47% / 0.3); +} + +.btn-ghost:hover { + background: hsl(4 87% 47% / 0.25); +} + +.btn-danger { + background: transparent; + color: hsl(0 84% 60%); + padding: 6px 10px; + font-size: 12px; + border-radius: var(--radius); +} + +.btn-danger:hover { + background: hsl(0 84% 60% / 0.1); +} + +.btn-sm { + padding: 5px 10px; + font-size: 12px; +} + +/* ── KPI Cards ─────────────────────────────────────── */ +.kpi-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + gap: 12px; + margin-bottom: 24px; +} + +.kpi-card { + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + padding: 16px 20px; + display: flex; + flex-direction: column; + gap: 8px; +} + +.kpi-card .kpi-label { + font-size: 12px; + font-weight: 500; + color: var(--fg-muted); + display: flex; + align-items: center; + gap: 6px; +} + +.kpi-card .kpi-label svg { + width: 14px; + height: 14px; +} + +.kpi-card .kpi-value { + font-size: 28px; + font-weight: 700; +} + +/* ── Cards Grid ────────────────────────────────────── */ +.cards-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 16px; +} + +.card { + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + padding: 20px; + transition: border-color var(--transition); +} + +.card:hover { + border-color: hsl(4 87% 47% / 0.3); +} + +.card h3 { + font-size: 16px; + font-weight: 700; + margin-bottom: 4px; +} + +.card .desc { + font-size: 12px; + color: var(--fg-muted); + margin-bottom: 12px; +} + +.card-footer { + display: flex; + align-items: center; + justify-content: space-between; + padding-top: 12px; + border-top: 1px solid var(--border); + margin-top: 12px; +} + +.card-footer .tag { + font-size: 12px; + background: var(--bg-hover); + padding: 3px 8px; + border-radius: var(--radius); +} + +/* ── Table ─────────────────────────────────────────── */ +.data-table { + width: 100%; + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + overflow: hidden; + border-collapse: collapse; +} + +.data-table th { + text-align: left; + padding: 12px 16px; + font-size: 12px; + font-weight: 600; + color: var(--fg-muted); + background: var(--bg-hover); + border-bottom: 1px solid var(--border); +} + +.data-table td { + padding: 12px 16px; + border-bottom: 1px solid var(--border-light); + font-size: 13px; +} + +.data-table tr:last-child td { + border-bottom: none; +} + +.data-table tr:hover td { + background: hsl(0 0% 50% / 0.03); +} + +.status-badge { + display: inline-block; + padding: 2px 10px; + border-radius: 999px; + font-size: 11px; + font-weight: 600; +} + +.status-badge.aktiv { + background: hsl(142 71% 45% / 0.15); + color: var(--green); +} + +.status-badge.inaktiv { + background: hsl(0 0% 50% / 0.1); + color: var(--fg-muted); +} + +.color-dot { + width: 10px; + height: 10px; + border-radius: 50%; + display: inline-block; + flex-shrink: 0; +} + +.rep-tag { + font-size: 11px; + background: var(--bg-hover); + padding: 2px 8px; + border-radius: var(--radius); + font-family: var(--font); +} + +/* ── Add Form Row ──────────────────────────────────── */ +.add-form { + display: flex; + gap: 8px; + align-items: flex-end; + flex-wrap: wrap; + margin-bottom: 20px; + padding: 16px; + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius-lg); +} + +.add-form .field { + display: flex; + flex-direction: column; + gap: 4px; +} + +.add-form .field label { + font-size: 11px; + font-weight: 600; + color: var(--fg-muted); + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.add-form input, +.add-form select { + min-width: 140px; +} + +/* ── Gantt Chart ───────────────────────────────────── */ +.gantt-container { + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + overflow: hidden; + font-family: var(--font); + font-size: 11px; +} + +.gantt-scroll-window { + position: relative; + overflow-x: auto; + border-top: 1px solid var(--border); + min-height: 400px; +} + +.gantt-header { + position: sticky; + top: 0; + z-index: 20; + height: 36px; + background: hsl(0 0% 0% / 0.04); + border-bottom: 1px solid var(--border-light); + margin-left: 170px; + /* Space for labels */ +} + +[data-theme="light"] .gantt-header { + background: hsl(0 0% 0% / 0.02); +} + +.gantt-tick { + position: absolute; + top: 0; + bottom: 0; + border-left: 1px solid var(--border-root); + border-left: 1px solid hsl(0 0% 50% / 0.2); + padding-left: 4px; + padding-top: 8px; +} + +.gantt-tick-label { + font-size: 10px; + color: var(--fg-muted); + font-weight: 600; + transform: translateX(-50%); + display: inline-block; + white-space: nowrap; +} + +.gantt-body { + position: relative; + min-height: 100px; + padding-bottom: 80px; + /* buffer */ +} + +.gantt-now-line { + position: absolute; + top: 0; + bottom: 0; + width: 2px; + background-color: var(--green); + z-index: 100; + box-shadow: 0 0 8px var(--green); + opacity: 0.8; + pointer-events: none; +} + +.gantt-row-group { + margin-bottom: 8px; +} + +.gantt-group-header { + background: hsl(0 0% 0% / 0.08); + font-weight: 700; + font-size: 12px; + padding: 8px 16px; + display: flex; + align-items: center; + gap: 8px; + color: var(--fg); + border-bottom: 1px solid var(--border-light); + border-top: 1px solid var(--border-light); +} + +[data-theme="light"] .gantt-group-header { + background: hsl(0 0% 0% / 0.03); +} + +.gantt-row { + display: flex; + min-height: 44px; + border-bottom: 1px dashed hsl(0 0% 50% / 0.15); + background: transparent; +} + +.gantt-row:hover { + background: hsl(0 0% 50% / 0.03); +} + +.gantt-row-info { + width: 170px; + flex-shrink: 0; + padding: 8px 12px; + display: flex; + align-items: center; + gap: 6px; + border-right: 1px solid var(--border-light); + background: var(--bg-card); + position: sticky; + left: 0; + z-index: 15; +} + +.gantt-bars-area { + flex: 1; + position: relative; + min-height: 44px; +} + +.gantt-block { + position: absolute; + top: 8px; + bottom: 8px; + border-radius: 4px; + min-width: 2px; + z-index: 10; + display: flex; + align-items: center; + overflow: hidden; + padding: 0 4px; + transition: transform 100ms ease; + cursor: pointer; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); +} + +.gantt-block:hover { + z-index: 50; + transform: scaleY(1.1); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); + filter: brightness(1.2); +} + +.gantt-block-label { + font-size: 10px; + color: #fff; + font-weight: 600; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.7); + white-space: nowrap; + pointer-events: none; + overflow: hidden; + text-overflow: ellipsis; +} + +/* ── Empty State ───────────────────────────────────── */ +.empty-state { + grid-column: 1 / -1; + text-align: center; + padding: 48px; + color: var(--fg-muted); + font-size: 13px; + border: 1px dashed var(--border); + border-radius: var(--radius-lg); +} + +/* ── Loading ───────────────────────────────────────── */ +.loading { + display: flex; + align-items: center; + justify-content: center; + padding: 64px; + color: var(--fg-muted); + font-size: 13px; + gap: 8px; +} + +.spinner { + width: 16px; + height: 16px; + border: 2px solid var(--border); + border-top-color: var(--primary); + border-radius: 50%; + animation: spin 0.6s linear infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} \ No newline at end of file diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..38d3698 --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,13 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import { BrowserRouter } from 'react-router-dom' +import App from './App' +import './index.css' + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + + + +) diff --git a/src/pages/Processes.tsx b/src/pages/Processes.tsx new file mode 100644 index 0000000..7835aa1 --- /dev/null +++ b/src/pages/Processes.tsx @@ -0,0 +1,307 @@ +import { useState, useEffect, useCallback } from 'react' +import { api, type Process, type Server } from '../api/baserow' + +const REPETITIONS = [ + 'TAEGLICH', 'STUENDLICH', 'INTERVALL', 'WERKTAGE', 'WOCHENENDE', + 'WOECHENTLICH', 'MONATLICH_FESTER_TAG', 'BENUTZERDEFINIERT' +] + +const MAX_VIEWS = [ + 'Immer', + 'Max 1 Woche', + 'Max 1 Tag', + 'Max 6 Stunden' +] + +export default function Processes() { + const [processes, setProcesses] = useState([]) + const [servers, setServers] = useState([]) + const [loading, setLoading] = useState(true) + const [saving, setSaving] = useState(false) + + // Form state + const [editingId, setEditingId] = useState(null) + const [name, setName] = useState('') + const [serverId, setServerId] = useState('') + const [status, setStatus] = useState('aktiv') + const [rep, setRep] = useState('TAEGLICH') + const [color, setColor] = useState('#3b82f6') + const [sichtbarkeit, setSichtbarkeit] = useState('Immer') + + // Conditional fields + const [startTime, setStartTime] = useState('') + const [endTime, setEndTime] = useState('') + const [firstExec, setFirstExec] = useState('') + const [execUntil, setExecUntil] = useState('') + const [intervalBetween, setIntervalBetween] = useState('') + const [startMinute, setStartMinute] = useState('') + const [duration, setDuration] = useState('') + const [weekdays, setWeekdays] = useState('') + + const load = useCallback(async () => { + try { + const [p, s] = await Promise.all([api.getProcesses(), api.getServers()]) + setProcesses(p) + setServers(s) + } finally { + setLoading(false) + } + }, []) + + useEffect(() => { load() }, [load]) + + const editProcess = (p: Process) => { + setEditingId(p.id) + setName(p.Name || '') + setServerId(p.Server?.[0]?.id || '') + setStatus(p.Status?.value || 'aktiv') + setRep(p.Wiederholung?.value || 'TAEGLICH') + setColor(p.Farbe || '#3b82f6') + setSichtbarkeit(p.Sichtbarkeit?.value || 'Immer') + + setStartTime(p.Start || '') + setEndTime(p.Ende || '') + setFirstExec(p['Erste AusfΓΌhrung'] || '') + setExecUntil(p['AusfΓΌhrung bis'] || '') + setIntervalBetween(p['Intervall zwischen'] || '') + setStartMinute(p['Start Minute'] !== null && p['Start Minute'] !== undefined ? String(p['Start Minute']) : '') + setDuration(p.Dauer || '') + setWeekdays(p.Wochentage || '') + + window.scrollTo({ top: 0, behavior: 'smooth' }) + } + + const cancelEdit = () => { + setEditingId(null) + setName('') + setStartTime('') + setEndTime('') + setFirstExec('') + setExecUntil('') + setIntervalBetween('') + setStartMinute('') + setDuration('') + setWeekdays('') + setSichtbarkeit('Immer') + } + + const handleSave = async (e: React.FormEvent) => { + e.preventDefault() + if (!name.trim() || !serverId) return + setSaving(true) + + const payload: any = { + Name: name.trim(), + Server: [serverId], + Status: status, + Wiederholung: rep, + Farbe: color, + Sichtbarkeit: sichtbarkeit, + Dauer: duration || null, + Wochentage: weekdays || null, + Start: startTime || null, + Ende: endTime || null, + 'Start Minute': startMinute ? parseInt(startMinute) : null, + 'Erste AusfΓΌhrung': firstExec || null, + 'AusfΓΌhrung bis': execUntil || null, + 'Intervall zwischen': intervalBetween || null, + } + + try { + if (editingId) { + await api.updateProcess(editingId, payload) + } else { + const maxSort = processes.reduce((max, p) => Math.max(max, p.Sortierung ?? 0), 0) + payload.Sortierung = maxSort + 1 + await api.createProcess(payload) + } + cancelEdit() + await load() + } catch (err: any) { + alert('Fehler beim Speichern. Hast du das Feld "Max View" in Baserow als Single-Select mit den richtigen Werten (z.B. "Immer") angelegt?\n\nDetails: ' + err.message) + } finally { + setSaving(false) + } + } + + const handleDelete = async (id: number, procName: string) => { + if (!confirm(`Prozess "${procName}" wirklich lΓΆschen?`)) return + await api.deleteProcess(id) + await load() + } + + if (loading) { + return
Laden…
+ } + + return ( +
+
+
+

Prozesse

+

Automatisierte Scripts und Backup-Tasks

+
+
+ + {/* Add/Edit form */} +
+
+
+ + setName(e.target.value)} placeholder="Prozessname" required /> +
+
+ + +
+
+ + +
+
+ + +
+
+ + setColor(e.target.value)} style={{ width: 40, padding: 2, height: 34 }} /> +
+
+ + +
+
+ + {/* Dynamic Fields row based on chosen Wiederholung */} +
+ {/* Default Start */} + {(rep === 'TAEGLICH' || rep === 'WOECHENTLICH' || rep === 'WERKTAGE' || rep === 'WOCHENENDE' || rep === 'MONATLICH_FESTER_TAG' || rep === 'BENUTZERDEFINIERT') && ( +
+ + setStartTime(e.target.value)} placeholder="08:00:00" style={{ width: 120 }} /> +
+ )} + + {/* Stuendlich */} + {rep === 'STUENDLICH' && ( +
+ + setStartMinute(e.target.value)} placeholder="45" style={{ width: 100 }} /> +
+ )} + + {/* Intervall */} + {rep === 'INTERVALL' && ( + <> +
+ + setFirstExec(e.target.value)} placeholder="00:00:00" style={{ width: 120 }} /> +
+
+ + setExecUntil(e.target.value)} placeholder="23:59:59" style={{ width: 120 }} /> +
+
+ + setIntervalBetween(e.target.value)} placeholder="3600" style={{ width: 120 }} /> +
+ + )} + + {/* Universal Dauer */} +
+ + setDuration(e.target.value)} placeholder="60" style={{ width: 120 }} /> +
+ + {/* Wochentage */} + {(rep === 'WOECHENTLICH' || rep === 'INTERVALL' || rep === 'BENUTZERDEFINIERT') && ( +
+ + setWeekdays(e.target.value)} placeholder="Mo,Di,Mi,Do,Fr" style={{ width: 140 }} /> +
+ )} +
+ +
+ + {editingId && ( + + )} +
+
+ + {/* Process table */} + {processes.length > 0 ? (() => { + const sortedProcesses = processes.slice().sort((a, b) => { + const sNameA = a.Server?.[0]?.value || '' + const sA = servers.find(s => s.Name === sNameA) + const sortA = sA?.Sortierung ?? 9999 + + const sNameB = b.Server?.[0]?.value || '' + const sB = servers.find(s => s.Name === sNameB) + const sortB = sB?.Sortierung ?? 9999 + + if (sortA !== sortB) return sortA - sortB + return (a.Sortierung ?? 9999) - (b.Sortierung ?? 9999) + }) + + return ( + + + + + + + + + + + + + + {sortedProcesses.map(p => ( + + + + + + + + + + ))} + +
StatusServerProzessnameWiederholungFarbeSichtbarkeitAktionen
+ + {p.Status?.value || '–'} + + {p.Server?.[0]?.value || '–'} +
+ {p.Name || '(Kein Name)'} +
+
{p.Wiederholung?.value || '–'}{p.Sichtbarkeit?.value || 'Immer'} + + +
+ ) + })() : ( +
Noch keine Prozesse angelegt.
+ )} +
+ ) +} diff --git a/src/pages/Servers.tsx b/src/pages/Servers.tsx new file mode 100644 index 0000000..e5bd0ff --- /dev/null +++ b/src/pages/Servers.tsx @@ -0,0 +1,105 @@ +import { useState, useEffect, useCallback } from 'react' +import { api, type Server } from '../api/baserow' + +export default function Servers() { + const [servers, setServers] = useState([]) + const [loading, setLoading] = useState(true) + const [name, setName] = useState('') + const [desc, setDesc] = useState('') + const [saving, setSaving] = useState(false) + + const load = useCallback(async () => { + try { + setServers(await api.getServers()) + } finally { + setLoading(false) + } + }, []) + + useEffect(() => { load() }, [load]) + + const handleAdd = async (e: React.FormEvent) => { + e.preventDefault() + if (!name.trim()) return + setSaving(true) + try { + await api.createServer(name.trim(), desc.trim()) + setName('') + setDesc('') + await load() + } finally { + setSaving(false) + } + } + + const handleDelete = async (id: number, serverName: string) => { + if (!confirm(`Server "${serverName}" wirklich lΓΆschen?`)) return + await api.deleteServer(id) + await load() + } + + if (loading) { + return
Laden…
+ } + + return ( +
+
+
+

Server

+

Server verwalten und ΓΌberwachen

+
+
+ + {/* Add form */} +
+
+ + setName(e.target.value)} + placeholder="Server-Name" + required + /> +
+
+ + setDesc(e.target.value)} + placeholder="Optional" + /> +
+ +
+ + {/* Server cards */} +
+ {servers.map(s => ( +
+

{s.Name || '(Kein Name)'}

+ {s.Beschreibung &&
{s.Beschreibung}
} +
+ {s.Prozesse?.length || 0} Prozesse + +
+
+ ))} + {servers.length === 0 && ( +
+ Noch keine Server konfiguriert. FΓΌge einen hinzu! +
+ )} +
+
+ ) +} diff --git a/src/pages/Timeline.tsx b/src/pages/Timeline.tsx new file mode 100644 index 0000000..b18a5f8 --- /dev/null +++ b/src/pages/Timeline.tsx @@ -0,0 +1,84 @@ +import { useState, useEffect, useCallback } from 'react' +import { Link } from 'react-router-dom' +import { api, type Server, type Process } from '../api/baserow' +import GanttChart from '../components/GanttChart' + +export default function Timeline() { + const [servers, setServers] = useState([]) + const [processes, setProcesses] = useState([]) + const [loading, setLoading] = useState(true) + const [runningCount, setRunningCount] = useState(0) + + const load = useCallback(async () => { + try { + const [s, p] = await Promise.all([api.getServers(), api.getProcesses()]) + setServers(s) + setProcesses(p) + } catch (e) { + console.error('Failed to load data:', e) + } finally { + setLoading(false) + } + }, []) + + useEffect(() => { + load() + const interval = setInterval(load, 30_000) // Auto-refresh every 30s + return () => clearInterval(interval) + }, [load]) + + const activeCount = processes.filter(p => p.Status?.value === 'aktiv').length + + if (loading) { + return
Daten werden geladen…
+ } + + return ( +
+
+
+

Prozess-Zeitplan

+

24-Stunden Übersicht aller Server-Prozesse

+
+ + Server verwalten +
+ + {/* KPI Cards */} +
+
+
+ + Server +
+
{servers.length}
+
+ +
+
+ + Prozesse gesamt +
+
{processes.length}
+
+ +
+
+ + Aktive Prozesse +
+
{activeCount}
+
+ +
+
+ + Laufen gerade +
+
{runningCount}
+
+
+ + +
+ ) +} diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/start.bat b/start.bat new file mode 100644 index 0000000..37bb554 --- /dev/null +++ b/start.bat @@ -0,0 +1,5 @@ +@echo off +echo Starting AZion Scheduler... +set "PATH=%LOCALAPPDATA%\nodejs-portable\node-v22.15.0-win-x64;%PATH%" +npm run dev +pause diff --git a/test-browser.js b/test-browser.js new file mode 100644 index 0000000..eece357 --- /dev/null +++ b/test-browser.js @@ -0,0 +1,10 @@ +const puppeteer = require('puppeteer'); +(async () => { + const browser = await puppeteer.launch(); + const page = await browser.newPage(); + page.on('console', msg => console.log('PAGE LOG:', msg.text())); + page.on('pageerror', error => console.log('PAGE ERROR:', error.message)); + page.on('requestfailed', request => console.log('REQUEST FAILED:', request.url(), request.failure().errorText)); + await page.goto('http://localhost:3001', {waitUntil: 'networkidle0'}).catch(e => console.log('GOTO ERROR:', e.message)); + await browser.close(); +})(); diff --git a/test-time.js b/test-time.js new file mode 100644 index 0000000..45d6869 --- /dev/null +++ b/test-time.js @@ -0,0 +1,9 @@ +function parseTime(tStr) { + if (!tStr) return [12, 0] + const p = tStr.toString().split(':').map(Number) + return [isNaN(p[0]) ? 12 : p[0], isNaN(p[1]) ? 0 : p[1]] +} + +console.log(parseTime('00:00:30')); +console.log(parseTime('07:23')); +console.log(parseTime('')); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..75e2ff7 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noFallthroughCasesInSwitch": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..3244652 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +export default defineConfig({ + plugins: [react()], + server: { + port: 3001, + watch: { + usePolling: true + } + }, + resolve: { + preserveSymlinks: true + } +})