Compare commits
69
Commits
main
..
1a8604f7e6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a8604f7e6 | ||
|
|
34c4db0228 | ||
|
|
062d992b63 | ||
|
|
bf007df737 | ||
|
|
7b33e3900d | ||
|
|
7852593a7a | ||
|
|
df62bcbc55 | ||
|
|
b758b1f9a7 | ||
|
|
889a2f42eb | ||
|
|
d684aea576 | ||
|
|
8d2a99e5d6 | ||
|
|
cbf77004ad | ||
|
|
2d4e569cc3 | ||
|
|
0c884a57d7 | ||
|
|
149b38b94b | ||
|
|
e678899141 | ||
|
|
8cb0bb8146 | ||
|
|
d6becf0151 | ||
|
|
d86f9c07cd | ||
|
|
cf1d653eb2 | ||
|
|
d42ee939e9 | ||
|
|
19464fe20d | ||
|
|
98601b4b3d | ||
|
|
3cc6ab3343 | ||
|
|
d7f9cf75a3 | ||
|
|
5361800ca9 | ||
|
|
52713fb481 | ||
|
|
e59a066d3c | ||
|
|
fe3e39bd04 | ||
|
|
4c305fcd46 | ||
|
|
0b24596f15 | ||
|
|
1cec319fa8 | ||
|
|
5786eed3ba | ||
|
|
5270084ff0 | ||
|
|
8855fe92ba | ||
|
|
612063455e | ||
|
|
9e4c60fdaf | ||
|
|
86fb24b1a9 | ||
|
|
6c3f03b5b4 | ||
|
|
42f729abfc | ||
|
|
ff536b048f | ||
|
|
4be20237a1 | ||
|
|
f76b738f5f | ||
|
|
27b365622e | ||
|
|
a1cb90372f | ||
|
|
7d764ce6bc | ||
|
|
1006824375 | ||
|
|
43a8064ad5 | ||
|
|
d20a4de117 | ||
|
|
8f6927fbb0 | ||
|
|
8abf11ad18 | ||
|
|
4a2e94bc83 | ||
|
|
fcd13e6a40 | ||
|
|
fda673702e | ||
|
|
8d62e353cb | ||
|
|
f5257cd251 | ||
|
|
73d6c50b8d | ||
|
|
f174421c63 | ||
|
|
d2e7088146 | ||
|
|
4f4de7f290 | ||
|
|
5fbb2fb4b5 | ||
|
|
a4c64b5398 | ||
|
|
cb110a184b | ||
|
|
99b89bcabe | ||
|
|
895c55399f | ||
|
|
ee7c866616 | ||
|
|
5717612db5 | ||
|
|
3f8fce3c02 | ||
|
|
0e19df6895 |
No files matched your search
@@ -0,0 +1,5 @@
|
|||||||
|
# Öffentliche Vite-Variablen für lokale Entwicklung (in Git, kein Secret)
|
||||||
|
VITE_APPWRITE_ENDPOINT=http://localhost:5173/v1
|
||||||
|
VITE_APPWRITE_PROJECT_ID=6a1058610003c5a13a05
|
||||||
|
VITE_APPWRITE_DATABASE_ID=woms-database
|
||||||
|
VITE_APPWRITE_BUCKET_ID=woms-attachments
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# Kopieren nach .env.local (wird nicht von Git versioniert):
|
||||||
|
# cp .env.example .env.local
|
||||||
|
#
|
||||||
|
# Lokal mit Vite-Proxy (empfohlen � Session-Cookies wie auf dem Server):
|
||||||
|
VITE_APPWRITE_ENDPOINT=http://localhost:5173/v1
|
||||||
|
VITE_APPWRITE_PROJECT_ID=6a1058610003c5a13a05
|
||||||
|
VITE_APPWRITE_DATABASE_ID=woms-database
|
||||||
|
VITE_APPWRITE_BUCKET_ID=woms-attachments
|
||||||
|
|
||||||
|
# Optional: Proxy-Ziel (Standard: https://appwrite.webklar.com)
|
||||||
|
# VITE_APPWRITE_PROXY_TARGET=https://appwrite.webklar.com
|
||||||
|
|
||||||
|
# Produktion / Build (ohne Vite-Proxy):
|
||||||
|
# VITE_APPWRITE_ENDPOINT=https://ticket.webklar.com/v1
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Öffentliche Vite-Variablen für Production-Build (in Git, kein Secret)
|
||||||
|
VITE_APPWRITE_ENDPOINT=https://ticket.webklar.com/v1
|
||||||
|
VITE_APPWRITE_PROJECT_ID=6a1058610003c5a13a05
|
||||||
|
VITE_APPWRITE_DATABASE_ID=woms-database
|
||||||
|
VITE_APPWRITE_BUCKET_ID=woms-attachments
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Environment variables (persönliche Overrides — nicht committen)
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Build output
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"enabled": false,
|
||||||
|
"type": "static",
|
||||||
|
"branch": "test",
|
||||||
|
"displayName": "tickte-system",
|
||||||
|
"templateName": "webklar-preview-template",
|
||||||
|
"projectType": "project",
|
||||||
|
"public": false
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
Sehr geehrtes Hetzner Team,
|
||||||
|
|
||||||
|
bezüglich der Portscan Erkennung haben wir folgende Präventionsmaßnahmen implementiert.
|
||||||
|
|
||||||
|
Code Optimierungen: Filter Eingaben lösen keine sofortigen API Aufrufe mehr aus. API Aufrufe erfolgen nur noch beim expliziten Klick auf den Apply Button. Dies reduziert unnötige TCP Verbindungen um etwa 90 Prozent.
|
||||||
|
|
||||||
|
Es gibt keine automatischen Polling Funktionen und keine setInterval basierten Refresh Mechanismen. API Aufrufe erfolgen nur bei Benutzerinteraktionen.
|
||||||
|
|
||||||
|
Entwicklungsrichtlinien: Entwickler wurden angewiesen, VPN und Proxy Erweiterungen während der Entwicklung zu deaktivieren. Security Plugins werden vor dem Testen überprüft.
|
||||||
|
|
||||||
|
Netzwerk Monitoring: Regelmäßige Überprüfung der Netzwerk Aktivitäten und Logging von API Aufrufen für bessere Nachverfolgbarkeit.
|
||||||
|
|
||||||
|
Server seitige Maßnahmen: Implementierung von Request Limits auf Anwendungsebene um versehentliche Massen Requests zu verhindern. Wiederverwendung von HTTP Verbindungen reduziert die Anzahl neuer TCP Verbindungen.
|
||||||
|
|
||||||
|
Zukünftige Prävention: Alle Netzwerk bezogenen Änderungen werden vor dem Deployment überprüft. Automatische Tests für Netzwerk Verhalten werden durchgeführt. Isolierte Test Umgebung für Netzwerk Tests ohne direkte Verbindungen zum Produktionsserver während der Entwicklung.
|
||||||
|
|
||||||
|
Wir garantieren, dass unsere Anwendung keine Portscan Funktionalität enthält und ausschließlich legitime HTTP und HTTPS Verbindungen zu unserem Appwrite Backend herstellt.
|
||||||
|
|
||||||
|
Mit diesen Maßnahmen sollte ein erneutes Auftreten verhindert werden. Wir bitten um Entsperrung unserer IP Adresse 91.99.156.85.
|
||||||
|
|
||||||
|
Mit freundlichen Grüßen
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
Sehr geehrtes Hetzner Team,
|
||||||
|
|
||||||
|
bezüglich der Portscan Erkennung von unserer IP Adresse 91.99.156.85 am 30.12.2025 um 10:59:37 UTC möchten wir die Ursache erläutern.
|
||||||
|
|
||||||
|
Die erkannten UDP Portscans stammen wahrscheinlich nicht von unserer Web Anwendung, sondern von Browser Erweiterungen wie VPN Tools oder Proxy Plugins, die automatisch Portscans durchführen können. Diese laufen im Hintergrund und sind dem Benutzer oft nicht bewusst.
|
||||||
|
|
||||||
|
Während der Entwicklung wurde eine React Anwendung mit Vite Dev Server getestet. Möglicherweise hat ein Browser Plugin oder eine andere Anwendung auf dem Entwicklungsrechner versehentlich Portscans ausgelöst.
|
||||||
|
|
||||||
|
Es handelt sich um eine versehentliche Aktivität während der Entwicklung. Es gab keine absichtliche Portscan Aktivität oder Angriffsversuche.
|
||||||
|
|
||||||
|
Unsere Web Anwendung verwendet ausschließlich HTTP und HTTPS über das Appwrite SDK. Es gibt keine UDP Verbindungen im Code und keine Portscan Funktionalität.
|
||||||
|
|
||||||
|
Wir bitten um Entsperrung unserer IP Adresse 91.99.156.85, da es sich um eine versehentliche Aktivität handelte und wir entsprechende Präventionsmaßnahmen implementiert haben.
|
||||||
|
|
||||||
|
Mit freundlichen Grüßen
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
# Lokale Entwicklung (Appwrite)
|
||||||
|
|
||||||
|
## Warum funktioniert es auf dem Server, aber nicht mit `npm run dev`?
|
||||||
|
|
||||||
|
| | Server (`ticket.webklar.com`) | Lokal (`localhost:5173`) |
|
||||||
|
|---|---|---|
|
||||||
|
| App-URL | `https://ticket.webklar.com` | `http://localhost:5173` |
|
||||||
|
| API-URL | `https://ticket.webklar.com/v1` (nginx-Proxy) | oft `https://ticket.webklar.com/v1` direkt |
|
||||||
|
| **Gleiche Origin?** | Ja | **Nein** (Cross-Origin) |
|
||||||
|
| Session-Cookie | `domain=.ticket.webklar.com` | Browser speichert Cookie **nicht** f�r localhost |
|
||||||
|
|
||||||
|
Auf dem Server leitet nginx `/v1/` an Appwrite weiter und schreibt Cookies auf `ticket.webklar.com` um. Die React-App und die API sind **dieselbe Site** � Login funktioniert.
|
||||||
|
|
||||||
|
Lokal ruft der Browser die API auf einer **anderen Domain** auf. Appwrite setzt Cookies f�r `.ticket.webklar.com`. Die werden bei Requests von `localhost:5173` **nicht mitgeschickt** ? `guests missing scopes`, Login scheint tot.
|
||||||
|
|
||||||
|
CORS f�r `http://localhost:5173` ist auf dem Server erlaubt � das reicht allein nicht f�r HttpOnly-Session-Cookies.
|
||||||
|
|
||||||
|
## L�sung: Vite-Proxy + `.env.local`
|
||||||
|
|
||||||
|
1. Datei anlegen (nicht in Git):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env.local
|
||||||
|
```
|
||||||
|
|
||||||
|
2. In `.env.local` muss stehen:
|
||||||
|
|
||||||
|
```env
|
||||||
|
VITE_APPWRITE_ENDPOINT=http://localhost:5173/v1
|
||||||
|
VITE_APPWRITE_PROJECT_ID=6a1058610003c5a13a05
|
||||||
|
VITE_APPWRITE_DATABASE_ID=woms-database
|
||||||
|
VITE_APPWRITE_BUCKET_ID=woms-attachments
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Dev-Server starten:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
`vite.config.js` leitet `/v1` an `https://ticket.webklar.com` weiter und schreibt Cookie-Domains auf `localhost` um � Verhalten wie nginx auf dem Server.
|
||||||
|
|
||||||
|
## Fehler: „Failed to load resource“ / **504** in der Konsole
|
||||||
|
|
||||||
|
Typisch bei `VITE_APPWRITE_ENDPOINT=http://localhost:5173/v1`: Der Vite-Proxy erreicht Appwrite nicht rechtzeitig.
|
||||||
|
|
||||||
|
1. **Neuesten Code holen** (`git pull`) — Proxy zeigt jetzt direkt auf `appwrite.webklar.com` mit längerem Timeout.
|
||||||
|
2. Dev-Server **komplett stoppen** (Strg+C) und neu starten: `npm run dev`
|
||||||
|
3. In `.env.local` prüfen:
|
||||||
|
```env
|
||||||
|
VITE_APPWRITE_ENDPOINT=http://localhost:5173/v1
|
||||||
|
```
|
||||||
|
4. Im Browser **Network**-Tab: Welche URL liefert 504?
|
||||||
|
- `http://localhost:5173/v1/...` ? Proxy/Netzwerk (Firewall, VPN, Server nicht erreichbar)
|
||||||
|
- `https://ticket.webklar.com/v1/...` ? ohne Proxy; Endpoint in `.env.local` auf `localhost:5173` stellen
|
||||||
|
5. Test im Terminal (ersetzt 5173 durch deinen Vite-Port):
|
||||||
|
```bash
|
||||||
|
curl -sS -o /dev/null -w "%{http_code}\n" http://localhost:5173/v1/health \
|
||||||
|
-H "X-Appwrite-Project: 6a1058610003c5a13a05"
|
||||||
|
```
|
||||||
|
Erwartung: **401** (nicht 504) — dann ist der Proxy ok.
|
||||||
|
|
||||||
|
**Notfall ohne Proxy:** In `.env.local` temporär `VITE_APPWRITE_ENDPOINT=https://ticket.webklar.com/v1` — Login-Cookies funktionieren lokal dann oft nicht, aber du siehst ob der Server erreichbar ist.
|
||||||
|
|
||||||
|
## Checkliste bei Problemen
|
||||||
|
|
||||||
|
- [ ] `.env.local` existiert (`.env` allein reicht; Vite l�dt `.env.local` mit h�herer Priorit�t)
|
||||||
|
- [ ] Endpoint ist `http://localhost:5173/v1`, **nicht** `https://appwrite.webklar.com/v1` (altes Projekt)
|
||||||
|
- [ ] Nach Login: DevTools ? Application ? Cookies ? `localhost` ? `a_session_6a1058610003c5a13a05`
|
||||||
|
- [ ] Appwrite Console ? Projekt **Ticket-System** ? Platforms ? `localhost` eingetragen
|
||||||
|
|
||||||
|
## Git / Deploy
|
||||||
|
|
||||||
|
- `.env` und `.env.local` sind in `.gitignore` � jeder Entwickler braucht eigene `.env.local`
|
||||||
|
- Server-Build nutzt Container-Env (`VITE_APPWRITE_ENDPOINT=https://ticket.webklar.com/v1`)
|
||||||
|
- Push nach Gitea deployt nur den **Frontend-Code**, nicht deine lokale `.env.local`
|
||||||
@@ -1,67 +1,42 @@
|
|||||||
# WOMS 2.0 - Work Order Management System
|
# WOMS – Ticketsystem von WEBklar
|
||||||
|
|
||||||
React-basiertes Ticket-Management-System mit Appwrite Backend.
|
Internes Ticketsystem unter https://ticket.webklar.com: Tickets, Arbeitsblätter mit
|
||||||
|
Zeiterfassung, Kunden und Leads, Website-Projekte, Planboard und Rechnungen (nur
|
||||||
|
anzeigen; Rechnungen werden in InvoiceNinja geschrieben).
|
||||||
|
React/Vite im Browser, Daten in Appwrite (Projekt `6a1058610003c5a13a05`,
|
||||||
|
Datenbank `woms-database`, API unter `https://ticket.webklar.com/v1`).
|
||||||
|
|
||||||
## Setup
|
## Entwickeln und live stellen
|
||||||
|
|
||||||
### 1. Dependencies installieren
|
- Lokal starten: siehe [LOCAL_DEV.md](./LOCAL_DEV.md) (Vite-Proxy + `.env.local`).
|
||||||
```bash
|
- Live ist der Branch `test` im Gitea-Repo `JUSN/tickte-system`. Ein Push löst
|
||||||
npm install
|
auf dem Server Build und Deploy aus.
|
||||||
```
|
- Achtung: Der Webhook baut **jeden** gepushten Branch und stellt ihn live.
|
||||||
|
Feature-Branches deshalb nicht pushen; live geht es mit
|
||||||
|
`git push origin <branch>:test`.
|
||||||
|
|
||||||
### 2. Appwrite konfigurieren
|
## Datenmodell (Kurzfassung)
|
||||||
|
|
||||||
Erstelle eine `.env` Datei basierend auf `.env.example`:
|
Datum speichert WOMS als Text `dd.mm.yyyy`, Uhrzeiten als `hhmm` (z. B. `0930`).
|
||||||
```env
|
|
||||||
VITE_APPWRITE_ENDPOINT=https://cloud.appwrite.io/v1
|
|
||||||
VITE_APPWRITE_PROJECT_ID=your-project-id
|
|
||||||
VITE_APPWRITE_DATABASE_ID=woms-database
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Appwrite Database Schema
|
| Collection | Inhalt | Wichtige Felder |
|
||||||
|
|---|---|---|
|
||||||
|
| `workorders` | Tickets | `woid` (fortlaufend ab 10000), `topic`, `details`, `status`, `priority`, `type` (Webpage, Migration, Project, Akquise), `serviceType`, `customerId`/`customerName`, `assignedTo`/`assignedName`, `requestedBy`, `startDate`/`startTime`/`endTime`, `deadline` |
|
||||||
|
| `worksheets` | Arbeitsblätter | `wsid` (fortlaufend ab 100000), `woid` + `workorderId`, `employeeId`/`employeeName`, `serviceType`, `oldStatus`/`newStatus`, `totalTime` (Minuten), `startDate`/`startTime`/`endDate`/`endTime`, `details`, `isComment`, `countsToPlan` (Abo-Stunden) |
|
||||||
|
| `customers` | Kunden und Leads | `code` (Kundennummer), `name`/`companyName`, `customerStatus` (z. B. lead, customer, lost), `archived`, Rechnungsadresse, `invoiceNinjaClientId`, Portal-Zugang |
|
||||||
|
| `employees` | Mitarbeiter | `userId` (Appwrite-Benutzer), `displayName`, `email`, `shortcode` (Kürzel) |
|
||||||
|
| `websiteProjects` | Website-Projekte und Vorschauen | `customerId` (steuert, was der Kunde im Portal sieht), `ticketId` (Ziel der Git-Push-Arbeitsblätter), Subdomain, Repo |
|
||||||
|
| `leads`, `leadPipeline`, `leadSettings` | Lead-Automatik | Recherche-Ergebnisse, Pipeline-Schritte, Sucheinstellungen |
|
||||||
|
| `roadmaps`, `roadmapNodes` | Projektpläne | Plan mit Schritten, verknüpft mit einem Ticket |
|
||||||
|
| `config` | alte Einstellungen | wird von WOMS nicht mehr gelesen (Prioritäten, alte Listen) |
|
||||||
|
|
||||||
Erstelle folgende Collections in deiner Appwrite-Datenbank:
|
Zusammenspiel mit anderen Systemen:
|
||||||
|
|
||||||
#### Collection: `workorders`
|
- **Kundenportal** (project.webklar.com): legt bei jedem Git-Push ein Arbeitsblatt an
|
||||||
| Attribut | Typ | Required |
|
(`employeeId` `gitea`, `serviceType` `GIT`, ohne Beginn). Die Zeit wird in WOMS
|
||||||
|----------|-----|----------|
|
nachgetragen, danach zählt sie als Arbeitszeit. Termine aus der Rechnungsanalyse
|
||||||
| title | string (255) | ✓ |
|
landen am Akquise-Ticket des Kunden und erscheinen im Planboard. Kunden-, Mitarbeiter-
|
||||||
| description | string (5000) | |
|
und Projektverwaltung laufen über die Admin-API des Portals.
|
||||||
| status | string (50) | ✓ |
|
- **ticket-integrations** (`/api/integrations`): Proxy zu InvoiceNinja (Rechnungen
|
||||||
| priority | integer | ✓ |
|
anzeigen, Kunden verknüpfen). Die Paperless-Routen dort nutzt WOMS nicht mehr;
|
||||||
| type | string (50) | |
|
Paperless selbst ist gestoppt.
|
||||||
| customerId | string (50) | |
|
|
||||||
| customerName | string (255) | |
|
|
||||||
| assignedTo | string (50) | |
|
|
||||||
| assignedName | string (255) | |
|
|
||||||
| response | string (50) | |
|
|
||||||
| createdAt | datetime | |
|
|
||||||
|
|
||||||
#### Collection: `customers`
|
|
||||||
| Attribut | Typ | Required |
|
|
||||||
|----------|-----|----------|
|
|
||||||
| name | string (255) | ✓ |
|
|
||||||
| email | string (255) | |
|
|
||||||
| phone | string (50) | |
|
|
||||||
|
|
||||||
#### Collection: `users`
|
|
||||||
| Attribut | Typ | Required |
|
|
||||||
|----------|-----|----------|
|
|
||||||
| name | string (255) | ✓ |
|
|
||||||
| email | string (255) | ✓ |
|
|
||||||
| role | string (50) | |
|
|
||||||
|
|
||||||
### 4. App starten
|
|
||||||
```bash
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- Ticket-Erstellung und -Verwaltung
|
|
||||||
- Status-Tracking (Open, Occupied, Assigned, Awaiting, Closed)
|
|
||||||
- Prioritäts-Management (Low, Normal, High, Critical)
|
|
||||||
- Filter und Suche
|
|
||||||
- Datei-Uploads
|
|
||||||
- Dashboard mit Statistiken
|
|
||||||
- Planboard für Terminplanung
|
|
||||||
+1
-2
@@ -3,8 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>NetWEB Systems WOMS 2.0</title>
|
<title>Webklar WOMS 2.0</title>
|
||||||
<link href="https://fonts.googleapis.com/css?family=Lato|Open+Sans" rel="stylesheet">
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
+16
-1
@@ -1 +1,16 @@
|
|||||||
../baseline-browser-mapping/dist/cli.js
|
#!/bin/sh
|
||||||
|
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||||
|
|
||||||
|
case `uname` in
|
||||||
|
*CYGWIN*|*MINGW*|*MSYS*)
|
||||||
|
if command -v cygpath > /dev/null 2>&1; then
|
||||||
|
basedir=`cygpath -w "$basedir"`
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -x "$basedir/node" ]; then
|
||||||
|
exec "$basedir/node" "$basedir/../baseline-browser-mapping/dist/cli.js" "$@"
|
||||||
|
else
|
||||||
|
exec node "$basedir/../baseline-browser-mapping/dist/cli.js" "$@"
|
||||||
|
fi
|
||||||
+16
-1
@@ -1 +1,16 @@
|
|||||||
../browserslist/cli.js
|
#!/bin/sh
|
||||||
|
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||||
|
|
||||||
|
case `uname` in
|
||||||
|
*CYGWIN*|*MINGW*|*MSYS*)
|
||||||
|
if command -v cygpath > /dev/null 2>&1; then
|
||||||
|
basedir=`cygpath -w "$basedir"`
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -x "$basedir/node" ]; then
|
||||||
|
exec "$basedir/node" "$basedir/../browserslist/cli.js" "$@"
|
||||||
|
else
|
||||||
|
exec node "$basedir/../browserslist/cli.js" "$@"
|
||||||
|
fi
|
||||||
+16
-1
@@ -1 +1,16 @@
|
|||||||
../esbuild/bin/esbuild
|
#!/bin/sh
|
||||||
|
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||||
|
|
||||||
|
case `uname` in
|
||||||
|
*CYGWIN*|*MINGW*|*MSYS*)
|
||||||
|
if command -v cygpath > /dev/null 2>&1; then
|
||||||
|
basedir=`cygpath -w "$basedir"`
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -x "$basedir/node" ]; then
|
||||||
|
exec "$basedir/node" "$basedir/../esbuild/bin/esbuild" "$@"
|
||||||
|
else
|
||||||
|
exec node "$basedir/../esbuild/bin/esbuild" "$@"
|
||||||
|
fi
|
||||||
+16
-1
@@ -1 +1,16 @@
|
|||||||
../jsesc/bin/jsesc
|
#!/bin/sh
|
||||||
|
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||||
|
|
||||||
|
case `uname` in
|
||||||
|
*CYGWIN*|*MINGW*|*MSYS*)
|
||||||
|
if command -v cygpath > /dev/null 2>&1; then
|
||||||
|
basedir=`cygpath -w "$basedir"`
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -x "$basedir/node" ]; then
|
||||||
|
exec "$basedir/node" "$basedir/../jsesc/bin/jsesc" "$@"
|
||||||
|
else
|
||||||
|
exec node "$basedir/../jsesc/bin/jsesc" "$@"
|
||||||
|
fi
|
||||||
+16
-1
@@ -1 +1,16 @@
|
|||||||
../json5/lib/cli.js
|
#!/bin/sh
|
||||||
|
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||||
|
|
||||||
|
case `uname` in
|
||||||
|
*CYGWIN*|*MINGW*|*MSYS*)
|
||||||
|
if command -v cygpath > /dev/null 2>&1; then
|
||||||
|
basedir=`cygpath -w "$basedir"`
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -x "$basedir/node" ]; then
|
||||||
|
exec "$basedir/node" "$basedir/../json5/lib/cli.js" "$@"
|
||||||
|
else
|
||||||
|
exec node "$basedir/../json5/lib/cli.js" "$@"
|
||||||
|
fi
|
||||||
+16
-1
@@ -1 +1,16 @@
|
|||||||
../loose-envify/cli.js
|
#!/bin/sh
|
||||||
|
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||||
|
|
||||||
|
case `uname` in
|
||||||
|
*CYGWIN*|*MINGW*|*MSYS*)
|
||||||
|
if command -v cygpath > /dev/null 2>&1; then
|
||||||
|
basedir=`cygpath -w "$basedir"`
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -x "$basedir/node" ]; then
|
||||||
|
exec "$basedir/node" "$basedir/../loose-envify/cli.js" "$@"
|
||||||
|
else
|
||||||
|
exec node "$basedir/../loose-envify/cli.js" "$@"
|
||||||
|
fi
|
||||||
+16
-1
@@ -1 +1,16 @@
|
|||||||
../nanoid/bin/nanoid.cjs
|
#!/bin/sh
|
||||||
|
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||||
|
|
||||||
|
case `uname` in
|
||||||
|
*CYGWIN*|*MINGW*|*MSYS*)
|
||||||
|
if command -v cygpath > /dev/null 2>&1; then
|
||||||
|
basedir=`cygpath -w "$basedir"`
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -x "$basedir/node" ]; then
|
||||||
|
exec "$basedir/node" "$basedir/../nanoid/bin/nanoid.cjs" "$@"
|
||||||
|
else
|
||||||
|
exec node "$basedir/../nanoid/bin/nanoid.cjs" "$@"
|
||||||
|
fi
|
||||||
+16
-1
@@ -1 +1,16 @@
|
|||||||
../@babel/parser/bin/babel-parser.js
|
#!/bin/sh
|
||||||
|
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||||
|
|
||||||
|
case `uname` in
|
||||||
|
*CYGWIN*|*MINGW*|*MSYS*)
|
||||||
|
if command -v cygpath > /dev/null 2>&1; then
|
||||||
|
basedir=`cygpath -w "$basedir"`
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -x "$basedir/node" ]; then
|
||||||
|
exec "$basedir/node" "$basedir/../@babel/parser/bin/babel-parser.js" "$@"
|
||||||
|
else
|
||||||
|
exec node "$basedir/../@babel/parser/bin/babel-parser.js" "$@"
|
||||||
|
fi
|
||||||
+16
-1
@@ -1 +1,16 @@
|
|||||||
../rollup/dist/bin/rollup
|
#!/bin/sh
|
||||||
|
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||||
|
|
||||||
|
case `uname` in
|
||||||
|
*CYGWIN*|*MINGW*|*MSYS*)
|
||||||
|
if command -v cygpath > /dev/null 2>&1; then
|
||||||
|
basedir=`cygpath -w "$basedir"`
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -x "$basedir/node" ]; then
|
||||||
|
exec "$basedir/node" "$basedir/../rollup/dist/bin/rollup" "$@"
|
||||||
|
else
|
||||||
|
exec node "$basedir/../rollup/dist/bin/rollup" "$@"
|
||||||
|
fi
|
||||||
+16
-1
@@ -1 +1,16 @@
|
|||||||
../semver/bin/semver.js
|
#!/bin/sh
|
||||||
|
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||||
|
|
||||||
|
case `uname` in
|
||||||
|
*CYGWIN*|*MINGW*|*MSYS*)
|
||||||
|
if command -v cygpath > /dev/null 2>&1; then
|
||||||
|
basedir=`cygpath -w "$basedir"`
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -x "$basedir/node" ]; then
|
||||||
|
exec "$basedir/node" "$basedir/../semver/bin/semver.js" "$@"
|
||||||
|
else
|
||||||
|
exec node "$basedir/../semver/bin/semver.js" "$@"
|
||||||
|
fi
|
||||||
+16
-1
@@ -1 +1,16 @@
|
|||||||
../update-browserslist-db/cli.js
|
#!/bin/sh
|
||||||
|
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||||
|
|
||||||
|
case `uname` in
|
||||||
|
*CYGWIN*|*MINGW*|*MSYS*)
|
||||||
|
if command -v cygpath > /dev/null 2>&1; then
|
||||||
|
basedir=`cygpath -w "$basedir"`
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -x "$basedir/node" ]; then
|
||||||
|
exec "$basedir/node" "$basedir/../update-browserslist-db/cli.js" "$@"
|
||||||
|
else
|
||||||
|
exec node "$basedir/../update-browserslist-db/cli.js" "$@"
|
||||||
|
fi
|
||||||
+16
-1
@@ -1 +1,16 @@
|
|||||||
../vite/bin/vite.js
|
#!/bin/sh
|
||||||
|
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||||
|
|
||||||
|
case `uname` in
|
||||||
|
*CYGWIN*|*MINGW*|*MSYS*)
|
||||||
|
if command -v cygpath > /dev/null 2>&1; then
|
||||||
|
basedir=`cygpath -w "$basedir"`
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -x "$basedir/node" ]; then
|
||||||
|
exec "$basedir/node" "$basedir/../vite/bin/vite.js" "$@"
|
||||||
|
else
|
||||||
|
exec node "$basedir/../vite/bin/vite.js" "$@"
|
||||||
|
fi
|
||||||
+162
-22
@@ -32,6 +32,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
|
||||||
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.27.1",
|
"@babel/code-frame": "^7.27.1",
|
||||||
"@babel/generator": "^7.28.5",
|
"@babel/generator": "^7.28.5",
|
||||||
@@ -275,17 +276,17 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/darwin-arm64": {
|
"node_modules/@esbuild/win32-x64": {
|
||||||
"version": "0.21.5",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
|
||||||
"integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
|
"integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"win32"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
@@ -350,19 +351,58 @@
|
|||||||
"integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
|
"integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
||||||
"version": "4.53.5",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.5.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.5.tgz",
|
||||||
"integrity": "sha512-S87zZPBmRO6u1YXQLwpveZm4JfPpAa6oHBX7/ghSiGH3rz/KDgAu1rKdGutV+WUI6tKDMbaBJomhnT30Y2t4VQ==",
|
"integrity": "sha512-2NqKgZSuLH9SXBBV2dWNRCZmocgSOx8OJSdpRaEcRlIfX8YrKxUT6z0F1NpvDVhOsl190UFTRh2F2WDWWCYp3A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"win32"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||||
|
"version": "4.53.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.5.tgz",
|
||||||
|
"integrity": "sha512-JRpZUhCfhZ4keB5v0fe02gQJy05GqboPOaxvjugW04RLSYYoB/9t2lx2u/tMs/Na/1NXfY8QYjgRljRpN+MjTQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@tabler/icons": {
|
||||||
|
"version": "3.36.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-3.36.0.tgz",
|
||||||
|
"integrity": "sha512-z9OfTEG6QbaQWM9KBOxxUdpgvMUn0atageXyiaSc2gmYm51ORO8Ua7eUcjlks+Dc0YMK4rrodAFdK9SfjJ4ZcA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/codecalm"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tabler/icons-react": {
|
||||||
|
"version": "3.36.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tabler/icons-react/-/icons-react-3.36.0.tgz",
|
||||||
|
"integrity": "sha512-sSZ00bEjTdTTskVFykq294RJq+9cFatwy4uYa78HcYBCXU1kSD1DIp5yoFsQXmybkIOKCjp18OnhAYk553UIfQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@tabler/icons": "3.36.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/codecalm"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">= 16"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/babel__core": {
|
"node_modules/@types/babel__core": {
|
||||||
"version": "7.20.5",
|
"version": "7.20.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
||||||
@@ -421,6 +461,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz",
|
||||||
"integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==",
|
"integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/prop-types": "*",
|
"@types/prop-types": "*",
|
||||||
"csstype": "^3.2.2"
|
"csstype": "^3.2.2"
|
||||||
@@ -497,6 +538,7 @@
|
|||||||
"url": "https://github.com/sponsors/ai"
|
"url": "https://github.com/sponsors/ai"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.9.0",
|
"baseline-browser-mapping": "^2.9.0",
|
||||||
"caniuse-lite": "^1.0.30001759",
|
"caniuse-lite": "^1.0.30001759",
|
||||||
@@ -543,6 +585,15 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/clsx": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/combined-stream": {
|
"node_modules/combined-stream": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
@@ -737,18 +788,31 @@
|
|||||||
"node": ">= 0.12"
|
"node": ">= 0.12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/fsevents": {
|
"node_modules/framer-motion": {
|
||||||
"version": "2.3.3",
|
"version": "12.23.26",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.26.tgz",
|
||||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
"integrity": "sha512-cPcIhgR42xBn1Uj+PzOyheMtZ73H927+uWPDVhUMqxy8UHt6Okavb6xIz9J/phFUHUj0OncR6UvMfJTXoc/LKA==",
|
||||||
"dev": true,
|
"license": "MIT",
|
||||||
"hasInstallScript": true,
|
"dependencies": {
|
||||||
"optional": true,
|
"motion-dom": "^12.23.23",
|
||||||
"os": [
|
"motion-utils": "^12.23.6",
|
||||||
"darwin"
|
"tslib": "^2.4.0"
|
||||||
],
|
},
|
||||||
"engines": {
|
"peerDependencies": {
|
||||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
"@emotion/is-prop-valid": "*",
|
||||||
|
"react": "^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@emotion/is-prop-valid": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/function-bind": {
|
"node_modules/function-bind": {
|
||||||
@@ -934,6 +998,47 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/motion": {
|
||||||
|
"version": "12.23.26",
|
||||||
|
"resolved": "https://registry.npmjs.org/motion/-/motion-12.23.26.tgz",
|
||||||
|
"integrity": "sha512-Ll8XhVxY8LXMVYTCfme27WH2GjBrCIzY4+ndr5QKxsK+YwCtOi2B/oBi5jcIbik5doXuWT/4KKDOVAZJkeY5VQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"framer-motion": "^12.23.26",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emotion/is-prop-valid": "*",
|
||||||
|
"react": "^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@emotion/is-prop-valid": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/motion-dom": {
|
||||||
|
"version": "12.23.23",
|
||||||
|
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.23.tgz",
|
||||||
|
"integrity": "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"motion-utils": "^12.23.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/motion-utils": {
|
||||||
|
"version": "12.23.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz",
|
||||||
|
"integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
@@ -1017,10 +1122,20 @@
|
|||||||
"node": "^10 || ^12 || >=14"
|
"node": "^10 || ^12 || >=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/postprocessing": {
|
||||||
|
"version": "6.38.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/postprocessing/-/postprocessing-6.38.2.tgz",
|
||||||
|
"integrity": "sha512-7DwuT7Tkst41ZjSj287g7C9c5/D3Xx5rMgBosg0dadbUPoZD2HNzkadKPol1d2PJAoI9f+Jeh1/v9YfLzpFGVw==",
|
||||||
|
"license": "Zlib",
|
||||||
|
"peerDependencies": {
|
||||||
|
"three": ">= 0.157.0 < 0.183.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react": {
|
"node_modules/react": {
|
||||||
"version": "18.3.1",
|
"version": "18.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||||
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.1.0"
|
"loose-envify": "^1.1.0"
|
||||||
},
|
},
|
||||||
@@ -1032,6 +1147,7 @@
|
|||||||
"version": "18.3.1",
|
"version": "18.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||||
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.1.0",
|
"loose-envify": "^1.1.0",
|
||||||
"scheduler": "^0.23.2"
|
"scheduler": "^0.23.2"
|
||||||
@@ -1173,11 +1289,34 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tailwind-merge": {
|
||||||
|
"version": "3.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz",
|
||||||
|
"integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/dcastil"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/three": {
|
||||||
|
"version": "0.182.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/three/-/three-0.182.0.tgz",
|
||||||
|
"integrity": "sha512-GbHabT+Irv+ihI1/f5kIIsZ+Ef9Sl5A1Y7imvS5RQjWgtTPfPnZ43JmlYI7NtCRDK9zir20lQpfg8/9Yd02OvQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"node_modules/tr46": {
|
"node_modules/tr46": {
|
||||||
"version": "0.0.3",
|
"version": "0.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||||
},
|
},
|
||||||
|
"node_modules/tslib": {
|
||||||
|
"version": "2.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
|
"license": "0BSD"
|
||||||
|
},
|
||||||
"node_modules/update-browserslist-db": {
|
"node_modules/update-browserslist-db": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
|
||||||
@@ -1213,6 +1352,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
|
||||||
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
|
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.21.3",
|
"esbuild": "^0.21.3",
|
||||||
"postcss": "^8.4.43",
|
"postcss": "^8.4.43",
|
||||||
|
|||||||
+67
-25
@@ -1,91 +1,133 @@
|
|||||||
{
|
{
|
||||||
"hash": "8745a243",
|
"hash": "91e90cf6",
|
||||||
"configHash": "d74aae15",
|
"configHash": "eaf678ca",
|
||||||
"lockfileHash": "8786fedf",
|
"lockfileHash": "4b2b8d7d",
|
||||||
"browserHash": "6bc53e11",
|
"browserHash": "114e306f",
|
||||||
"optimized": {
|
"optimized": {
|
||||||
"react": {
|
"react": {
|
||||||
"src": "../../react/index.js",
|
"src": "../../react/index.js",
|
||||||
"file": "react.js",
|
"file": "react.js",
|
||||||
"fileHash": "0ccae341",
|
"fileHash": "c1394b31",
|
||||||
"needsInterop": true
|
"needsInterop": true
|
||||||
},
|
},
|
||||||
"react-dom": {
|
"react-dom": {
|
||||||
"src": "../../react-dom/index.js",
|
"src": "../../react-dom/index.js",
|
||||||
"file": "react-dom.js",
|
"file": "react-dom.js",
|
||||||
"fileHash": "b3aa3ef2",
|
"fileHash": "e0cfaa25",
|
||||||
"needsInterop": true
|
"needsInterop": true
|
||||||
},
|
},
|
||||||
"react/jsx-dev-runtime": {
|
"react/jsx-dev-runtime": {
|
||||||
"src": "../../react/jsx-dev-runtime.js",
|
"src": "../../react/jsx-dev-runtime.js",
|
||||||
"file": "react_jsx-dev-runtime.js",
|
"file": "react_jsx-dev-runtime.js",
|
||||||
"fileHash": "898bc344",
|
"fileHash": "4e6552af",
|
||||||
"needsInterop": true
|
"needsInterop": true
|
||||||
},
|
},
|
||||||
"react/jsx-runtime": {
|
"react/jsx-runtime": {
|
||||||
"src": "../../react/jsx-runtime.js",
|
"src": "../../react/jsx-runtime.js",
|
||||||
"file": "react_jsx-runtime.js",
|
"file": "react_jsx-runtime.js",
|
||||||
"fileHash": "49a33e04",
|
"fileHash": "326b9f6e",
|
||||||
"needsInterop": true
|
"needsInterop": true
|
||||||
},
|
},
|
||||||
|
"@tabler/icons-react": {
|
||||||
|
"src": "../../@tabler/icons-react/dist/esm/tabler-icons-react.mjs",
|
||||||
|
"file": "@tabler_icons-react.js",
|
||||||
|
"fileHash": "ef17992e",
|
||||||
|
"needsInterop": false
|
||||||
|
},
|
||||||
"appwrite": {
|
"appwrite": {
|
||||||
"src": "../../appwrite/dist/esm/sdk.js",
|
"src": "../../appwrite/dist/esm/sdk.js",
|
||||||
"file": "appwrite.js",
|
"file": "appwrite.js",
|
||||||
"fileHash": "1f73c770",
|
"fileHash": "e3207492",
|
||||||
|
"needsInterop": false
|
||||||
|
},
|
||||||
|
"clsx": {
|
||||||
|
"src": "../../clsx/dist/clsx.mjs",
|
||||||
|
"file": "clsx.js",
|
||||||
|
"fileHash": "438277fe",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"date-fns": {
|
"date-fns": {
|
||||||
"src": "../../date-fns/esm/index.js",
|
"src": "../../date-fns/esm/index.js",
|
||||||
"file": "date-fns.js",
|
"file": "date-fns.js",
|
||||||
"fileHash": "9fa70dfb",
|
"fileHash": "c8cdb3c6",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"date-fns/locale": {
|
"date-fns/locale": {
|
||||||
"src": "../../date-fns/esm/locale/index.js",
|
"src": "../../date-fns/esm/locale/index.js",
|
||||||
"file": "date-fns_locale.js",
|
"file": "date-fns_locale.js",
|
||||||
"fileHash": "af0a0b06",
|
"fileHash": "affdbcf7",
|
||||||
|
"needsInterop": false
|
||||||
|
},
|
||||||
|
"motion/react": {
|
||||||
|
"src": "../../motion/dist/es/react.mjs",
|
||||||
|
"file": "motion_react.js",
|
||||||
|
"fileHash": "d06adb28",
|
||||||
|
"needsInterop": false
|
||||||
|
},
|
||||||
|
"postprocessing": {
|
||||||
|
"src": "../../postprocessing/build/index.js",
|
||||||
|
"file": "postprocessing.js",
|
||||||
|
"fileHash": "a05d80d0",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"react-dom/client": {
|
"react-dom/client": {
|
||||||
"src": "../../react-dom/client.js",
|
"src": "../../react-dom/client.js",
|
||||||
"file": "react-dom_client.js",
|
"file": "react-dom_client.js",
|
||||||
"fileHash": "f6b57c12",
|
"fileHash": "35e13db7",
|
||||||
"needsInterop": true
|
"needsInterop": true
|
||||||
},
|
},
|
||||||
"react-icons/fa": {
|
"react-icons/fa": {
|
||||||
"src": "../../react-icons/fa/index.esm.js",
|
"src": "../../react-icons/fa/index.esm.js",
|
||||||
"file": "react-icons_fa.js",
|
"file": "react-icons_fa.js",
|
||||||
"fileHash": "750bb1d4",
|
"fileHash": "b39d751a",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"react-icons/fa6": {
|
"react-icons/fa6": {
|
||||||
"src": "../../react-icons/fa6/index.esm.js",
|
"src": "../../react-icons/fa6/index.esm.js",
|
||||||
"file": "react-icons_fa6.js",
|
"file": "react-icons_fa6.js",
|
||||||
"fileHash": "e0a50b57",
|
"fileHash": "ee91d98a",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"react-router-dom": {
|
"react-router-dom": {
|
||||||
"src": "../../react-router-dom/dist/index.js",
|
"src": "../../react-router-dom/dist/index.js",
|
||||||
"file": "react-router-dom.js",
|
"file": "react-router-dom.js",
|
||||||
"fileHash": "061d1fe8",
|
"fileHash": "96b74880",
|
||||||
|
"needsInterop": false
|
||||||
|
},
|
||||||
|
"tailwind-merge": {
|
||||||
|
"src": "../../tailwind-merge/dist/bundle-mjs.mjs",
|
||||||
|
"file": "tailwind-merge.js",
|
||||||
|
"fileHash": "422eebfe",
|
||||||
|
"needsInterop": false
|
||||||
|
},
|
||||||
|
"three": {
|
||||||
|
"src": "../../three/build/three.module.js",
|
||||||
|
"file": "three.js",
|
||||||
|
"fileHash": "7ea78180",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chunks": {
|
"chunks": {
|
||||||
"chunk-EHLE63A5": {
|
"chunk-IFCYBMKG": {
|
||||||
"file": "chunk-EHLE63A5.js"
|
"file": "chunk-IFCYBMKG.js"
|
||||||
},
|
},
|
||||||
"chunk-TOOCKHFL": {
|
"chunk-DQJKJRV5": {
|
||||||
"file": "chunk-TOOCKHFL.js"
|
"file": "chunk-DQJKJRV5.js"
|
||||||
},
|
},
|
||||||
"chunk-TYILIMWK": {
|
"chunk-PJEEZAML": {
|
||||||
"file": "chunk-TYILIMWK.js"
|
"file": "chunk-PJEEZAML.js"
|
||||||
},
|
},
|
||||||
"chunk-CANBAPAS": {
|
"chunk-6PXSGDAH": {
|
||||||
"file": "chunk-CANBAPAS.js"
|
"file": "chunk-6PXSGDAH.js"
|
||||||
},
|
},
|
||||||
"chunk-5WRI5ZAA": {
|
"chunk-DRWLMN53": {
|
||||||
"file": "chunk-5WRI5ZAA.js"
|
"file": "chunk-DRWLMN53.js"
|
||||||
|
},
|
||||||
|
"chunk-SJKHQ62W": {
|
||||||
|
"file": "chunk-SJKHQ62W.js"
|
||||||
|
},
|
||||||
|
"chunk-G3PMV62Z": {
|
||||||
|
"file": "chunk-G3PMV62Z.js"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
__commonJS,
|
__commonJS,
|
||||||
__toESM
|
__toESM
|
||||||
} from "./chunk-5WRI5ZAA.js";
|
} from "./chunk-G3PMV62Z.js";
|
||||||
|
|
||||||
// node_modules/isomorphic-form-data/lib/browser.js
|
// node_modules/isomorphic-form-data/lib/browser.js
|
||||||
var require_browser = __commonJS({
|
var require_browser = __commonJS({
|
||||||
|
|||||||
-31
@@ -1,31 +0,0 @@
|
|||||||
var __create = Object.create;
|
|
||||||
var __defProp = Object.defineProperty;
|
|
||||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
||||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
||||||
var __getProtoOf = Object.getPrototypeOf;
|
|
||||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
||||||
var __commonJS = (cb, mod) => function __require() {
|
|
||||||
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
||||||
};
|
|
||||||
var __copyProps = (to, from, except, desc) => {
|
|
||||||
if (from && typeof from === "object" || typeof from === "function") {
|
|
||||||
for (let key of __getOwnPropNames(from))
|
|
||||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
||||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
||||||
}
|
|
||||||
return to;
|
|
||||||
};
|
|
||||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
||||||
// If the importer is in node compatibility mode or this is not an ESM
|
|
||||||
// file that has been converted to a CommonJS file using a Babel-
|
|
||||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
||||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
||||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
||||||
mod
|
|
||||||
));
|
|
||||||
|
|
||||||
export {
|
|
||||||
__commonJS,
|
|
||||||
__toESM
|
|
||||||
};
|
|
||||||
//# sourceMappingURL=chunk-5WRI5ZAA.js.map
|
|
||||||
-7
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 3,
|
|
||||||
"sources": [],
|
|
||||||
"sourcesContent": [],
|
|
||||||
"mappings": "",
|
|
||||||
"names": []
|
|
||||||
}
|
|
||||||
-1906
File diff suppressed because it is too large.
Load diff
-7
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 3,
|
|
||||||
"sources": ["../../react/cjs/react.development.js", "../../react/index.js"],
|
|
||||||
"sourcesContent": ["/**\n * @license React\n * react.development.js\n *\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n'use strict';\n\nif (process.env.NODE_ENV !== \"production\") {\n (function() {\n\n 'use strict';\n\n/* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */\nif (\n typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' &&\n typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart ===\n 'function'\n) {\n __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error());\n}\n var ReactVersion = '18.3.1';\n\n// ATTENTION\n// When adding new symbols to this file,\n// Please consider also adding to 'react-devtools-shared/src/backend/ReactSymbols'\n// The Symbol used to tag the ReactElement-like types.\nvar REACT_ELEMENT_TYPE = Symbol.for('react.element');\nvar REACT_PORTAL_TYPE = Symbol.for('react.portal');\nvar REACT_FRAGMENT_TYPE = Symbol.for('react.fragment');\nvar REACT_STRICT_MODE_TYPE = Symbol.for('react.strict_mode');\nvar REACT_PROFILER_TYPE = Symbol.for('react.profiler');\nvar REACT_PROVIDER_TYPE = Symbol.for('react.provider');\nvar REACT_CONTEXT_TYPE = Symbol.for('react.context');\nvar REACT_FORWARD_REF_TYPE = Symbol.for('react.forward_ref');\nvar REACT_SUSPENSE_TYPE = Symbol.for('react.suspense');\nvar REACT_SUSPENSE_LIST_TYPE = Symbol.for('react.suspense_list');\nvar REACT_MEMO_TYPE = Symbol.for('react.memo');\nvar REACT_LAZY_TYPE = Symbol.for('react.lazy');\nvar REACT_OFFSCREEN_TYPE = Symbol.for('react.offscreen');\nvar MAYBE_ITERATOR_SYMBOL = Symbol.iterator;\nvar FAUX_ITERATOR_SYMBOL = '@@iterator';\nfunction getIteratorFn(maybeIterable) {\n if (maybeIterable === null || typeof maybeIterable !== 'object') {\n return null;\n }\n\n var maybeIterator = MAYBE_ITERATOR_SYMBOL && maybeIterable[MAYBE_ITERATOR_SYMBOL] || maybeIterable[FAUX_ITERATOR_SYMBOL];\n\n if (typeof maybeIterator === 'function') {\n return maybeIterator;\n }\n\n return null;\n}\n\n/**\n * Keeps track of the current dispatcher.\n */\nvar ReactCurrentDispatcher = {\n /**\n * @internal\n * @type {ReactComponent}\n */\n current: null\n};\n\n/**\n * Keeps track of the current batch's configuration such as how long an update\n * should suspend for if it needs to.\n */\nvar ReactCurrentBatchConfig = {\n transition: null\n};\n\nvar ReactCurrentActQueue = {\n current: null,\n // Used to reproduce behavior of `batchedUpdates` in legacy mode.\n isBatchingLegacy: false,\n didScheduleLegacyUpdate: false\n};\n\n/**\n * Keeps track of the current owner.\n *\n * The current owner is the component who should own any components that are\n * currently being constructed.\n */\nvar ReactCurrentOwner = {\n /**\n * @internal\n * @type {ReactComponent}\n */\n current: null\n};\n\nvar ReactDebugCurrentFrame = {};\nvar currentExtraStackFrame = null;\nfunction setExtraStackFrame(stack) {\n {\n currentExtraStackFrame = stack;\n }\n}\n\n{\n ReactDebugCurrentFrame.setExtraStackFrame = function (stack) {\n {\n currentExtraStackFrame = stack;\n }\n }; // Stack implementation injected by the current renderer.\n\n\n ReactDebugCurrentFrame.getCurrentStack = null;\n\n ReactDebugCurrentFrame.getStackAddendum = function () {\n var stack = ''; // Add an extra top frame while an element is being validated\n\n if (currentExtraStackFrame) {\n stack += currentExtraStackFrame;\n } // Delegate to the injected renderer-specific implementation\n\n\n var impl = ReactDebugCurrentFrame.getCurrentStack;\n\n if (impl) {\n stack += impl() || '';\n }\n\n return stack;\n };\n}\n\n// -----------------------------------------------------------------------------\n\nvar enableScopeAPI = false; // Experimental Create Event Handle API.\nvar enableCacheElement = false;\nvar enableTransitionTracing = false; // No known bugs, but needs performance testing\n\nvar enableLegacyHidden = false; // Enables unstable_avoidThisFallback feature in Fiber\n// stuff. Intended to enable React core members to more easily debug scheduling\n// issues in DEV builds.\n\nvar enableDebugTracing = false; // Track which Fiber(s) schedule render work.\n\nvar ReactSharedInternals = {\n ReactCurrentDispatcher: ReactCurrentDispatcher,\n ReactCurrentBatchConfig: ReactCurrentBatchConfig,\n ReactCurrentOwner: ReactCurrentOwner\n};\n\n{\n ReactSharedInternals.ReactDebugCurrentFrame = ReactDebugCurrentFrame;\n ReactSharedInternals.ReactCurrentActQueue = ReactCurrentActQueue;\n}\n\n// by calls to these methods by a Babel plugin.\n//\n// In PROD (or in packages without access to React internals),\n// they are left as they are instead.\n\nfunction warn(format) {\n {\n {\n for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {\n args[_key - 1] = arguments[_key];\n }\n\n prLine truncated
|
|
||||||
"mappings": "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 truncated
|
|
||||||
"names": ["ReactDebugCurrentFrame", "moduleObject", "error", "Component", "returnValue"]
|
|
||||||
}
|
|
||||||
-84
@@ -1,84 +0,0 @@
|
|||||||
import {
|
|
||||||
require_react
|
|
||||||
} from "./chunk-CANBAPAS.js";
|
|
||||||
import {
|
|
||||||
__toESM
|
|
||||||
} from "./chunk-5WRI5ZAA.js";
|
|
||||||
|
|
||||||
// node_modules/react-icons/lib/esm/iconBase.js
|
|
||||||
var import_react2 = __toESM(require_react());
|
|
||||||
|
|
||||||
// node_modules/react-icons/lib/esm/iconContext.js
|
|
||||||
var import_react = __toESM(require_react());
|
|
||||||
var DefaultContext = {
|
|
||||||
color: void 0,
|
|
||||||
size: void 0,
|
|
||||||
className: void 0,
|
|
||||||
style: void 0,
|
|
||||||
attr: void 0
|
|
||||||
};
|
|
||||||
var IconContext = import_react.default.createContext && import_react.default.createContext(DefaultContext);
|
|
||||||
|
|
||||||
// node_modules/react-icons/lib/esm/iconBase.js
|
|
||||||
var __assign = function() {
|
|
||||||
__assign = Object.assign || function(t) {
|
|
||||||
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
||||||
s = arguments[i];
|
|
||||||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
|
|
||||||
}
|
|
||||||
return t;
|
|
||||||
};
|
|
||||||
return __assign.apply(this, arguments);
|
|
||||||
};
|
|
||||||
var __rest = function(s, e) {
|
|
||||||
var t = {};
|
|
||||||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p];
|
|
||||||
if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
||||||
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]];
|
|
||||||
}
|
|
||||||
return t;
|
|
||||||
};
|
|
||||||
function Tree2Element(tree) {
|
|
||||||
return tree && tree.map(function(node, i) {
|
|
||||||
return import_react2.default.createElement(node.tag, __assign({
|
|
||||||
key: i
|
|
||||||
}, node.attr), Tree2Element(node.child));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function GenIcon(data) {
|
|
||||||
return function(props) {
|
|
||||||
return import_react2.default.createElement(IconBase, __assign({
|
|
||||||
attr: __assign({}, data.attr)
|
|
||||||
}, props), Tree2Element(data.child));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
function IconBase(props) {
|
|
||||||
var elem = function(conf) {
|
|
||||||
var attr = props.attr, size = props.size, title = props.title, svgProps = __rest(props, ["attr", "size", "title"]);
|
|
||||||
var computedSize = size || conf.size || "1em";
|
|
||||||
var className;
|
|
||||||
if (conf.className) className = conf.className;
|
|
||||||
if (props.className) className = (className ? className + " " : "") + props.className;
|
|
||||||
return import_react2.default.createElement("svg", __assign({
|
|
||||||
stroke: "currentColor",
|
|
||||||
fill: "currentColor",
|
|
||||||
strokeWidth: "0"
|
|
||||||
}, conf.attr, attr, svgProps, {
|
|
||||||
className,
|
|
||||||
style: __assign(__assign({
|
|
||||||
color: props.color || conf.color
|
|
||||||
}, conf.style), props.style),
|
|
||||||
height: computedSize,
|
|
||||||
width: computedSize,
|
|
||||||
xmlns: "http://www.w3.org/2000/svg"
|
|
||||||
}), title && import_react2.default.createElement("title", null, title), props.children);
|
|
||||||
};
|
|
||||||
return IconContext !== void 0 ? import_react2.default.createElement(IconContext.Consumer, null, function(conf) {
|
|
||||||
return elem(conf);
|
|
||||||
}) : elem(DefaultContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
|
||||||
GenIcon
|
|
||||||
};
|
|
||||||
//# sourceMappingURL=chunk-EHLE63A5.js.map
|
|
||||||
-7
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 3,
|
|
||||||
"sources": ["../../react-icons/lib/esm/iconBase.js", "../../react-icons/lib/esm/iconContext.js"],
|
|
||||||
"sourcesContent": ["var __assign = this && this.__assign || function () {\n __assign = Object.assign || function (t) {\n for (var s, i = 1, n = arguments.length; i < n; i++) {\n s = arguments[i];\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\n }\n return t;\n };\n return __assign.apply(this, arguments);\n};\nvar __rest = this && this.__rest || function (s, e) {\n var t = {};\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p];\n if (s != null && typeof Object.getOwnPropertySymbols === \"function\") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\n if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]];\n }\n return t;\n};\nimport React from \"react\";\nimport { IconContext, DefaultContext } from \"./iconContext\";\nfunction Tree2Element(tree) {\n return tree && tree.map(function (node, i) {\n return React.createElement(node.tag, __assign({\n key: i\n }, node.attr), Tree2Element(node.child));\n });\n}\nexport function GenIcon(data) {\n // eslint-disable-next-line react/display-name\n return function (props) {\n return React.createElement(IconBase, __assign({\n attr: __assign({}, data.attr)\n }, props), Tree2Element(data.child));\n };\n}\nexport function IconBase(props) {\n var elem = function (conf) {\n var attr = props.attr,\n size = props.size,\n title = props.title,\n svgProps = __rest(props, [\"attr\", \"size\", \"title\"]);\n var computedSize = size || conf.size || \"1em\";\n var className;\n if (conf.className) className = conf.className;\n if (props.className) className = (className ? className + \" \" : \"\") + props.className;\n return React.createElement(\"svg\", __assign({\n stroke: \"currentColor\",\n fill: \"currentColor\",\n strokeWidth: \"0\"\n }, conf.attr, attr, svgProps, {\n className: className,\n style: __assign(__assign({\n color: props.color || conf.color\n }, conf.style), props.style),\n height: computedSize,\n width: computedSize,\n xmlns: \"http://www.w3.org/2000/svg\"\n }), title && React.createElement(\"title\", null, title), props.children);\n };\n return IconContext !== undefined ? React.createElement(IconContext.Consumer, null, function (conf) {\n return elem(conf);\n }) : elem(DefaultContext);\n}", "import React from \"react\";\nexport var DefaultContext = {\n color: undefined,\n size: undefined,\n className: undefined,\n style: undefined,\n attr: undefined\n};\nexport var IconContext = React.createContext && React.createContext(DefaultContext);"],
|
|
||||||
"mappings": "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",
|
|
||||||
"names": ["import_react", "React", "React"]
|
|
||||||
}
|
|
||||||
-7261
File diff suppressed because it is too large.
Load diff
-7
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 3,
|
|
||||||
"sources": ["../../@babel/runtime/helpers/esm/typeof.js", "../../date-fns/esm/_lib/toInteger/index.js", "../../date-fns/esm/_lib/requiredArgs/index.js", "../../date-fns/esm/toDate/index.js", "../../date-fns/esm/addDays/index.js", "../../date-fns/esm/addMonths/index.js", "../../date-fns/esm/add/index.js", "../../date-fns/esm/isWeekend/index.js", "../../date-fns/esm/isSunday/index.js", "../../date-fns/esm/isSaturday/index.js", "../../date-fns/esm/addBusinessDays/index.js", "../../date-fns/esm/addMilliseconds/index.js", "../../date-fns/esm/addHours/index.js", "../../date-fns/esm/_lib/defaultOptions/index.js", "../../date-fns/esm/startOfWeek/index.js", "../../date-fns/esm/startOfISOWeek/index.js", "../../date-fns/esm/getISOWeekYear/index.js", "../../date-fns/esm/startOfISOWeekYear/index.js", "../../date-fns/esm/_lib/getTimezoneOffsetInMilliseconds/index.js", "../../date-fns/esm/startOfDay/index.js", "../../date-fns/esm/differenceInCalendarDays/index.js", "../../date-fns/esm/setISOWeekYear/index.js", "../../date-fns/esm/addISOWeekYears/index.js", "../../date-fns/esm/addMinutes/index.js", "../../date-fns/esm/addQuarters/index.js", "../../date-fns/esm/addSeconds/index.js", "../../date-fns/esm/addWeeks/index.js", "../../date-fns/esm/addYears/index.js", "../../date-fns/esm/areIntervalsOverlapping/index.js", "../../date-fns/esm/max/index.js", "../../date-fns/esm/min/index.js", "../../date-fns/esm/clamp/index.js", "../../date-fns/esm/closestIndexTo/index.js", "../../date-fns/esm/closestTo/index.js", "../../date-fns/esm/compareAsc/index.js", "../../date-fns/esm/compareDesc/index.js", "../../date-fns/esm/constants/index.js", "../../date-fns/esm/daysToWeeks/index.js", "../../date-fns/esm/isSameDay/index.js", "../../date-fns/esm/isDate/index.js", "../../date-fns/esm/isValid/index.js", "../../date-fns/esm/differenceInBusinessDays/index.js", "../../date-fns/esm/differenceInCalendarISOWeekYears/index.js", "../../date-fns/esm/differenceInCalendarISOWeeks/index.js", "../../date-fns/esm/differenceInCalendarMonths/index.js", "../../date-fns/esm/getQuarter/index.js", "../../date-fns/esm/differenceInCalendarQuarters/index.js", "../../date-fns/esm/differenceInCalendarWeeks/index.js", "../../date-fns/esm/differenceInCalendarYears/index.js", "../../date-fns/esm/differenceInDays/index.js", "../../date-fns/esm/differenceInMilliseconds/index.js", "../../date-fns/esm/_lib/roundingMethods/index.js", "../../date-fns/esm/differenceInHours/index.js", "../../date-fns/esm/subISOWeekYears/index.js", "../../date-fns/esm/differenceInISOWeekYears/index.js", "../../date-fns/esm/differenceInMinutes/index.js", "../../date-fns/esm/endOfDay/index.js", "../../date-fns/esm/endOfMonth/index.js", "../../date-fns/esm/isLastDayOfMonth/index.js", "../../date-fns/esm/differenceInMonths/index.js", "../../date-fns/esm/differenceInQuarters/index.js", "../../date-fns/esm/differenceInSeconds/index.js", "../../date-fns/esm/differenceInWeeks/index.js", "../../date-fns/esm/differenceInYears/index.js", "../../date-fns/esm/eachDayOfInterval/index.js", "../../date-fns/esm/eachHourOfInterval/index.js", "../../date-fns/esm/startOfMinute/index.js", "../../date-fns/esm/eachMinuteOfInterval/index.js", "../../date-fns/esm/eachMonthOfInterval/index.js", "../../date-fns/esm/startOfQuarter/index.js", "../../date-fns/esm/eachQuarterOfInterval/index.js", "../../date-fns/esm/eachWeekOfInterval/index.js", "../../date-fns/esm/eachWeekendOfInterval/index.js", "../../date-fns/esm/startOfMonth/index.js", "../../date-fns/esm/eachWeekendOfMonth/index.js", "../../date-fns/esm/endOfYear/index.js", "../../date-fns/esm/startOfYear/index.js", "../../date-fns/esm/eachWeekendOfYear/index.js", "../../date-fns/esm/eachYearOfInterval/index.js", "../../date-fns/esm/endOfDecade/index.js", "../../date-fns/esm/endOfHour/index.js", "../../date-fns/esm/endOfWeek/index.js", "../../date-fns/esm/endOfISOWeek/index.js", "../../date-fns/esm/endOfISOWeekYear/index.js", "../../date-fns/esm/endOfMinute/index.js", "../../date-fns/esm/endOfQuarter/index.js", "../../date-fns/esm/endOfSecond/index.js", "../../date-fns/esm/endOfToday/index.js", "../../date-fns/esm/endOfTomorrow/index.js", "../../date-fns/esm/endOfYesterday/index.js", "../../date-fns/esm/subMilliseconds/index.js", "../../date-fns/esm/_lib/getUTCDayOfYear/index.js", "../../date-fns/esm/_lib/startOfUTCISOWeek/index.js", "../../date-fns/esm/_lib/getUTCISOWeekYear/index.js", "../../date-fns/esm/_lib/startOfUTCISOWeekYear/index.js", "../../date-fns/esm/_lib/getUTCISOWeek/index.js", "../../date-fns/esm/_lib/startOfUTCWeek/index.js", "../../date-fns/esm/_lib/getUTCWeekYear/index.js", "../../date-fns/esm/_lib/startOfUTCWeekYear/index.js", "../../date-fns/esm/_lib/getUTCWeek/index.js", "../../date-fns/esm/_lib/addLeadingZeros/index.js", "../../date-fns/esm/_lib/format/lightFormatters/index.js", "../../date-fns/esm/_lib/format/formatters/index.js", "../../date-fns/esm/_lib/format/longFormatters/index.js", "../../date-fns/esm/_lib/protectedTokens/index.js", ".Line truncated
|
|
||||||
"sourcesContent": ["function _typeof(o) {\n \"@babel/helpers - typeof\";\n\n return _typeof = \"function\" == typeof Symbol && \"symbol\" == typeof Symbol.iterator ? function (o) {\n return typeof o;\n } : function (o) {\n return o && \"function\" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? \"symbol\" : typeof o;\n }, _typeof(o);\n}\nexport { _typeof as default };", "export default function toInteger(dirtyNumber) {\n if (dirtyNumber === null || dirtyNumber === true || dirtyNumber === false) {\n return NaN;\n }\n var number = Number(dirtyNumber);\n if (isNaN(number)) {\n return number;\n }\n return number < 0 ? Math.ceil(number) : Math.floor(number);\n}", "export default function requiredArgs(required, args) {\n if (args.length < required) {\n throw new TypeError(required + ' argument' + (required > 1 ? 's' : '') + ' required, but only ' + args.length + ' present');\n }\n}", "import _typeof from \"@babel/runtime/helpers/esm/typeof\";\nimport requiredArgs from \"../_lib/requiredArgs/index.js\";\n/**\n * @name toDate\n * @category Common Helpers\n * @summary Convert the given argument to an instance of Date.\n *\n * @description\n * Convert the given argument to an instance of Date.\n *\n * If the argument is an instance of Date, the function returns its clone.\n *\n * If the argument is a number, it is treated as a timestamp.\n *\n * If the argument is none of the above, the function returns Invalid Date.\n *\n * **Note**: *all* Date arguments passed to any *date-fns* function is processed by `toDate`.\n *\n * @param {Date|Number} argument - the value to convert\n * @returns {Date} the parsed date in the local time zone\n * @throws {TypeError} 1 argument required\n *\n * @example\n * // Clone the date:\n * const result = toDate(new Date(2014, 1, 11, 11, 30, 30))\n * //=> Tue Feb 11 2014 11:30:30\n *\n * @example\n * // Convert the timestamp to date:\n * const result = toDate(1392098430000)\n * //=> Tue Feb 11 2014 11:30:30\n */\nexport default function toDate(argument) {\n requiredArgs(1, arguments);\n var argStr = Object.prototype.toString.call(argument);\n\n // Clone the date\n if (argument instanceof Date || _typeof(argument) === 'object' && argStr === '[object Date]') {\n // Prevent the date to lose the milliseconds when passed to new Date() in IE10\n return new Date(argument.getTime());\n } else if (typeof argument === 'number' || argStr === '[object Number]') {\n return new Date(argument);\n } else {\n if ((typeof argument === 'string' || argStr === '[object String]') && typeof console !== 'undefined') {\n // eslint-disable-next-line no-console\n console.warn(\"Starting with v2.0.0-beta.1 date-fns doesn't accept strings as date arguments. Please use `parseISO` to parse strings. See: https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#string-arguments\");\n // eslint-disable-next-line no-console\n console.warn(new Error().stack);\n }\n return new Date(NaN);\n }\n}", "import toInteger from \"../_lib/toInteger/index.js\";\nimport toDate from \"../toDate/index.js\";\nimport requiredArgs from \"../_lib/requiredArgs/index.js\";\n/**\n * @name addDays\n * @category Day Helpers\n * @summary Add the specified number of days to the given date.\n *\n * @description\n * Add the specified number of days to the given date.\n *\n * @param {Date|Number} date - the date to be changed\n * @param {Number} amount - the amount of days to be added. Positive decimals will be rounded using `Math.floor`, decimals less than zero will be rounded using `Math.ceil`.\n * @returns {Date} - the new date with the days added\n * @throws {TypeError} - 2 arguments required\n *\n * @example\n * // Add 10 days to 1 September 2014:\n * const result = addDays(new Date(2014, 8, 1), 10)\n * //=> Thu Sep 11 2014 00:00:00\n */\nexport default function addDays(dirtyDate, dirtyAmount) {\n requiredArgs(2, arguments);\n var date = toDate(dirtyDate);\n var amount = toInteger(dirtyAmount);\n if (isNaN(amount)) {\n return new Date(NaN);\n }\n if (!amount) {\n // If 0 days, no-op to avoid changing times in the hour before end of DST\n return date;\n }\n date.setDate(date.getDate() + amount);\n return date;\n}", "import toInteger from \"../_lib/toInteger/index.js\";\nimport toDate from \"../toDate/index.js\";\nimport requiredArgs from \"../_lib/requiredArgs/index.js\";\n/**\n * @name addMonths\n * @category Month Helpers\n * @summary Add the specified number of months to the given date.\n *\n * @description\n * Add the specified number of months to the given date.\n *\n * @param {Date|Number} date - the date to be changed\n * @param {Number} amount - the amount of months to be added. Positive decimals will be rounded using `Math.floor`, decimals less than zero will be rounded using `Math.ceil`.\n * @returns {Date} the new date with the months added\n * @throws {TypeError} 2 arguments required\n *\n * @example\n * // ALine truncated
|
|
||||||
"mappings": "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 truncated
|
|
||||||
"names": ["o", "months", "days", "defaultOptions", "months", "days", "days", "MILLISECONDS_IN_WEEK", "defaultOptions", "MILLISECONDS_IN_DAY", "MILLISECONDS_IN_WEEK", "defaultOptions", "defaultOptions", "defaultOptions", "MILLISECONDS_IN_WEEK", "milliseconds", "formatters", "localize", "y", "M", "d", "a", "h", "H", "m", "s", "S", "dateLongFormatter", "formatLong", "timeLongFormatter", "dateTimeLongFormatter", "format", "formatDistance", "format", "formatRelative", "ordinalNumber", "valueCallback", "defaultOptions", "locale", "formatDistance", "defaultOptions", "locale", "months", "days", "MILLISECONDS_IN_MINUTE", "MINUTES_IN_DAY", "MINUTES_IN_MONTH", "defaultOptions", "locale", "milliseconds", "days", "months", "formatDistance", "defaultOptions", "locale", "format", "m", "format", "format", "months", "days", "milliseconds", "formatRelative", "defaultOptions", "locale", "lastDayOfMonth", "getDefaultOptions", "MILLISECONDS_IN_WEEK", "MILLISECONDS_IN_WEEK", "milliseconds", "MILLISECONDS_IN_DAY", "defaultOptions", "defaultOptions", "MILLISECONDS_IN_WEEK", "defaultOptions", "a", "e", "a", "t", "e", "t", "F", "r", "a", "u", "s", "e", "t", "e", "t", "e", "t", "t", "_isNativeReflectConstruct", "t", "e", "t", "e", "s", "a", "t", "e", "i", "t", "i", "e", "t", "e", "t", "Setter", "ValueSetter", "set", "DateToSystemTimezoneSetter", "Parser", "match", "EraParser", "parse", "match", "set", "YearParser", "parse", "match", "valueCallback", "set", "LocalWeekYearParser", "parse", "match", "valueCallback", "set", "ISOWeekYearParser", "parse", "set", "ExtendedYearParser", "parse", "set", "QuarterParser", "parse", "match", "set", "StandAloneQuarterParser", "parse", "match", "set", "MonthParser", "parse", "match", "valueCallback", "set", "StandAloneMonthParser", "parse", "match", "valueCallback", "set", "LocalWeekParser", "parse", "match", "set", "ISOWeekParser", "parse", "match", "set", "DateParser", "parse", "match", "isLeapYear", "set", "DayOfYearParser", "parse", "match", "isLeapYear", "set", "defaultOptions", "DayParser", "parse", "match", "set", "LocalDayParser", "parse", "match", "valueCallback", "set", "StandAloneLocalDayParser", "parse", "match", "valueCallback", "set", "ISODayParser", "parse", "match", "valueCallback", "set", "AMPMParser", "parse", "match", "set", "AMPMMidnightParser", "parse", "match", "set", "DayPeriodParser", "parse", "match", "set", "Hour1to12Parser", "parse", "match", "set", "Hour0to23Parser", "parse", "match", "set", "Hour0To11Parser", "parse", "match", "set", "Hour1To24Parser", "parse", "match", "set", "MinuteParser", "parse", "match", "set", "SecondParser", "parse", "match", "set", "FractionOfSecondParser", "parse", "valueCallback", "set", "ISOTimezoneWithZParser", "parse", "set", "ISOTimezoneParser", "parse", "set", "TimestampSecondsParser", "parse", "set", "TimestampMillisecondsParser", "parse", "set", "formattingTokensRegExp", "longFormattingTokensRegExp", "escapedStringRegExp", "doubleQuoteRegExp", "unescapedLatinCharacterRegExp", "defaultOptions", "locale", "_loop", "cleanEscapedString", "setter", "a", "b", "defaultOptions", "formattingTokensRegExp", "escapedStringRegExp", "doubleQuoteRegExp", "unescapedLatinCharacterRegExp", "cleanEscapedString", "daysInYear", "months", "days", "milliseconds", "milliseconds", "milliseconds", "months", "months", "isLeapYearIndex", "defaultOptions", "setDefaultOptions", "defaultOptions", "milliseconds", "defaultOptions", "months", "days"]
|
|
||||||
}
|
|
||||||
-21628
File diff suppressed because it is too large.
Load diff
-7
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 3,
|
|
||||||
"sources": ["../../scheduler/cjs/scheduler.development.js", "../../scheduler/index.js", "../../react-dom/cjs/react-dom.development.js", "../../react-dom/index.js"],
|
|
||||||
"sourcesContent": ["/**\n * @license React\n * scheduler.development.js\n *\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n'use strict';\n\nif (process.env.NODE_ENV !== \"production\") {\n (function() {\n\n 'use strict';\n\n/* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */\nif (\n typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' &&\n typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart ===\n 'function'\n) {\n __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error());\n}\n var enableSchedulerDebugging = false;\nvar enableProfiling = false;\nvar frameYieldMs = 5;\n\nfunction push(heap, node) {\n var index = heap.length;\n heap.push(node);\n siftUp(heap, node, index);\n}\nfunction peek(heap) {\n return heap.length === 0 ? null : heap[0];\n}\nfunction pop(heap) {\n if (heap.length === 0) {\n return null;\n }\n\n var first = heap[0];\n var last = heap.pop();\n\n if (last !== first) {\n heap[0] = last;\n siftDown(heap, last, 0);\n }\n\n return first;\n}\n\nfunction siftUp(heap, node, i) {\n var index = i;\n\n while (index > 0) {\n var parentIndex = index - 1 >>> 1;\n var parent = heap[parentIndex];\n\n if (compare(parent, node) > 0) {\n // The parent is larger. Swap positions.\n heap[parentIndex] = node;\n heap[index] = parent;\n index = parentIndex;\n } else {\n // The parent is smaller. Exit.\n return;\n }\n }\n}\n\nfunction siftDown(heap, node, i) {\n var index = i;\n var length = heap.length;\n var halfLength = length >>> 1;\n\n while (index < halfLength) {\n var leftIndex = (index + 1) * 2 - 1;\n var left = heap[leftIndex];\n var rightIndex = leftIndex + 1;\n var right = heap[rightIndex]; // If the left or right node is smaller, swap with the smaller of those.\n\n if (compare(left, node) < 0) {\n if (rightIndex < length && compare(right, left) < 0) {\n heap[index] = right;\n heap[rightIndex] = node;\n index = rightIndex;\n } else {\n heap[index] = left;\n heap[leftIndex] = node;\n index = leftIndex;\n }\n } else if (rightIndex < length && compare(right, node) < 0) {\n heap[index] = right;\n heap[rightIndex] = node;\n index = rightIndex;\n } else {\n // Neither child is smaller. Exit.\n return;\n }\n }\n}\n\nfunction compare(a, b) {\n // Compare sort index first, then task id.\n var diff = a.sortIndex - b.sortIndex;\n return diff !== 0 ? diff : a.id - b.id;\n}\n\n// TODO: Use symbols?\nvar ImmediatePriority = 1;\nvar UserBlockingPriority = 2;\nvar NormalPriority = 3;\nvar LowPriority = 4;\nvar IdlePriority = 5;\n\nfunction markTaskErrored(task, ms) {\n}\n\n/* eslint-disable no-var */\n\nvar hasPerformanceNow = typeof performance === 'object' && typeof performance.now === 'function';\n\nif (hasPerformanceNow) {\n var localPerformance = performance;\n\n exports.unstable_now = function () {\n return localPerformance.now();\n };\n} else {\n var localDate = Date;\n var initialTime = localDate.now();\n\n exports.unstable_now = function () {\n return localDate.now() - initialTime;\n };\n} // Max 31 bit integer. The max integer size in V8 for 32-bit systems.\n// Math.pow(2, 30) - 1\n// 0b111111111111111111111111111111\n\n\nvar maxSigned31BitInt = 1073741823; // Times out immediately\n\nvar IMMEDIATE_PRIORITY_TIMEOUT = -1; // Eventually times out\n\nvar USER_BLOCKING_PRIORITY_TIMEOUT = 250;\nvar NORMAL_PRIORITY_TIMEOUT = 5000;\nvar LOW_PRIORITY_TIMEOUT = 10000; // Never times out\n\nvar IDLE_PRIORITY_TIMEOUT = maxSigned31BitInt; // Tasks are stored on a min heap\n\nvar taskQueue = [];\nvar timerQueue = []; // Incrementing id counter. Used to maintain insertion order.\n\nvar taskIdCounter = 1; // Pausing the scheduler is useful for debugging.\nvar currentTask = null;\nvar currentPriorityLevel = NormalPriority; // This is set while performing work, to prevent re-entrance.\n\nvar isPerformingWork = false;\nvar isHostCallbackScheduled = false;\nvar isHostTimeoutScheduled = false; // Capture local references to native APIs, in case a polyfill overrides them.\n\nvar localSetTimeout = typeof setTimeout === 'function' ? setTimeout : null;\nvar localClearTimeout = typeof clearTimeout === 'function' ? clearTimeout : null;\nvar localSetImmediate = typeof setImmediate !== 'undefined' ? setImmediate : null; // IE and Node.js + jsdom\n\nvar isInputPending = typeof navigator !== 'undefined' && navigator.scheduling !== undefined && navigator.scheduling.isInputPending !== undefined ? navigator.scheduling.isInputPending.bind(navigator.scheduling) : null;\n\nfunction advanceTimers(currentTime) {\n // Check for tasks that are no longer delayed and add them to the queue.\n var timer = peek(timerQueue);\n\n while (timer !== null) {\n if (timer.caLine truncated
|
|
||||||
"mappings": "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 truncated
|
|
||||||
"names": ["initialTime", "startTime", "ReactDebugCurrentFrame", "prefix", "sanitizeURL", "workInProgress", "get", "set", "isHydrating", "options", "style", "registrationNameDependencies", "possibleRegistrationNames", "error", "callCallback", "current", "root", "index", "renderLanes", "queuedEvent", "prefixes", "has", "updateFragment", "child", "reconcileChildFibers", "error$1", "render", "subtreeRenderLanes", "hasContextChanged", "commitTime", "shouldFireAfterActiveInstanceBlur", "hydrate", "currentHook", "ReactCurrentDispatcher"]
|
|
||||||
}
|
|
||||||
+2
-2
@@ -256,8 +256,8 @@ import {
|
|||||||
weeksToDays,
|
weeksToDays,
|
||||||
yearsToMonths,
|
yearsToMonths,
|
||||||
yearsToQuarters
|
yearsToQuarters
|
||||||
} from "./chunk-TOOCKHFL.js";
|
} from "./chunk-SJKHQ62W.js";
|
||||||
import "./chunk-5WRI5ZAA.js";
|
import "./chunk-G3PMV62Z.js";
|
||||||
export {
|
export {
|
||||||
add,
|
add,
|
||||||
addBusinessDays,
|
addBusinessDays,
|
||||||
|
|||||||
+2
-2
@@ -11,8 +11,8 @@ import {
|
|||||||
requiredArgs,
|
requiredArgs,
|
||||||
startOfUTCWeek,
|
startOfUTCWeek,
|
||||||
toDate
|
toDate
|
||||||
} from "./chunk-TOOCKHFL.js";
|
} from "./chunk-SJKHQ62W.js";
|
||||||
import "./chunk-5WRI5ZAA.js";
|
import "./chunk-G3PMV62Z.js";
|
||||||
|
|
||||||
// node_modules/date-fns/esm/locale/af/_lib/formatDistance/index.js
|
// node_modules/date-fns/esm/locale/af/_lib/formatDistance/index.js
|
||||||
var formatDistanceLocale = {
|
var formatDistanceLocale = {
|
||||||
|
|||||||
+3
-3
@@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
require_react_dom
|
require_react_dom
|
||||||
} from "./chunk-TYILIMWK.js";
|
} from "./chunk-PJEEZAML.js";
|
||||||
import "./chunk-CANBAPAS.js";
|
import "./chunk-DRWLMN53.js";
|
||||||
import "./chunk-5WRI5ZAA.js";
|
import "./chunk-G3PMV62Z.js";
|
||||||
export default require_react_dom();
|
export default require_react_dom();
|
||||||
//# sourceMappingURL=react-dom.js.map
|
//# sourceMappingURL=react-dom.js.map
|
||||||
+3
-3
@@ -1,10 +1,10 @@
|
|||||||
import {
|
import {
|
||||||
require_react_dom
|
require_react_dom
|
||||||
} from "./chunk-TYILIMWK.js";
|
} from "./chunk-PJEEZAML.js";
|
||||||
import "./chunk-CANBAPAS.js";
|
import "./chunk-DRWLMN53.js";
|
||||||
import {
|
import {
|
||||||
__commonJS
|
__commonJS
|
||||||
} from "./chunk-5WRI5ZAA.js";
|
} from "./chunk-G3PMV62Z.js";
|
||||||
|
|
||||||
// node_modules/react-dom/client.js
|
// node_modules/react-dom/client.js
|
||||||
var require_client = __commonJS({
|
var require_client = __commonJS({
|
||||||
|
|||||||
+3
-3
@@ -1,8 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
GenIcon
|
GenIcon
|
||||||
} from "./chunk-EHLE63A5.js";
|
} from "./chunk-DQJKJRV5.js";
|
||||||
import "./chunk-CANBAPAS.js";
|
import "./chunk-DRWLMN53.js";
|
||||||
import "./chunk-5WRI5ZAA.js";
|
import "./chunk-G3PMV62Z.js";
|
||||||
|
|
||||||
// node_modules/react-icons/fa/index.esm.js
|
// node_modules/react-icons/fa/index.esm.js
|
||||||
function Fa500Px(props) {
|
function Fa500Px(props) {
|
||||||
|
|||||||
+3
-3
@@ -1,8 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
GenIcon
|
GenIcon
|
||||||
} from "./chunk-EHLE63A5.js";
|
} from "./chunk-DQJKJRV5.js";
|
||||||
import "./chunk-CANBAPAS.js";
|
import "./chunk-DRWLMN53.js";
|
||||||
import "./chunk-5WRI5ZAA.js";
|
import "./chunk-G3PMV62Z.js";
|
||||||
|
|
||||||
// node_modules/react-icons/fa6/index.esm.js
|
// node_modules/react-icons/fa6/index.esm.js
|
||||||
function Fa42Group(props) {
|
function Fa42Group(props) {
|
||||||
|
|||||||
+3
-3
@@ -1,12 +1,12 @@
|
|||||||
import {
|
import {
|
||||||
require_react_dom
|
require_react_dom
|
||||||
} from "./chunk-TYILIMWK.js";
|
} from "./chunk-PJEEZAML.js";
|
||||||
import {
|
import {
|
||||||
require_react
|
require_react
|
||||||
} from "./chunk-CANBAPAS.js";
|
} from "./chunk-DRWLMN53.js";
|
||||||
import {
|
import {
|
||||||
__toESM
|
__toESM
|
||||||
} from "./chunk-5WRI5ZAA.js";
|
} from "./chunk-G3PMV62Z.js";
|
||||||
|
|
||||||
// node_modules/react-router-dom/dist/index.js
|
// node_modules/react-router-dom/dist/index.js
|
||||||
var React2 = __toESM(require_react());
|
var React2 = __toESM(require_react());
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
require_react
|
require_react
|
||||||
} from "./chunk-CANBAPAS.js";
|
} from "./chunk-DRWLMN53.js";
|
||||||
import "./chunk-5WRI5ZAA.js";
|
import "./chunk-G3PMV62Z.js";
|
||||||
export default require_react();
|
export default require_react();
|
||||||
//# sourceMappingURL=react.js.map
|
//# sourceMappingURL=react.js.map
|
||||||
+2
-2
@@ -1,9 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
require_react
|
require_react
|
||||||
} from "./chunk-CANBAPAS.js";
|
} from "./chunk-DRWLMN53.js";
|
||||||
import {
|
import {
|
||||||
__commonJS
|
__commonJS
|
||||||
} from "./chunk-5WRI5ZAA.js";
|
} from "./chunk-G3PMV62Z.js";
|
||||||
|
|
||||||
// node_modules/react/cjs/react-jsx-dev-runtime.development.js
|
// node_modules/react/cjs/react-jsx-dev-runtime.development.js
|
||||||
var require_react_jsx_dev_runtime_development = __commonJS({
|
var require_react_jsx_dev_runtime_development = __commonJS({
|
||||||
|
|||||||
+4
-922
@@ -1,925 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
require_react
|
require_jsx_runtime
|
||||||
} from "./chunk-CANBAPAS.js";
|
} from "./chunk-6PXSGDAH.js";
|
||||||
import {
|
import "./chunk-DRWLMN53.js";
|
||||||
__commonJS
|
import "./chunk-G3PMV62Z.js";
|
||||||
} from "./chunk-5WRI5ZAA.js";
|
|
||||||
|
|
||||||
// node_modules/react/cjs/react-jsx-runtime.development.js
|
|
||||||
var require_react_jsx_runtime_development = __commonJS({
|
|
||||||
"node_modules/react/cjs/react-jsx-runtime.development.js"(exports) {
|
|
||||||
"use strict";
|
|
||||||
if (true) {
|
|
||||||
(function() {
|
|
||||||
"use strict";
|
|
||||||
var React = require_react();
|
|
||||||
var REACT_ELEMENT_TYPE = Symbol.for("react.element");
|
|
||||||
var REACT_PORTAL_TYPE = Symbol.for("react.portal");
|
|
||||||
var REACT_FRAGMENT_TYPE = Symbol.for("react.fragment");
|
|
||||||
var REACT_STRICT_MODE_TYPE = Symbol.for("react.strict_mode");
|
|
||||||
var REACT_PROFILER_TYPE = Symbol.for("react.profiler");
|
|
||||||
var REACT_PROVIDER_TYPE = Symbol.for("react.provider");
|
|
||||||
var REACT_CONTEXT_TYPE = Symbol.for("react.context");
|
|
||||||
var REACT_FORWARD_REF_TYPE = Symbol.for("react.forward_ref");
|
|
||||||
var REACT_SUSPENSE_TYPE = Symbol.for("react.suspense");
|
|
||||||
var REACT_SUSPENSE_LIST_TYPE = Symbol.for("react.suspense_list");
|
|
||||||
var REACT_MEMO_TYPE = Symbol.for("react.memo");
|
|
||||||
var REACT_LAZY_TYPE = Symbol.for("react.lazy");
|
|
||||||
var REACT_OFFSCREEN_TYPE = Symbol.for("react.offscreen");
|
|
||||||
var MAYBE_ITERATOR_SYMBOL = Symbol.iterator;
|
|
||||||
var FAUX_ITERATOR_SYMBOL = "@@iterator";
|
|
||||||
function getIteratorFn(maybeIterable) {
|
|
||||||
if (maybeIterable === null || typeof maybeIterable !== "object") {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
var maybeIterator = MAYBE_ITERATOR_SYMBOL && maybeIterable[MAYBE_ITERATOR_SYMBOL] || maybeIterable[FAUX_ITERATOR_SYMBOL];
|
|
||||||
if (typeof maybeIterator === "function") {
|
|
||||||
return maybeIterator;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
var ReactSharedInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
|
|
||||||
function error(format) {
|
|
||||||
{
|
|
||||||
{
|
|
||||||
for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
|
|
||||||
args[_key2 - 1] = arguments[_key2];
|
|
||||||
}
|
|
||||||
printWarning("error", format, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function printWarning(level, format, args) {
|
|
||||||
{
|
|
||||||
var ReactDebugCurrentFrame2 = ReactSharedInternals.ReactDebugCurrentFrame;
|
|
||||||
var stack = ReactDebugCurrentFrame2.getStackAddendum();
|
|
||||||
if (stack !== "") {
|
|
||||||
format += "%s";
|
|
||||||
args = args.concat([stack]);
|
|
||||||
}
|
|
||||||
var argsWithFormat = args.map(function(item) {
|
|
||||||
return String(item);
|
|
||||||
});
|
|
||||||
argsWithFormat.unshift("Warning: " + format);
|
|
||||||
Function.prototype.apply.call(console[level], console, argsWithFormat);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var enableScopeAPI = false;
|
|
||||||
var enableCacheElement = false;
|
|
||||||
var enableTransitionTracing = false;
|
|
||||||
var enableLegacyHidden = false;
|
|
||||||
var enableDebugTracing = false;
|
|
||||||
var REACT_MODULE_REFERENCE;
|
|
||||||
{
|
|
||||||
REACT_MODULE_REFERENCE = Symbol.for("react.module.reference");
|
|
||||||
}
|
|
||||||
function isValidElementType(type) {
|
|
||||||
if (typeof type === "string" || typeof type === "function") {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (type === REACT_FRAGMENT_TYPE || type === REACT_PROFILER_TYPE || enableDebugTracing || type === REACT_STRICT_MODE_TYPE || type === REACT_SUSPENSE_TYPE || type === REACT_SUSPENSE_LIST_TYPE || enableLegacyHidden || type === REACT_OFFSCREEN_TYPE || enableScopeAPI || enableCacheElement || enableTransitionTracing) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (typeof type === "object" && type !== null) {
|
|
||||||
if (type.$$typeof === REACT_LAZY_TYPE || type.$$typeof === REACT_MEMO_TYPE || type.$$typeof === REACT_PROVIDER_TYPE || type.$$typeof === REACT_CONTEXT_TYPE || type.$$typeof === REACT_FORWARD_REF_TYPE || // This needs to include all possible module reference object
|
|
||||||
// types supported by any Flight configuration anywhere since
|
|
||||||
// we don't know which Flight build this will end up being used
|
|
||||||
// with.
|
|
||||||
type.$$typeof === REACT_MODULE_REFERENCE || type.getModuleId !== void 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
function getWrappedName(outerType, innerType, wrapperName) {
|
|
||||||
var displayName = outerType.displayName;
|
|
||||||
if (displayName) {
|
|
||||||
return displayName;
|
|
||||||
}
|
|
||||||
var functionName = innerType.displayName || innerType.name || "";
|
|
||||||
return functionName !== "" ? wrapperName + "(" + functionName + ")" : wrapperName;
|
|
||||||
}
|
|
||||||
function getContextName(type) {
|
|
||||||
return type.displayName || "Context";
|
|
||||||
}
|
|
||||||
function getComponentNameFromType(type) {
|
|
||||||
if (type == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
{
|
|
||||||
if (typeof type.tag === "number") {
|
|
||||||
error("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (typeof type === "function") {
|
|
||||||
return type.displayName || type.name || null;
|
|
||||||
}
|
|
||||||
if (typeof type === "string") {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
switch (type) {
|
|
||||||
case REACT_FRAGMENT_TYPE:
|
|
||||||
return "Fragment";
|
|
||||||
case REACT_PORTAL_TYPE:
|
|
||||||
return "Portal";
|
|
||||||
case REACT_PROFILER_TYPE:
|
|
||||||
return "Profiler";
|
|
||||||
case REACT_STRICT_MODE_TYPE:
|
|
||||||
return "StrictMode";
|
|
||||||
case REACT_SUSPENSE_TYPE:
|
|
||||||
return "Suspense";
|
|
||||||
case REACT_SUSPENSE_LIST_TYPE:
|
|
||||||
return "SuspenseList";
|
|
||||||
}
|
|
||||||
if (typeof type === "object") {
|
|
||||||
switch (type.$$typeof) {
|
|
||||||
case REACT_CONTEXT_TYPE:
|
|
||||||
var context = type;
|
|
||||||
return getContextName(context) + ".Consumer";
|
|
||||||
case REACT_PROVIDER_TYPE:
|
|
||||||
var provider = type;
|
|
||||||
return getContextName(provider._context) + ".Provider";
|
|
||||||
case REACT_FORWARD_REF_TYPE:
|
|
||||||
return getWrappedName(type, type.render, "ForwardRef");
|
|
||||||
case REACT_MEMO_TYPE:
|
|
||||||
var outerName = type.displayName || null;
|
|
||||||
if (outerName !== null) {
|
|
||||||
return outerName;
|
|
||||||
}
|
|
||||||
return getComponentNameFromType(type.type) || "Memo";
|
|
||||||
case REACT_LAZY_TYPE: {
|
|
||||||
var lazyComponent = type;
|
|
||||||
var payload = lazyComponent._payload;
|
|
||||||
var init = lazyComponent._init;
|
|
||||||
try {
|
|
||||||
return getComponentNameFromType(init(payload));
|
|
||||||
} catch (x) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
var assign = Object.assign;
|
|
||||||
var disabledDepth = 0;
|
|
||||||
var prevLog;
|
|
||||||
var prevInfo;
|
|
||||||
var prevWarn;
|
|
||||||
var prevError;
|
|
||||||
var prevGroup;
|
|
||||||
var prevGroupCollapsed;
|
|
||||||
var prevGroupEnd;
|
|
||||||
function disabledLog() {
|
|
||||||
}
|
|
||||||
disabledLog.__reactDisabledLog = true;
|
|
||||||
function disableLogs() {
|
|
||||||
{
|
|
||||||
if (disabledDepth === 0) {
|
|
||||||
prevLog = console.log;
|
|
||||||
prevInfo = console.info;
|
|
||||||
prevWarn = console.warn;
|
|
||||||
prevError = console.error;
|
|
||||||
prevGroup = console.group;
|
|
||||||
prevGroupCollapsed = console.groupCollapsed;
|
|
||||||
prevGroupEnd = console.groupEnd;
|
|
||||||
var props = {
|
|
||||||
configurable: true,
|
|
||||||
enumerable: true,
|
|
||||||
value: disabledLog,
|
|
||||||
writable: true
|
|
||||||
};
|
|
||||||
Object.defineProperties(console, {
|
|
||||||
info: props,
|
|
||||||
log: props,
|
|
||||||
warn: props,
|
|
||||||
error: props,
|
|
||||||
group: props,
|
|
||||||
groupCollapsed: props,
|
|
||||||
groupEnd: props
|
|
||||||
});
|
|
||||||
}
|
|
||||||
disabledDepth++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function reenableLogs() {
|
|
||||||
{
|
|
||||||
disabledDepth--;
|
|
||||||
if (disabledDepth === 0) {
|
|
||||||
var props = {
|
|
||||||
configurable: true,
|
|
||||||
enumerable: true,
|
|
||||||
writable: true
|
|
||||||
};
|
|
||||||
Object.defineProperties(console, {
|
|
||||||
log: assign({}, props, {
|
|
||||||
value: prevLog
|
|
||||||
}),
|
|
||||||
info: assign({}, props, {
|
|
||||||
value: prevInfo
|
|
||||||
}),
|
|
||||||
warn: assign({}, props, {
|
|
||||||
value: prevWarn
|
|
||||||
}),
|
|
||||||
error: assign({}, props, {
|
|
||||||
value: prevError
|
|
||||||
}),
|
|
||||||
group: assign({}, props, {
|
|
||||||
value: prevGroup
|
|
||||||
}),
|
|
||||||
groupCollapsed: assign({}, props, {
|
|
||||||
value: prevGroupCollapsed
|
|
||||||
}),
|
|
||||||
groupEnd: assign({}, props, {
|
|
||||||
value: prevGroupEnd
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (disabledDepth < 0) {
|
|
||||||
error("disabledDepth fell below zero. This is a bug in React. Please file an issue.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var ReactCurrentDispatcher = ReactSharedInternals.ReactCurrentDispatcher;
|
|
||||||
var prefix;
|
|
||||||
function describeBuiltInComponentFrame(name, source, ownerFn) {
|
|
||||||
{
|
|
||||||
if (prefix === void 0) {
|
|
||||||
try {
|
|
||||||
throw Error();
|
|
||||||
} catch (x) {
|
|
||||||
var match = x.stack.trim().match(/\n( *(at )?)/);
|
|
||||||
prefix = match && match[1] || "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "\n" + prefix + name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var reentry = false;
|
|
||||||
var componentFrameCache;
|
|
||||||
{
|
|
||||||
var PossiblyWeakMap = typeof WeakMap === "function" ? WeakMap : Map;
|
|
||||||
componentFrameCache = new PossiblyWeakMap();
|
|
||||||
}
|
|
||||||
function describeNativeComponentFrame(fn, construct) {
|
|
||||||
if (!fn || reentry) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
{
|
|
||||||
var frame = componentFrameCache.get(fn);
|
|
||||||
if (frame !== void 0) {
|
|
||||||
return frame;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var control;
|
|
||||||
reentry = true;
|
|
||||||
var previousPrepareStackTrace = Error.prepareStackTrace;
|
|
||||||
Error.prepareStackTrace = void 0;
|
|
||||||
var previousDispatcher;
|
|
||||||
{
|
|
||||||
previousDispatcher = ReactCurrentDispatcher.current;
|
|
||||||
ReactCurrentDispatcher.current = null;
|
|
||||||
disableLogs();
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (construct) {
|
|
||||||
var Fake = function() {
|
|
||||||
throw Error();
|
|
||||||
};
|
|
||||||
Object.defineProperty(Fake.prototype, "props", {
|
|
||||||
set: function() {
|
|
||||||
throw Error();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (typeof Reflect === "object" && Reflect.construct) {
|
|
||||||
try {
|
|
||||||
Reflect.construct(Fake, []);
|
|
||||||
} catch (x) {
|
|
||||||
control = x;
|
|
||||||
}
|
|
||||||
Reflect.construct(fn, [], Fake);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
Fake.call();
|
|
||||||
} catch (x) {
|
|
||||||
control = x;
|
|
||||||
}
|
|
||||||
fn.call(Fake.prototype);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
throw Error();
|
|
||||||
} catch (x) {
|
|
||||||
control = x;
|
|
||||||
}
|
|
||||||
fn();
|
|
||||||
}
|
|
||||||
} catch (sample) {
|
|
||||||
if (sample && control && typeof sample.stack === "string") {
|
|
||||||
var sampleLines = sample.stack.split("\n");
|
|
||||||
var controlLines = control.stack.split("\n");
|
|
||||||
var s = sampleLines.length - 1;
|
|
||||||
var c = controlLines.length - 1;
|
|
||||||
while (s >= 1 && c >= 0 && sampleLines[s] !== controlLines[c]) {
|
|
||||||
c--;
|
|
||||||
}
|
|
||||||
for (; s >= 1 && c >= 0; s--, c--) {
|
|
||||||
if (sampleLines[s] !== controlLines[c]) {
|
|
||||||
if (s !== 1 || c !== 1) {
|
|
||||||
do {
|
|
||||||
s--;
|
|
||||||
c--;
|
|
||||||
if (c < 0 || sampleLines[s] !== controlLines[c]) {
|
|
||||||
var _frame = "\n" + sampleLines[s].replace(" at new ", " at ");
|
|
||||||
if (fn.displayName && _frame.includes("<anonymous>")) {
|
|
||||||
_frame = _frame.replace("<anonymous>", fn.displayName);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
if (typeof fn === "function") {
|
|
||||||
componentFrameCache.set(fn, _frame);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return _frame;
|
|
||||||
}
|
|
||||||
} while (s >= 1 && c >= 0);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
reentry = false;
|
|
||||||
{
|
|
||||||
ReactCurrentDispatcher.current = previousDispatcher;
|
|
||||||
reenableLogs();
|
|
||||||
}
|
|
||||||
Error.prepareStackTrace = previousPrepareStackTrace;
|
|
||||||
}
|
|
||||||
var name = fn ? fn.displayName || fn.name : "";
|
|
||||||
var syntheticFrame = name ? describeBuiltInComponentFrame(name) : "";
|
|
||||||
{
|
|
||||||
if (typeof fn === "function") {
|
|
||||||
componentFrameCache.set(fn, syntheticFrame);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return syntheticFrame;
|
|
||||||
}
|
|
||||||
function describeFunctionComponentFrame(fn, source, ownerFn) {
|
|
||||||
{
|
|
||||||
return describeNativeComponentFrame(fn, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function shouldConstruct(Component) {
|
|
||||||
var prototype = Component.prototype;
|
|
||||||
return !!(prototype && prototype.isReactComponent);
|
|
||||||
}
|
|
||||||
function describeUnknownElementTypeFrameInDEV(type, source, ownerFn) {
|
|
||||||
if (type == null) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
if (typeof type === "function") {
|
|
||||||
{
|
|
||||||
return describeNativeComponentFrame(type, shouldConstruct(type));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (typeof type === "string") {
|
|
||||||
return describeBuiltInComponentFrame(type);
|
|
||||||
}
|
|
||||||
switch (type) {
|
|
||||||
case REACT_SUSPENSE_TYPE:
|
|
||||||
return describeBuiltInComponentFrame("Suspense");
|
|
||||||
case REACT_SUSPENSE_LIST_TYPE:
|
|
||||||
return describeBuiltInComponentFrame("SuspenseList");
|
|
||||||
}
|
|
||||||
if (typeof type === "object") {
|
|
||||||
switch (type.$$typeof) {
|
|
||||||
case REACT_FORWARD_REF_TYPE:
|
|
||||||
return describeFunctionComponentFrame(type.render);
|
|
||||||
case REACT_MEMO_TYPE:
|
|
||||||
return describeUnknownElementTypeFrameInDEV(type.type, source, ownerFn);
|
|
||||||
case REACT_LAZY_TYPE: {
|
|
||||||
var lazyComponent = type;
|
|
||||||
var payload = lazyComponent._payload;
|
|
||||||
var init = lazyComponent._init;
|
|
||||||
try {
|
|
||||||
return describeUnknownElementTypeFrameInDEV(init(payload), source, ownerFn);
|
|
||||||
} catch (x) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
var hasOwnProperty = Object.prototype.hasOwnProperty;
|
|
||||||
var loggedTypeFailures = {};
|
|
||||||
var ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;
|
|
||||||
function setCurrentlyValidatingElement(element) {
|
|
||||||
{
|
|
||||||
if (element) {
|
|
||||||
var owner = element._owner;
|
|
||||||
var stack = describeUnknownElementTypeFrameInDEV(element.type, element._source, owner ? owner.type : null);
|
|
||||||
ReactDebugCurrentFrame.setExtraStackFrame(stack);
|
|
||||||
} else {
|
|
||||||
ReactDebugCurrentFrame.setExtraStackFrame(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function checkPropTypes(typeSpecs, values, location, componentName, element) {
|
|
||||||
{
|
|
||||||
var has = Function.call.bind(hasOwnProperty);
|
|
||||||
for (var typeSpecName in typeSpecs) {
|
|
||||||
if (has(typeSpecs, typeSpecName)) {
|
|
||||||
var error$1 = void 0;
|
|
||||||
try {
|
|
||||||
if (typeof typeSpecs[typeSpecName] !== "function") {
|
|
||||||
var err = Error((componentName || "React class") + ": " + location + " type `" + typeSpecName + "` is invalid; it must be a function, usually from the `prop-types` package, but received `" + typeof typeSpecs[typeSpecName] + "`.This often happens because of typos such as `PropTypes.function` instead of `PropTypes.func`.");
|
|
||||||
err.name = "Invariant Violation";
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
error$1 = typeSpecs[typeSpecName](values, typeSpecName, componentName, location, null, "SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED");
|
|
||||||
} catch (ex) {
|
|
||||||
error$1 = ex;
|
|
||||||
}
|
|
||||||
if (error$1 && !(error$1 instanceof Error)) {
|
|
||||||
setCurrentlyValidatingElement(element);
|
|
||||||
error("%s: type specification of %s `%s` is invalid; the type checker function must return `null` or an `Error` but returned a %s. You may have forgotten to pass an argument to the type checker creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and shape all require an argument).", componentName || "React class", location, typeSpecName, typeof error$1);
|
|
||||||
setCurrentlyValidatingElement(null);
|
|
||||||
}
|
|
||||||
if (error$1 instanceof Error && !(error$1.message in loggedTypeFailures)) {
|
|
||||||
loggedTypeFailures[error$1.message] = true;
|
|
||||||
setCurrentlyValidatingElement(element);
|
|
||||||
error("Failed %s type: %s", location, error$1.message);
|
|
||||||
setCurrentlyValidatingElement(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var isArrayImpl = Array.isArray;
|
|
||||||
function isArray(a) {
|
|
||||||
return isArrayImpl(a);
|
|
||||||
}
|
|
||||||
function typeName(value) {
|
|
||||||
{
|
|
||||||
var hasToStringTag = typeof Symbol === "function" && Symbol.toStringTag;
|
|
||||||
var type = hasToStringTag && value[Symbol.toStringTag] || value.constructor.name || "Object";
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function willCoercionThrow(value) {
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
testStringCoercion(value);
|
|
||||||
return false;
|
|
||||||
} catch (e) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function testStringCoercion(value) {
|
|
||||||
return "" + value;
|
|
||||||
}
|
|
||||||
function checkKeyStringCoercion(value) {
|
|
||||||
{
|
|
||||||
if (willCoercionThrow(value)) {
|
|
||||||
error("The provided key is an unsupported type %s. This value must be coerced to a string before before using it here.", typeName(value));
|
|
||||||
return testStringCoercion(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
|
|
||||||
var RESERVED_PROPS = {
|
|
||||||
key: true,
|
|
||||||
ref: true,
|
|
||||||
__self: true,
|
|
||||||
__source: true
|
|
||||||
};
|
|
||||||
var specialPropKeyWarningShown;
|
|
||||||
var specialPropRefWarningShown;
|
|
||||||
var didWarnAboutStringRefs;
|
|
||||||
{
|
|
||||||
didWarnAboutStringRefs = {};
|
|
||||||
}
|
|
||||||
function hasValidRef(config) {
|
|
||||||
{
|
|
||||||
if (hasOwnProperty.call(config, "ref")) {
|
|
||||||
var getter = Object.getOwnPropertyDescriptor(config, "ref").get;
|
|
||||||
if (getter && getter.isReactWarning) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return config.ref !== void 0;
|
|
||||||
}
|
|
||||||
function hasValidKey(config) {
|
|
||||||
{
|
|
||||||
if (hasOwnProperty.call(config, "key")) {
|
|
||||||
var getter = Object.getOwnPropertyDescriptor(config, "key").get;
|
|
||||||
if (getter && getter.isReactWarning) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return config.key !== void 0;
|
|
||||||
}
|
|
||||||
function warnIfStringRefCannotBeAutoConverted(config, self) {
|
|
||||||
{
|
|
||||||
if (typeof config.ref === "string" && ReactCurrentOwner.current && self && ReactCurrentOwner.current.stateNode !== self) {
|
|
||||||
var componentName = getComponentNameFromType(ReactCurrentOwner.current.type);
|
|
||||||
if (!didWarnAboutStringRefs[componentName]) {
|
|
||||||
error('Component "%s" contains the string ref "%s". Support for string refs will be removed in a future major release. This case cannot be automatically converted to an arrow function. We ask you to manually fix this case by using useRef() or createRef() instead. Learn more about using refs safely here: https://reactjs.org/link/strict-mode-string-ref', getComponentNameFromType(ReactCurrentOwner.current.type), config.ref);
|
|
||||||
didWarnAboutStringRefs[componentName] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function defineKeyPropWarningGetter(props, displayName) {
|
|
||||||
{
|
|
||||||
var warnAboutAccessingKey = function() {
|
|
||||||
if (!specialPropKeyWarningShown) {
|
|
||||||
specialPropKeyWarningShown = true;
|
|
||||||
error("%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://reactjs.org/link/special-props)", displayName);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
warnAboutAccessingKey.isReactWarning = true;
|
|
||||||
Object.defineProperty(props, "key", {
|
|
||||||
get: warnAboutAccessingKey,
|
|
||||||
configurable: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function defineRefPropWarningGetter(props, displayName) {
|
|
||||||
{
|
|
||||||
var warnAboutAccessingRef = function() {
|
|
||||||
if (!specialPropRefWarningShown) {
|
|
||||||
specialPropRefWarningShown = true;
|
|
||||||
error("%s: `ref` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://reactjs.org/link/special-props)", displayName);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
warnAboutAccessingRef.isReactWarning = true;
|
|
||||||
Object.defineProperty(props, "ref", {
|
|
||||||
get: warnAboutAccessingRef,
|
|
||||||
configurable: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var ReactElement = function(type, key, ref, self, source, owner, props) {
|
|
||||||
var element = {
|
|
||||||
// This tag allows us to uniquely identify this as a React Element
|
|
||||||
$$typeof: REACT_ELEMENT_TYPE,
|
|
||||||
// Built-in properties that belong on the element
|
|
||||||
type,
|
|
||||||
key,
|
|
||||||
ref,
|
|
||||||
props,
|
|
||||||
// Record the component responsible for creating this element.
|
|
||||||
_owner: owner
|
|
||||||
};
|
|
||||||
{
|
|
||||||
element._store = {};
|
|
||||||
Object.defineProperty(element._store, "validated", {
|
|
||||||
configurable: false,
|
|
||||||
enumerable: false,
|
|
||||||
writable: true,
|
|
||||||
value: false
|
|
||||||
});
|
|
||||||
Object.defineProperty(element, "_self", {
|
|
||||||
configurable: false,
|
|
||||||
enumerable: false,
|
|
||||||
writable: false,
|
|
||||||
value: self
|
|
||||||
});
|
|
||||||
Object.defineProperty(element, "_source", {
|
|
||||||
configurable: false,
|
|
||||||
enumerable: false,
|
|
||||||
writable: false,
|
|
||||||
value: source
|
|
||||||
});
|
|
||||||
if (Object.freeze) {
|
|
||||||
Object.freeze(element.props);
|
|
||||||
Object.freeze(element);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return element;
|
|
||||||
};
|
|
||||||
function jsxDEV(type, config, maybeKey, source, self) {
|
|
||||||
{
|
|
||||||
var propName;
|
|
||||||
var props = {};
|
|
||||||
var key = null;
|
|
||||||
var ref = null;
|
|
||||||
if (maybeKey !== void 0) {
|
|
||||||
{
|
|
||||||
checkKeyStringCoercion(maybeKey);
|
|
||||||
}
|
|
||||||
key = "" + maybeKey;
|
|
||||||
}
|
|
||||||
if (hasValidKey(config)) {
|
|
||||||
{
|
|
||||||
checkKeyStringCoercion(config.key);
|
|
||||||
}
|
|
||||||
key = "" + config.key;
|
|
||||||
}
|
|
||||||
if (hasValidRef(config)) {
|
|
||||||
ref = config.ref;
|
|
||||||
warnIfStringRefCannotBeAutoConverted(config, self);
|
|
||||||
}
|
|
||||||
for (propName in config) {
|
|
||||||
if (hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) {
|
|
||||||
props[propName] = config[propName];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (type && type.defaultProps) {
|
|
||||||
var defaultProps = type.defaultProps;
|
|
||||||
for (propName in defaultProps) {
|
|
||||||
if (props[propName] === void 0) {
|
|
||||||
props[propName] = defaultProps[propName];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (key || ref) {
|
|
||||||
var displayName = typeof type === "function" ? type.displayName || type.name || "Unknown" : type;
|
|
||||||
if (key) {
|
|
||||||
defineKeyPropWarningGetter(props, displayName);
|
|
||||||
}
|
|
||||||
if (ref) {
|
|
||||||
defineRefPropWarningGetter(props, displayName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var ReactCurrentOwner$1 = ReactSharedInternals.ReactCurrentOwner;
|
|
||||||
var ReactDebugCurrentFrame$1 = ReactSharedInternals.ReactDebugCurrentFrame;
|
|
||||||
function setCurrentlyValidatingElement$1(element) {
|
|
||||||
{
|
|
||||||
if (element) {
|
|
||||||
var owner = element._owner;
|
|
||||||
var stack = describeUnknownElementTypeFrameInDEV(element.type, element._source, owner ? owner.type : null);
|
|
||||||
ReactDebugCurrentFrame$1.setExtraStackFrame(stack);
|
|
||||||
} else {
|
|
||||||
ReactDebugCurrentFrame$1.setExtraStackFrame(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var propTypesMisspellWarningShown;
|
|
||||||
{
|
|
||||||
propTypesMisspellWarningShown = false;
|
|
||||||
}
|
|
||||||
function isValidElement(object) {
|
|
||||||
{
|
|
||||||
return typeof object === "object" && object !== null && object.$$typeof === REACT_ELEMENT_TYPE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function getDeclarationErrorAddendum() {
|
|
||||||
{
|
|
||||||
if (ReactCurrentOwner$1.current) {
|
|
||||||
var name = getComponentNameFromType(ReactCurrentOwner$1.current.type);
|
|
||||||
if (name) {
|
|
||||||
return "\n\nCheck the render method of `" + name + "`.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function getSourceInfoErrorAddendum(source) {
|
|
||||||
{
|
|
||||||
if (source !== void 0) {
|
|
||||||
var fileName = source.fileName.replace(/^.*[\\\/]/, "");
|
|
||||||
var lineNumber = source.lineNumber;
|
|
||||||
return "\n\nCheck your code at " + fileName + ":" + lineNumber + ".";
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var ownerHasKeyUseWarning = {};
|
|
||||||
function getCurrentComponentErrorInfo(parentType) {
|
|
||||||
{
|
|
||||||
var info = getDeclarationErrorAddendum();
|
|
||||||
if (!info) {
|
|
||||||
var parentName = typeof parentType === "string" ? parentType : parentType.displayName || parentType.name;
|
|
||||||
if (parentName) {
|
|
||||||
info = "\n\nCheck the top-level render call using <" + parentName + ">.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function validateExplicitKey(element, parentType) {
|
|
||||||
{
|
|
||||||
if (!element._store || element._store.validated || element.key != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
element._store.validated = true;
|
|
||||||
var currentComponentErrorInfo = getCurrentComponentErrorInfo(parentType);
|
|
||||||
if (ownerHasKeyUseWarning[currentComponentErrorInfo]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ownerHasKeyUseWarning[currentComponentErrorInfo] = true;
|
|
||||||
var childOwner = "";
|
|
||||||
if (element && element._owner && element._owner !== ReactCurrentOwner$1.current) {
|
|
||||||
childOwner = " It was passed a child from " + getComponentNameFromType(element._owner.type) + ".";
|
|
||||||
}
|
|
||||||
setCurrentlyValidatingElement$1(element);
|
|
||||||
error('Each child in a list should have a unique "key" prop.%s%s See https://reactjs.org/link/warning-keys for more information.', currentComponentErrorInfo, childOwner);
|
|
||||||
setCurrentlyValidatingElement$1(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function validateChildKeys(node, parentType) {
|
|
||||||
{
|
|
||||||
if (typeof node !== "object") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (isArray(node)) {
|
|
||||||
for (var i = 0; i < node.length; i++) {
|
|
||||||
var child = node[i];
|
|
||||||
if (isValidElement(child)) {
|
|
||||||
validateExplicitKey(child, parentType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (isValidElement(node)) {
|
|
||||||
if (node._store) {
|
|
||||||
node._store.validated = true;
|
|
||||||
}
|
|
||||||
} else if (node) {
|
|
||||||
var iteratorFn = getIteratorFn(node);
|
|
||||||
if (typeof iteratorFn === "function") {
|
|
||||||
if (iteratorFn !== node.entries) {
|
|
||||||
var iterator = iteratorFn.call(node);
|
|
||||||
var step;
|
|
||||||
while (!(step = iterator.next()).done) {
|
|
||||||
if (isValidElement(step.value)) {
|
|
||||||
validateExplicitKey(step.value, parentType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function validatePropTypes(element) {
|
|
||||||
{
|
|
||||||
var type = element.type;
|
|
||||||
if (type === null || type === void 0 || typeof type === "string") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var propTypes;
|
|
||||||
if (typeof type === "function") {
|
|
||||||
propTypes = type.propTypes;
|
|
||||||
} else if (typeof type === "object" && (type.$$typeof === REACT_FORWARD_REF_TYPE || // Note: Memo only checks outer props here.
|
|
||||||
// Inner props are checked in the reconciler.
|
|
||||||
type.$$typeof === REACT_MEMO_TYPE)) {
|
|
||||||
propTypes = type.propTypes;
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (propTypes) {
|
|
||||||
var name = getComponentNameFromType(type);
|
|
||||||
checkPropTypes(propTypes, element.props, "prop", name, element);
|
|
||||||
} else if (type.PropTypes !== void 0 && !propTypesMisspellWarningShown) {
|
|
||||||
propTypesMisspellWarningShown = true;
|
|
||||||
var _name = getComponentNameFromType(type);
|
|
||||||
error("Component %s declared `PropTypes` instead of `propTypes`. Did you misspell the property assignment?", _name || "Unknown");
|
|
||||||
}
|
|
||||||
if (typeof type.getDefaultProps === "function" && !type.getDefaultProps.isReactClassApproved) {
|
|
||||||
error("getDefaultProps is only used on classic React.createClass definitions. Use a static property named `defaultProps` instead.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function validateFragmentProps(fragment) {
|
|
||||||
{
|
|
||||||
var keys = Object.keys(fragment.props);
|
|
||||||
for (var i = 0; i < keys.length; i++) {
|
|
||||||
var key = keys[i];
|
|
||||||
if (key !== "children" && key !== "key") {
|
|
||||||
setCurrentlyValidatingElement$1(fragment);
|
|
||||||
error("Invalid prop `%s` supplied to `React.Fragment`. React.Fragment can only have `key` and `children` props.", key);
|
|
||||||
setCurrentlyValidatingElement$1(null);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (fragment.ref !== null) {
|
|
||||||
setCurrentlyValidatingElement$1(fragment);
|
|
||||||
error("Invalid attribute `ref` supplied to `React.Fragment`.");
|
|
||||||
setCurrentlyValidatingElement$1(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var didWarnAboutKeySpread = {};
|
|
||||||
function jsxWithValidation(type, props, key, isStaticChildren, source, self) {
|
|
||||||
{
|
|
||||||
var validType = isValidElementType(type);
|
|
||||||
if (!validType) {
|
|
||||||
var info = "";
|
|
||||||
if (type === void 0 || typeof type === "object" && type !== null && Object.keys(type).length === 0) {
|
|
||||||
info += " You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.";
|
|
||||||
}
|
|
||||||
var sourceInfo = getSourceInfoErrorAddendum(source);
|
|
||||||
if (sourceInfo) {
|
|
||||||
info += sourceInfo;
|
|
||||||
} else {
|
|
||||||
info += getDeclarationErrorAddendum();
|
|
||||||
}
|
|
||||||
var typeString;
|
|
||||||
if (type === null) {
|
|
||||||
typeString = "null";
|
|
||||||
} else if (isArray(type)) {
|
|
||||||
typeString = "array";
|
|
||||||
} else if (type !== void 0 && type.$$typeof === REACT_ELEMENT_TYPE) {
|
|
||||||
typeString = "<" + (getComponentNameFromType(type.type) || "Unknown") + " />";
|
|
||||||
info = " Did you accidentally export a JSX literal instead of a component?";
|
|
||||||
} else {
|
|
||||||
typeString = typeof type;
|
|
||||||
}
|
|
||||||
error("React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: %s.%s", typeString, info);
|
|
||||||
}
|
|
||||||
var element = jsxDEV(type, props, key, source, self);
|
|
||||||
if (element == null) {
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
if (validType) {
|
|
||||||
var children = props.children;
|
|
||||||
if (children !== void 0) {
|
|
||||||
if (isStaticChildren) {
|
|
||||||
if (isArray(children)) {
|
|
||||||
for (var i = 0; i < children.length; i++) {
|
|
||||||
validateChildKeys(children[i], type);
|
|
||||||
}
|
|
||||||
if (Object.freeze) {
|
|
||||||
Object.freeze(children);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
error("React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead.");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
validateChildKeys(children, type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{
|
|
||||||
if (hasOwnProperty.call(props, "key")) {
|
|
||||||
var componentName = getComponentNameFromType(type);
|
|
||||||
var keys = Object.keys(props).filter(function(k) {
|
|
||||||
return k !== "key";
|
|
||||||
});
|
|
||||||
var beforeExample = keys.length > 0 ? "{key: someKey, " + keys.join(": ..., ") + ": ...}" : "{key: someKey}";
|
|
||||||
if (!didWarnAboutKeySpread[componentName + beforeExample]) {
|
|
||||||
var afterExample = keys.length > 0 ? "{" + keys.join(": ..., ") + ": ...}" : "{}";
|
|
||||||
error('A props object containing a "key" prop is being spread into JSX:\n let props = %s;\n <%s {...props} />\nReact keys must be passed directly to JSX without using spread:\n let props = %s;\n <%s key={someKey} {...props} />', beforeExample, componentName, afterExample, componentName);
|
|
||||||
didWarnAboutKeySpread[componentName + beforeExample] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (type === REACT_FRAGMENT_TYPE) {
|
|
||||||
validateFragmentProps(element);
|
|
||||||
} else {
|
|
||||||
validatePropTypes(element);
|
|
||||||
}
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function jsxWithValidationStatic(type, props, key) {
|
|
||||||
{
|
|
||||||
return jsxWithValidation(type, props, key, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function jsxWithValidationDynamic(type, props, key) {
|
|
||||||
{
|
|
||||||
return jsxWithValidation(type, props, key, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var jsx = jsxWithValidationDynamic;
|
|
||||||
var jsxs = jsxWithValidationStatic;
|
|
||||||
exports.Fragment = REACT_FRAGMENT_TYPE;
|
|
||||||
exports.jsx = jsx;
|
|
||||||
exports.jsxs = jsxs;
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// node_modules/react/jsx-runtime.js
|
|
||||||
var require_jsx_runtime = __commonJS({
|
|
||||||
"node_modules/react/jsx-runtime.js"(exports, module) {
|
|
||||||
if (false) {
|
|
||||||
module.exports = null;
|
|
||||||
} else {
|
|
||||||
module.exports = require_react_jsx_runtime_development();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
export default require_jsx_runtime();
|
export default require_jsx_runtime();
|
||||||
/*! Bundled license information:
|
|
||||||
|
|
||||||
react/cjs/react-jsx-runtime.development.js:
|
|
||||||
(**
|
|
||||||
* @license React
|
|
||||||
* react-jsx-runtime.development.js
|
|
||||||
*
|
|
||||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*)
|
|
||||||
*/
|
|
||||||
//# sourceMappingURL=react_jsx-runtime.js.map
|
//# sourceMappingURL=react_jsx-runtime.js.map
|
||||||
+4
-4
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"version": 3,
|
"version": 3,
|
||||||
"sources": ["../../react/cjs/react-jsx-runtime.development.js", "../../react/jsx-runtime.js"],
|
"sources": [],
|
||||||
"sourcesContent": ["/**\n * @license React\n * react-jsx-runtime.development.js\n *\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n'use strict';\n\nif (process.env.NODE_ENV !== \"production\") {\n (function() {\n'use strict';\n\nvar React = require('react');\n\n// ATTENTION\n// When adding new symbols to this file,\n// Please consider also adding to 'react-devtools-shared/src/backend/ReactSymbols'\n// The Symbol used to tag the ReactElement-like types.\nvar REACT_ELEMENT_TYPE = Symbol.for('react.element');\nvar REACT_PORTAL_TYPE = Symbol.for('react.portal');\nvar REACT_FRAGMENT_TYPE = Symbol.for('react.fragment');\nvar REACT_STRICT_MODE_TYPE = Symbol.for('react.strict_mode');\nvar REACT_PROFILER_TYPE = Symbol.for('react.profiler');\nvar REACT_PROVIDER_TYPE = Symbol.for('react.provider');\nvar REACT_CONTEXT_TYPE = Symbol.for('react.context');\nvar REACT_FORWARD_REF_TYPE = Symbol.for('react.forward_ref');\nvar REACT_SUSPENSE_TYPE = Symbol.for('react.suspense');\nvar REACT_SUSPENSE_LIST_TYPE = Symbol.for('react.suspense_list');\nvar REACT_MEMO_TYPE = Symbol.for('react.memo');\nvar REACT_LAZY_TYPE = Symbol.for('react.lazy');\nvar REACT_OFFSCREEN_TYPE = Symbol.for('react.offscreen');\nvar MAYBE_ITERATOR_SYMBOL = Symbol.iterator;\nvar FAUX_ITERATOR_SYMBOL = '@@iterator';\nfunction getIteratorFn(maybeIterable) {\n if (maybeIterable === null || typeof maybeIterable !== 'object') {\n return null;\n }\n\n var maybeIterator = MAYBE_ITERATOR_SYMBOL && maybeIterable[MAYBE_ITERATOR_SYMBOL] || maybeIterable[FAUX_ITERATOR_SYMBOL];\n\n if (typeof maybeIterator === 'function') {\n return maybeIterator;\n }\n\n return null;\n}\n\nvar ReactSharedInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;\n\nfunction error(format) {\n {\n {\n for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {\n args[_key2 - 1] = arguments[_key2];\n }\n\n printWarning('error', format, args);\n }\n }\n}\n\nfunction printWarning(level, format, args) {\n // When changing this logic, you might want to also\n // update consoleWithStackDev.www.js as well.\n {\n var ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;\n var stack = ReactDebugCurrentFrame.getStackAddendum();\n\n if (stack !== '') {\n format += '%s';\n args = args.concat([stack]);\n } // eslint-disable-next-line react-internal/safe-string-coercion\n\n\n var argsWithFormat = args.map(function (item) {\n return String(item);\n }); // Careful: RN currently depends on this prefix\n\n argsWithFormat.unshift('Warning: ' + format); // We intentionally don't use spread (or .apply) directly because it\n // breaks IE9: https://github.com/facebook/react/issues/13610\n // eslint-disable-next-line react-internal/no-production-logging\n\n Function.prototype.apply.call(console[level], console, argsWithFormat);\n }\n}\n\n// -----------------------------------------------------------------------------\n\nvar enableScopeAPI = false; // Experimental Create Event Handle API.\nvar enableCacheElement = false;\nvar enableTransitionTracing = false; // No known bugs, but needs performance testing\n\nvar enableLegacyHidden = false; // Enables unstable_avoidThisFallback feature in Fiber\n// stuff. Intended to enable React core members to more easily debug scheduling\n// issues in DEV builds.\n\nvar enableDebugTracing = false; // Track which Fiber(s) schedule render work.\n\nvar REACT_MODULE_REFERENCE;\n\n{\n REACT_MODULE_REFERENCE = Symbol.for('react.module.reference');\n}\n\nfunction isValidElementType(type) {\n if (typeof type === 'string' || typeof type === 'function') {\n return true;\n } // Note: typeof might be other than 'symbol' or 'number' (e.g. if it's a polyfill).\n\n\n if (type === REACT_FRAGMENT_TYPE || type === REACT_PROFILER_TYPE || enableDebugTracing || type === REACT_STRICT_MODE_TYPE || type === REACT_SUSPENSE_TYPE || type === REACT_SUSPENSE_LIST_TYPE || enableLegacyHidden || type === REACT_OFFSCREEN_TYPE || enableScopeAPI || enableCacheElement || enableTransitionTracing ) {\n return true;\n }\n\n if (typeof type === 'object' && type !== null) {\n if (type.$$typeof === REACT_LAZY_TYPE || type.$$typeof === REACT_MEMO_TYPE || type.$$typeof === REACT_PROVIDER_TYPE || type.$$typeof === REACT_CONTEXT_TYPE || type.$$typeof === REACT_FORWARD_REF_TYPE || // This needs to include all possible module reference object\n // types supported by any Flight configuration anywhere since\n // we don't know which Flight build this will end up being used\n // with.\n type.$$typeof === REACT_MODULE_REFERENCE || type.getModuleId !== undefined) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction getWrappedName(outerType, innerType, wrapperName) {Line truncated
|
"sourcesContent": [],
|
||||||
"mappings": "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 truncated
|
"mappings": "",
|
||||||
"names": ["ReactDebugCurrentFrame"]
|
"names": []
|
||||||
}
|
}
|
||||||
-3
@@ -1,3 +0,0 @@
|
|||||||
# esbuild
|
|
||||||
|
|
||||||
This is the macOS ARM 64-bit binary for esbuild, a JavaScript bundler and minifier. See https://github.com/evanw/esbuild for details.
|
|
||||||
BIN
Binary file not shown.
-20
@@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@esbuild/darwin-arm64",
|
|
||||||
"version": "0.21.5",
|
|
||||||
"description": "The macOS ARM 64-bit binary for esbuild, a JavaScript bundler.",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git+https://github.com/evanw/esbuild.git"
|
|
||||||
},
|
|
||||||
"license": "MIT",
|
|
||||||
"preferUnplugged": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
},
|
|
||||||
"os": [
|
|
||||||
"darwin"
|
|
||||||
],
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
-3
@@ -1,3 +0,0 @@
|
|||||||
# `@rollup/rollup-darwin-arm64`
|
|
||||||
|
|
||||||
This is the **aarch64-apple-darwin** binary for `rollup`
|
|
||||||
-22
@@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@rollup/rollup-darwin-arm64",
|
|
||||||
"version": "4.53.5",
|
|
||||||
"os": [
|
|
||||||
"darwin"
|
|
||||||
],
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"files": [
|
|
||||||
"rollup.darwin-arm64.node"
|
|
||||||
],
|
|
||||||
"description": "Native bindings for Rollup",
|
|
||||||
"author": "Lukas Taegert-Atkinson",
|
|
||||||
"homepage": "https://rollupjs.org/",
|
|
||||||
"license": "MIT",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git+https://github.com/rollup/rollup.git"
|
|
||||||
},
|
|
||||||
"main": "./rollup.darwin-arm64.node"
|
|
||||||
}
|
|
||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
-22
@@ -1,22 +0,0 @@
|
|||||||
MIT License
|
|
||||||
-----------
|
|
||||||
|
|
||||||
Copyright (C) 2010-2020 by Philipp Dunkel, Ben Noordhuis, Elan Shankar, Paul Miller
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
-89
@@ -1,89 +0,0 @@
|
|||||||
# fsevents
|
|
||||||
|
|
||||||
Native access to MacOS FSEvents in [Node.js](https://nodejs.org/)
|
|
||||||
|
|
||||||
The FSEvents API in MacOS allows applications to register for notifications of
|
|
||||||
changes to a given directory tree. It is a very fast and lightweight alternative
|
|
||||||
to kqueue.
|
|
||||||
|
|
||||||
This is a low-level library. For a cross-platform file watching module that
|
|
||||||
uses fsevents, check out [Chokidar](https://github.com/paulmillr/chokidar).
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm install fsevents
|
|
||||||
```
|
|
||||||
|
|
||||||
Supports only **Node.js v8.16 and higher**.
|
|
||||||
|
|
||||||
```js
|
|
||||||
const fsevents = require('fsevents');
|
|
||||||
|
|
||||||
// To start observation
|
|
||||||
const stop = fsevents.watch(__dirname, (path, flags, id) => {
|
|
||||||
const info = fsevents.getInfo(path, flags);
|
|
||||||
});
|
|
||||||
|
|
||||||
// To end observation
|
|
||||||
stop();
|
|
||||||
```
|
|
||||||
|
|
||||||
> **Important note:** The API behaviour is slightly different from typical JS APIs. The `stop` function **must** be
|
|
||||||
> retrieved and stored somewhere, even if you don't plan to stop the watcher. If you forget it, the garbage collector
|
|
||||||
> will eventually kick in, the watcher will be unregistered, and your callbacks won't be called anymore.
|
|
||||||
|
|
||||||
The callback passed as the second parameter to `.watch` get's called whenever the operating system detects a
|
|
||||||
a change in the file system. It takes three arguments:
|
|
||||||
|
|
||||||
###### `fsevents.watch(dirname: string, (path: string, flags: number, id: string) => void): () => Promise<undefined>`
|
|
||||||
|
|
||||||
* `path: string` - the item in the filesystem that have been changed
|
|
||||||
* `flags: number` - a numeric value describing what the change was
|
|
||||||
* `id: string` - an unique-id identifying this specific event
|
|
||||||
|
|
||||||
Returns closer callback which when called returns a Promise resolving when the watcher process has been shut down.
|
|
||||||
|
|
||||||
###### `fsevents.getInfo(path: string, flags: number, id: string): FsEventInfo`
|
|
||||||
|
|
||||||
The `getInfo` function takes the `path`, `flags` and `id` arguments and converts those parameters into a structure
|
|
||||||
that is easier to digest to determine what the change was.
|
|
||||||
|
|
||||||
The `FsEventsInfo` has the following shape:
|
|
||||||
|
|
||||||
```js
|
|
||||||
/**
|
|
||||||
* @typedef {'created'|'modified'|'deleted'|'moved'|'root-changed'|'cloned'|'unknown'} FsEventsEvent
|
|
||||||
* @typedef {'file'|'directory'|'symlink'} FsEventsType
|
|
||||||
*/
|
|
||||||
{
|
|
||||||
"event": "created", // {FsEventsEvent}
|
|
||||||
"path": "file.txt",
|
|
||||||
"type": "file", // {FsEventsType}
|
|
||||||
"changes": {
|
|
||||||
"inode": true, // Had iNode Meta-Information changed
|
|
||||||
"finder": false, // Had Finder Meta-Data changed
|
|
||||||
"access": false, // Had access permissions changed
|
|
||||||
"xattrs": false // Had xAttributes changed
|
|
||||||
},
|
|
||||||
"flags": 0x100000000
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Changelog
|
|
||||||
|
|
||||||
- v2.3 supports Apple Silicon ARM CPUs
|
|
||||||
- v2 supports node 8.16+ and reduces package size massively
|
|
||||||
- v1.2.8 supports node 6+
|
|
||||||
- v1.2.7 supports node 4+
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
- I'm getting `EBADPLATFORM` `Unsupported platform for fsevents` error.
|
|
||||||
- It's fine, nothing is broken. fsevents is macos-only. Other platforms are skipped. If you want to hide this warning, report a bug to NPM bugtracker asking them to hide ebadplatform warnings by default.
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
The MIT License Copyright (C) 2010-2020 by Philipp Dunkel, Ben Noordhuis, Elan Shankar, Paul Miller — see LICENSE file.
|
|
||||||
|
|
||||||
Visit our [GitHub page](https://github.com/fsevents/fsevents) and [NPM Page](https://npmjs.org/package/fsevents)
|
|
||||||
-46
@@ -1,46 +0,0 @@
|
|||||||
declare type Event = "created" | "cloned" | "modified" | "deleted" | "moved" | "root-changed" | "unknown";
|
|
||||||
declare type Type = "file" | "directory" | "symlink";
|
|
||||||
declare type FileChanges = {
|
|
||||||
inode: boolean;
|
|
||||||
finder: boolean;
|
|
||||||
access: boolean;
|
|
||||||
xattrs: boolean;
|
|
||||||
};
|
|
||||||
declare type Info = {
|
|
||||||
event: Event;
|
|
||||||
path: string;
|
|
||||||
type: Type;
|
|
||||||
changes: FileChanges;
|
|
||||||
flags: number;
|
|
||||||
};
|
|
||||||
declare type WatchHandler = (path: string, flags: number, id: string) => void;
|
|
||||||
export declare function watch(path: string, handler: WatchHandler): () => Promise<void>;
|
|
||||||
export declare function watch(path: string, since: number, handler: WatchHandler): () => Promise<void>;
|
|
||||||
export declare function getInfo(path: string, flags: number): Info;
|
|
||||||
export declare const constants: {
|
|
||||||
None: 0x00000000;
|
|
||||||
MustScanSubDirs: 0x00000001;
|
|
||||||
UserDropped: 0x00000002;
|
|
||||||
KernelDropped: 0x00000004;
|
|
||||||
EventIdsWrapped: 0x00000008;
|
|
||||||
HistoryDone: 0x00000010;
|
|
||||||
RootChanged: 0x00000020;
|
|
||||||
Mount: 0x00000040;
|
|
||||||
Unmount: 0x00000080;
|
|
||||||
ItemCreated: 0x00000100;
|
|
||||||
ItemRemoved: 0x00000200;
|
|
||||||
ItemInodeMetaMod: 0x00000400;
|
|
||||||
ItemRenamed: 0x00000800;
|
|
||||||
ItemModified: 0x00001000;
|
|
||||||
ItemFinderInfoMod: 0x00002000;
|
|
||||||
ItemChangeOwner: 0x00004000;
|
|
||||||
ItemXattrMod: 0x00008000;
|
|
||||||
ItemIsFile: 0x00010000;
|
|
||||||
ItemIsDir: 0x00020000;
|
|
||||||
ItemIsSymlink: 0x00040000;
|
|
||||||
ItemIsHardlink: 0x00100000;
|
|
||||||
ItemIsLastHardlink: 0x00200000;
|
|
||||||
OwnEvent: 0x00080000;
|
|
||||||
ItemCloned: 0x00400000;
|
|
||||||
};
|
|
||||||
export {};
|
|
||||||
-83
@@ -1,83 +0,0 @@
|
|||||||
/*
|
|
||||||
** © 2020 by Philipp Dunkel, Ben Noordhuis, Elan Shankar, Paul Miller
|
|
||||||
** Licensed under MIT License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* jshint node:true */
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
if (process.platform !== "darwin") {
|
|
||||||
throw new Error(`Module 'fsevents' is not compatible with platform '${process.platform}'`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const Native = require("./fsevents.node");
|
|
||||||
const events = Native.constants;
|
|
||||||
|
|
||||||
function watch(path, since, handler) {
|
|
||||||
if (typeof path !== "string") {
|
|
||||||
throw new TypeError(`fsevents argument 1 must be a string and not a ${typeof path}`);
|
|
||||||
}
|
|
||||||
if ("function" === typeof since && "undefined" === typeof handler) {
|
|
||||||
handler = since;
|
|
||||||
since = Native.flags.SinceNow;
|
|
||||||
}
|
|
||||||
if (typeof since !== "number") {
|
|
||||||
throw new TypeError(`fsevents argument 2 must be a number and not a ${typeof since}`);
|
|
||||||
}
|
|
||||||
if (typeof handler !== "function") {
|
|
||||||
throw new TypeError(`fsevents argument 3 must be a function and not a ${typeof handler}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
let instance = Native.start(Native.global, path, since, handler);
|
|
||||||
if (!instance) throw new Error(`could not watch: ${path}`);
|
|
||||||
return () => {
|
|
||||||
const result = instance ? Promise.resolve(instance).then(Native.stop) : Promise.resolve(undefined);
|
|
||||||
instance = undefined;
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getInfo(path, flags) {
|
|
||||||
return {
|
|
||||||
path,
|
|
||||||
flags,
|
|
||||||
event: getEventType(flags),
|
|
||||||
type: getFileType(flags),
|
|
||||||
changes: getFileChanges(flags),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFileType(flags) {
|
|
||||||
if (events.ItemIsFile & flags) return "file";
|
|
||||||
if (events.ItemIsDir & flags) return "directory";
|
|
||||||
if (events.MustScanSubDirs & flags) return "directory";
|
|
||||||
if (events.ItemIsSymlink & flags) return "symlink";
|
|
||||||
}
|
|
||||||
function anyIsTrue(obj) {
|
|
||||||
for (let key in obj) {
|
|
||||||
if (obj[key]) return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
function getEventType(flags) {
|
|
||||||
if (events.ItemRemoved & flags) return "deleted";
|
|
||||||
if (events.ItemRenamed & flags) return "moved";
|
|
||||||
if (events.ItemCreated & flags) return "created";
|
|
||||||
if (events.ItemModified & flags) return "modified";
|
|
||||||
if (events.RootChanged & flags) return "root-changed";
|
|
||||||
if (events.ItemCloned & flags) return "cloned";
|
|
||||||
if (anyIsTrue(flags)) return "modified";
|
|
||||||
return "unknown";
|
|
||||||
}
|
|
||||||
function getFileChanges(flags) {
|
|
||||||
return {
|
|
||||||
inode: !!(events.ItemInodeMetaMod & flags),
|
|
||||||
finder: !!(events.ItemFinderInfoMod & flags),
|
|
||||||
access: !!(events.ItemChangeOwner & flags),
|
|
||||||
xattrs: !!(events.ItemXattrMod & flags),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.watch = watch;
|
|
||||||
exports.getInfo = getInfo;
|
|
||||||
exports.constants = events;
|
|
||||||
BIN
Binary file not shown.
-62
@@ -1,62 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "fsevents",
|
|
||||||
"version": "2.3.3",
|
|
||||||
"description": "Native Access to MacOS FSEvents",
|
|
||||||
"main": "fsevents.js",
|
|
||||||
"types": "fsevents.d.ts",
|
|
||||||
"os": [
|
|
||||||
"darwin"
|
|
||||||
],
|
|
||||||
"files": [
|
|
||||||
"fsevents.d.ts",
|
|
||||||
"fsevents.js",
|
|
||||||
"fsevents.node"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"clean": "node-gyp clean && rm -f fsevents.node",
|
|
||||||
"build": "node-gyp clean && rm -f fsevents.node && node-gyp rebuild && node-gyp clean",
|
|
||||||
"test": "/bin/bash ./test.sh 2>/dev/null",
|
|
||||||
"prepublishOnly": "npm run build"
|
|
||||||
},
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/fsevents/fsevents.git"
|
|
||||||
},
|
|
||||||
"keywords": [
|
|
||||||
"fsevents",
|
|
||||||
"mac"
|
|
||||||
],
|
|
||||||
"contributors": [
|
|
||||||
{
|
|
||||||
"name": "Philipp Dunkel",
|
|
||||||
"email": "pip@pipobscure.com"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Ben Noordhuis",
|
|
||||||
"email": "info@bnoordhuis.nl"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Elan Shankar",
|
|
||||||
"email": "elan.shanker@gmail.com"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Miroslav Bajtoš",
|
|
||||||
"email": "mbajtoss@gmail.com"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Paul Miller",
|
|
||||||
"url": "https://paulmillr.com"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/fsevents/fsevents/issues"
|
|
||||||
},
|
|
||||||
"homepage": "https://github.com/fsevents/fsevents",
|
|
||||||
"devDependencies": {
|
|
||||||
"node-gyp": "^9.4.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Generated
+1206
-18
File diff suppressed because it is too large.
Load diff
+6
-6
@@ -3,18 +3,18 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@tabler/icons-react": "^3.36.0",
|
||||||
|
"appwrite": "^13.0.0",
|
||||||
|
"date-fns": "^2.30.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-router-dom": "^6.20.0",
|
|
||||||
"appwrite": "^13.0.0",
|
|
||||||
"react-icons": "^4.12.0",
|
"react-icons": "^4.12.0",
|
||||||
"date-fns": "^2.30.0"
|
"react-markdown": "^10.1.0",
|
||||||
|
"react-router-dom": "^6.20.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"vite": "^5.0.0",
|
|
||||||
"@vitejs/plugin-react": "^4.2.0",
|
"@vitejs/plugin-react": "^4.2.0",
|
||||||
"@types/react": "^18.2.0",
|
"vite": "^5.0.0"
|
||||||
"@types/react-dom": "^18.2.0"
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
+31
-15
@@ -1,22 +1,25 @@
|
|||||||
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
|
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
|
||||||
import { AuthProvider, useAuth } from './context/AuthContext'
|
import { AuthProvider, useAuth } from './context/AuthContext'
|
||||||
import Navbar from './components/Navbar'
|
import Sidebar from './components/Navbar'
|
||||||
import Footer from './components/Footer'
|
import Footer from './components/Footer'
|
||||||
import LoginPage from './pages/LoginPage'
|
import LoginPage from './pages/LoginPage'
|
||||||
import TicketsPage from './pages/TicketsPage'
|
import TicketsPage from './pages/TicketsPage'
|
||||||
import DashboardPage from './pages/DashboardPage'
|
|
||||||
import AssetsPage from './pages/AssetsPage'
|
|
||||||
import PlanboardPage from './pages/PlanboardPage'
|
import PlanboardPage from './pages/PlanboardPage'
|
||||||
import ProjectsPage from './pages/ProjectsPage'
|
import ProjectsPage from './pages/ProjectsPage'
|
||||||
import ReportsPage from './pages/ReportsPage'
|
import RoadmapsPage from './pages/RoadmapsPage'
|
||||||
import DocsPage from './pages/DocsPage'
|
import RoadmapDetailPage from './pages/RoadmapDetailPage'
|
||||||
|
import AdminPage from './pages/AdminPage'
|
||||||
|
import CustomersPage from './pages/CustomersPage'
|
||||||
|
import LeadsPage from './pages/LeadsPage'
|
||||||
|
import CustomerDetailPage from './pages/CustomerDetailPage'
|
||||||
|
import FinancePage from './pages/FinancePage'
|
||||||
|
|
||||||
function ProtectedRoute({ children }) {
|
function ProtectedRoute({ children }) {
|
||||||
const { user, loading } = useAuth()
|
const { user, loading } = useAuth()
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="text-center p-4">
|
<div className="text-center p-4" style={{ marginTop: 80 }}>
|
||||||
<div className="spinner"></div>
|
<div className="spinner"></div>
|
||||||
<p>Loading...</p>
|
<p>Loading...</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -38,12 +41,17 @@ function AppRoutes() {
|
|||||||
<Route path="/login" element={user ? <Navigate to="/tickets" replace /> : <LoginPage />} />
|
<Route path="/login" element={user ? <Navigate to="/tickets" replace /> : <LoginPage />} />
|
||||||
<Route path="/" element={<Navigate to="/tickets" replace />} />
|
<Route path="/" element={<Navigate to="/tickets" replace />} />
|
||||||
<Route path="/tickets" element={<ProtectedRoute><TicketsPage /></ProtectedRoute>} />
|
<Route path="/tickets" element={<ProtectedRoute><TicketsPage /></ProtectedRoute>} />
|
||||||
<Route path="/dashboard" element={<ProtectedRoute><DashboardPage /></ProtectedRoute>} />
|
<Route path="/customers" element={<ProtectedRoute><CustomersPage /></ProtectedRoute>} />
|
||||||
<Route path="/assets" element={<ProtectedRoute><AssetsPage /></ProtectedRoute>} />
|
<Route path="/customers/:id" element={<ProtectedRoute><CustomerDetailPage /></ProtectedRoute>} />
|
||||||
|
<Route path="/leads" element={<ProtectedRoute><LeadsPage /></ProtectedRoute>} />
|
||||||
|
<Route path="/finance" element={<ProtectedRoute><FinancePage /></ProtectedRoute>} />
|
||||||
<Route path="/planboard" element={<ProtectedRoute><PlanboardPage /></ProtectedRoute>} />
|
<Route path="/planboard" element={<ProtectedRoute><PlanboardPage /></ProtectedRoute>} />
|
||||||
<Route path="/projects" element={<ProtectedRoute><ProjectsPage /></ProtectedRoute>} />
|
<Route path="/projects" element={<ProtectedRoute><ProjectsPage /></ProtectedRoute>} />
|
||||||
<Route path="/reports" element={<ProtectedRoute><ReportsPage /></ProtectedRoute>} />
|
<Route path="/roadmaps" element={<ProtectedRoute><RoadmapsPage /></ProtectedRoute>} />
|
||||||
<Route path="/docs" element={<ProtectedRoute><DocsPage /></ProtectedRoute>} />
|
<Route path="/roadmaps/:id" element={<ProtectedRoute><RoadmapDetailPage /></ProtectedRoute>} />
|
||||||
|
<Route path="/admin" element={<ProtectedRoute><AdminPage /></ProtectedRoute>} />
|
||||||
|
{/* Alte Links (z. B. die entfernten Seiten Assets, Reports, Docs) landen bei den Tickets */}
|
||||||
|
<Route path="*" element={<Navigate to="/tickets" replace />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -51,12 +59,20 @@ function AppRoutes() {
|
|||||||
function AppContent() {
|
function AppContent() {
|
||||||
const { user } = useAuth()
|
const { user } = useAuth()
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return <AppRoutes />
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="app-shell">
|
||||||
{user && <Navbar />}
|
<Sidebar />
|
||||||
<AppRoutes />
|
<div className="app-main">
|
||||||
{user && <Footer />}
|
<div className="app-scroll">
|
||||||
</>
|
<AppRoutes />
|
||||||
|
</div>
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
Lato Regular 1.105 (lato-regular.ttf)
|
||||||
|
Copyright (c) 2010-2013 by tyPoland Lukasz Dziedzic with Reserved Font Name "Lato".
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
https://openfontlicense.org
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
Binary file not shown.
@@ -0,0 +1,97 @@
|
|||||||
|
import { FaTrash, FaCodeBranch } from 'react-icons/fa'
|
||||||
|
import { FaGlobe, FaCodeCommit, FaClock } from 'react-icons/fa6'
|
||||||
|
import ProjectReadme from './ProjectReadme'
|
||||||
|
import GiteaLinkButton from './GiteaLinkButton'
|
||||||
|
import PreviewLinkButton from './PreviewLinkButton'
|
||||||
|
|
||||||
|
const STATUS_COLORS = {
|
||||||
|
ready: '#10b981',
|
||||||
|
live: '#10b981',
|
||||||
|
online: '#10b981',
|
||||||
|
provisioning: '#f59e0b',
|
||||||
|
pending: '#f59e0b',
|
||||||
|
building: '#3b82f6',
|
||||||
|
error: '#ef4444',
|
||||||
|
failed: '#ef4444',
|
||||||
|
}
|
||||||
|
|
||||||
|
function StatusBadge({ label, value }) {
|
||||||
|
if (!value) return null
|
||||||
|
const color = STATUS_COLORS[String(value).toLowerCase()] || '#a0aec0'
|
||||||
|
return (
|
||||||
|
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 6, fontSize: 12, color: '#cbd5e0', marginRight: 12 }}>
|
||||||
|
{label}:
|
||||||
|
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 4, color }}>
|
||||||
|
<span style={{ width: 8, height: 8, borderRadius: '50%', background: color, display: 'inline-block' }} />
|
||||||
|
{value}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AssignedProjectCard({ project, onUnassign, showActions = false }) {
|
||||||
|
const lastPush = project.lastPushAt || project.giteaSyncedAt
|
||||||
|
return (
|
||||||
|
<div style={{ background: 'rgba(30,41,59,0.5)', border: '1px solid rgba(59,130,246,0.25)', borderRadius: 10, padding: '12px 14px', marginBottom: 12 }}>
|
||||||
|
<div style={{ display: 'flex', gap: 8, alignItems: 'flex-start', justifyContent: 'space-between' }}>
|
||||||
|
<div style={{ minWidth: 0 }}>
|
||||||
|
<strong>{project.projectName}</strong>
|
||||||
|
{project.subdomain && <span className="text-grey" style={{ marginLeft: 8, fontSize: 13 }}>({project.subdomain})</span>}
|
||||||
|
{project.repoFullName && (
|
||||||
|
<div className="text-grey" style={{ fontSize: 12, marginTop: 4 }}>
|
||||||
|
<FaCodeBranch style={{ marginRight: 4 }} />{project.repoFullName}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{project.liveDomain && (
|
||||||
|
<div style={{ fontSize: 12, marginTop: 4 }}>
|
||||||
|
<FaGlobe style={{ marginRight: 4, color: '#34d399' }} />
|
||||||
|
<a href={`https://${project.liveDomain}`} target="_blank" rel="noopener noreferrer" style={{ color: '#34d399', textDecoration: 'none' }}>
|
||||||
|
{project.liveDomain}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', gap: 8, alignItems: 'center', flexShrink: 0 }}>
|
||||||
|
<PreviewLinkButton href={project.previewUrl} />
|
||||||
|
<GiteaLinkButton href={project.giteaRepoUrl} />
|
||||||
|
{showActions && onUnassign && (
|
||||||
|
<button type="button" className="btn btn-sm" onClick={() => onUnassign(project.$id)}><FaTrash /></button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Status- und Provisioning-Zeile */}
|
||||||
|
<div style={{ marginTop: 10, display: 'flex', flexWrap: 'wrap', alignItems: 'center' }}>
|
||||||
|
<StatusBadge label="Status" value={project.status} />
|
||||||
|
<StatusBadge label="Provisioning" value={project.provisioningStatus} />
|
||||||
|
{project.hasPreview && <StatusBadge label="Preview" value="aktiv" />}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Letzter Commit */}
|
||||||
|
{(project.lastCommitMessage || project.lastCommitSha) && (
|
||||||
|
<div style={{ marginTop: 8, fontSize: 12, color: '#94a3b8', background: 'rgba(0,0,0,0.2)', borderRadius: 8, padding: '8px 10px' }}>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||||
|
<FaCodeCommit style={{ color: '#3b82f6' }} />
|
||||||
|
{project.lastCommitSha && <code style={{ color: '#cbd5e0' }}>{String(project.lastCommitSha).slice(0, 7)}</code>}
|
||||||
|
{project.lastCommitBranch || project.lastPushBranch ? (
|
||||||
|
<span className="text-grey">({project.lastPushBranch || project.lastCommitBranch})</span>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
{project.lastCommitMessage && (
|
||||||
|
<div style={{ marginTop: 4, color: '#cbd5e0', whiteSpace: 'pre-wrap' }}>{project.lastCommitMessage}</div>
|
||||||
|
)}
|
||||||
|
<div style={{ marginTop: 4, display: 'flex', gap: 12, flexWrap: 'wrap' }}>
|
||||||
|
{project.lastCommitAuthor && <span>von {project.lastCommitAuthor}</span>}
|
||||||
|
{lastPush && (
|
||||||
|
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 4 }}>
|
||||||
|
<FaClock /> {new Date(lastPush).toLocaleString('de-DE')}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{project.repoFullName && <ProjectReadme repoFullName={project.repoFullName} />}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import { useEffect, useRef, useState } from 'react'
|
||||||
|
import { FaCheck, FaCopy, FaEye, FaEyeSlash } from 'react-icons/fa6'
|
||||||
|
|
||||||
|
async function copyText(text) {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(text)
|
||||||
|
} catch {
|
||||||
|
const textarea = document.createElement('textarea')
|
||||||
|
textarea.value = text
|
||||||
|
textarea.setAttribute('readonly', '')
|
||||||
|
textarea.style.position = 'fixed'
|
||||||
|
textarea.style.left = '-9999px'
|
||||||
|
document.body.appendChild(textarea)
|
||||||
|
textarea.select()
|
||||||
|
document.execCommand('copy')
|
||||||
|
document.body.removeChild(textarea)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function CopyableCredential({ value, secret = false, label = 'Wert' }) {
|
||||||
|
const [visible, setVisible] = useState(!secret)
|
||||||
|
const [copied, setCopied] = useState(false)
|
||||||
|
const timeoutRef = useRef(null)
|
||||||
|
const text = String(value || '').trim()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setVisible(!secret)
|
||||||
|
}, [secret, text])
|
||||||
|
|
||||||
|
useEffect(() => () => clearTimeout(timeoutRef.current), [])
|
||||||
|
|
||||||
|
if (!text) return <span className="credential-empty">–</span>
|
||||||
|
|
||||||
|
const handleCopy = async (event) => {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
await copyText(text)
|
||||||
|
setCopied(true)
|
||||||
|
clearTimeout(timeoutRef.current)
|
||||||
|
timeoutRef.current = setTimeout(() => setCopied(false), 1800)
|
||||||
|
}
|
||||||
|
|
||||||
|
const displayValue = secret && !visible ? '••••••••••••' : text
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className="credential">
|
||||||
|
<span
|
||||||
|
className={`credential-value ${secret && !visible ? 'is-masked' : ''}`}
|
||||||
|
title={secret && !visible ? `${label} anzeigen` : text}
|
||||||
|
>
|
||||||
|
{displayValue}
|
||||||
|
</span>
|
||||||
|
{secret && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="credential-action"
|
||||||
|
onClick={(event) => {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
setVisible((current) => !current)
|
||||||
|
}}
|
||||||
|
title={visible ? `${label} ausblenden` : `${label} anzeigen`}
|
||||||
|
aria-label={visible ? `${label} ausblenden` : `${label} anzeigen`}
|
||||||
|
>
|
||||||
|
{visible ? <FaEyeSlash /> : <FaEye />}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="credential-action"
|
||||||
|
onClick={handleCopy}
|
||||||
|
title={copied ? 'Kopiert' : `${label} kopieren`}
|
||||||
|
aria-label={copied ? `${label} kopiert` : `${label} kopieren`}
|
||||||
|
>
|
||||||
|
{copied ? <FaCheck className="credential-check" /> : <FaCopy />}
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCustomerPortalPassword(customer) {
|
||||||
|
return customer?.portalPassword || customer?.password || ''
|
||||||
|
}
|
||||||
@@ -1,92 +1,119 @@
|
|||||||
import { useState } from 'react'
|
import { useState, useEffect, useMemo } from 'react'
|
||||||
import { FaTimes } from 'react-icons/fa'
|
import { FaTimes } from 'react-icons/fa'
|
||||||
|
import { useEmployees } from '../hooks/useEmployees'
|
||||||
|
import { databases, DATABASE_ID, COLLECTIONS, Query } from '../lib/appwrite'
|
||||||
|
import {
|
||||||
|
SERVICE_TYPES,
|
||||||
|
todayIso,
|
||||||
|
isoToGermanDate,
|
||||||
|
timeToWoms,
|
||||||
|
buildRequesterSuggestions,
|
||||||
|
rankSuggestions,
|
||||||
|
} from '../lib/ticketForm'
|
||||||
|
import { priorityFor } from '../lib/ticketStatus'
|
||||||
|
import { AutocompleteInput, DateTimeInput } from './ui'
|
||||||
|
|
||||||
|
// Neue Tickets gibt es aktuell nur fuer diese Arten; alte Tickets behalten ihre Art.
|
||||||
|
// Migration = ein Kunde, den wir von einem anderen Anbieter uebernehmen.
|
||||||
const TICKET_TYPES = [
|
const TICKET_TYPES = [
|
||||||
'Home Office', 'Holidays', 'Trip', 'Supportrequest', 'Change Request',
|
{ value: 'Webpage', label: 'Webpage' },
|
||||||
'Maintenance', 'Project', 'Controlling', 'Development', 'Documentation',
|
{ value: 'Migration', label: 'Migration (Übernahme von anderem Anbieter)' },
|
||||||
'Meeting/Conference', 'IT Management', 'IT Security', 'Procurement',
|
|
||||||
'Rollout', 'Emergency Call', 'Other Services'
|
|
||||||
]
|
]
|
||||||
|
|
||||||
const SYSTEMS = [
|
// Datum als "yyyy-mm-dd", Uhrzeit als "hh:mm" (Werte der Kalender-Felder).
|
||||||
'Account View', 'Client', 'Cofano', 'Credentials', 'Diamant', 'Docuware',
|
// Die Service-Art fragt das Formular nicht mehr ab: gespeichert wird Fernwartung.
|
||||||
'EDI', 'eMail', 'Employee', 'Invoice', 'LBase', 'Medical Office', 'Network',
|
const emptyForm = () => ({
|
||||||
'O365', 'PDF Viewer', 'Printer', 'Reports', 'Server', 'Time Tracking',
|
customerId: '',
|
||||||
'TK', 'TOS', 'Vivendi NG', 'VGM', '(W)LAN', '(W)WAN', 'WOMS', 'n/a'
|
type: TICKET_TYPES[0].value,
|
||||||
]
|
serviceType: SERVICE_TYPES[0].value,
|
||||||
|
urgent: false,
|
||||||
const RESPONSE_LEVELS = [
|
topic: '',
|
||||||
'USER', 'KEY USER', 'Helpdesk', 'Support', 'Admin', 'FS/FE', '24/7',
|
requestedBy: '',
|
||||||
'TECH MGMT', 'Backoffice', 'BUSI MGMT', 'n/a'
|
startDate: todayIso(),
|
||||||
]
|
startTime: '',
|
||||||
|
deadline: todayIso(),
|
||||||
const SERVICE_TYPES = ['Remote', 'On Site', 'Off Site']
|
endTime: '',
|
||||||
|
details: ''
|
||||||
const PRIORITIES = [
|
})
|
||||||
{ value: 0, label: 'None' },
|
|
||||||
{ value: 1, label: 'Low' },
|
|
||||||
{ value: 2, label: 'Medium' },
|
|
||||||
{ value: 3, label: 'High' },
|
|
||||||
{ value: 4, label: 'Critical' }
|
|
||||||
]
|
|
||||||
|
|
||||||
const today = new Date().toLocaleDateString('de-DE')
|
|
||||||
|
|
||||||
export default function CreateTicketModal({ isOpen, onClose, onCreate, customers = [] }) {
|
export default function CreateTicketModal({ isOpen, onClose, onCreate, customers = [] }) {
|
||||||
const [formData, setFormData] = useState({
|
const { employees } = useEmployees()
|
||||||
customerId: '',
|
|
||||||
type: 'Supportrequest',
|
const [formData, setFormData] = useState(emptyForm)
|
||||||
systemType: '',
|
const [recentTickets, setRecentTickets] = useState([])
|
||||||
responseLevel: '',
|
|
||||||
serviceType: 'Remote',
|
|
||||||
priority: 1,
|
|
||||||
topic: '',
|
|
||||||
requestedBy: '',
|
|
||||||
requestedFor: '',
|
|
||||||
startDate: today,
|
|
||||||
startTime: '',
|
|
||||||
deadline: today,
|
|
||||||
endTime: '',
|
|
||||||
estimate: '30',
|
|
||||||
mailCopyTo: '',
|
|
||||||
sendNotification: false,
|
|
||||||
details: ''
|
|
||||||
})
|
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
|
const [error, setError] = useState('')
|
||||||
|
|
||||||
|
// Reset error when modal opens
|
||||||
|
useEffect(() => {
|
||||||
|
if (isOpen) setError('')
|
||||||
|
}, [isOpen])
|
||||||
|
|
||||||
|
// Namen aus den letzten Tickets fuer die Autovervollstaendigung
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen) return
|
||||||
|
let cancelled = false
|
||||||
|
databases
|
||||||
|
.listDocuments(DATABASE_ID, COLLECTIONS.WORKORDERS, [Query.orderDesc('$createdAt'), Query.limit(200)])
|
||||||
|
.then((response) => {
|
||||||
|
if (!cancelled) setRecentTickets(response.documents || [])
|
||||||
|
})
|
||||||
|
.catch((err) => console.warn('Vorschläge aus früheren Tickets nicht geladen:', err))
|
||||||
|
return () => {
|
||||||
|
cancelled = true
|
||||||
|
}
|
||||||
|
}, [isOpen])
|
||||||
|
|
||||||
|
const suggestions = useMemo(
|
||||||
|
() => buildRequesterSuggestions({ customers, employees, tickets: recentTickets }),
|
||||||
|
[customers, employees, recentTickets]
|
||||||
|
)
|
||||||
|
const getSuggestions = (text) => rankSuggestions(suggestions, text, { customerId: formData.customerId })
|
||||||
|
|
||||||
const handleChange = (field, value) => {
|
const handleChange = (field, value) => {
|
||||||
setFormData(prev => ({ ...prev, [field]: value }))
|
setFormData(prev => ({ ...prev, [field]: value }))
|
||||||
|
// Clear error when user makes changes
|
||||||
|
if (error) setError('')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Das Ende darf nicht vor dem Start liegen, deshalb rueckt es mit
|
||||||
|
const handleStartDateChange = (value) => {
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
startDate: value,
|
||||||
|
deadline: value && prev.deadline && prev.deadline < value ? value : prev.deadline
|
||||||
|
}))
|
||||||
|
if (error) setError('')
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSubmit = async (e) => {
|
const handleSubmit = async (e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
|
setError('')
|
||||||
|
|
||||||
|
const customer = customers.find(c => c.$id === formData.customerId)
|
||||||
|
const { urgent, ...fields } = formData
|
||||||
try {
|
try {
|
||||||
await onCreate(formData)
|
const result = await onCreate({
|
||||||
onClose()
|
...fields,
|
||||||
setFormData({
|
priority: priorityFor(urgent),
|
||||||
customerId: '',
|
customerName: customer?.name || '',
|
||||||
type: 'Supportrequest',
|
customerLocation: customer?.location || '',
|
||||||
systemType: '',
|
startDate: isoToGermanDate(formData.startDate),
|
||||||
responseLevel: '',
|
startTime: timeToWoms(formData.startTime),
|
||||||
serviceType: 'Remote',
|
deadline: isoToGermanDate(formData.deadline),
|
||||||
priority: 1,
|
endTime: timeToWoms(formData.endTime)
|
||||||
topic: '',
|
|
||||||
requestedBy: '',
|
|
||||||
requestedFor: '',
|
|
||||||
startDate: today,
|
|
||||||
startTime: '',
|
|
||||||
deadline: today,
|
|
||||||
endTime: '',
|
|
||||||
estimate: '30',
|
|
||||||
mailCopyTo: '',
|
|
||||||
sendNotification: false,
|
|
||||||
details: ''
|
|
||||||
})
|
})
|
||||||
|
if (result.success) {
|
||||||
|
onClose()
|
||||||
|
setFormData(emptyForm())
|
||||||
|
} else {
|
||||||
|
setError(result.error || 'Fehler beim Erstellen des Tickets')
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error creating ticket:', error)
|
console.error('Error creating ticket:', error)
|
||||||
|
setError(error.message || 'Ein unerwarteter Fehler ist aufgetreten')
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
@@ -100,104 +127,73 @@ export default function CreateTicketModal({ isOpen, onClose, onCreate, customers
|
|||||||
<FaTimes />
|
<FaTimes />
|
||||||
</span>
|
</span>
|
||||||
<div className="overlay-content">
|
<div className="overlay-content">
|
||||||
<h2 className="mb-2">Create New Ticket</h2>
|
<h2 className="mb-2">Neues Ticket</h2>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<div className="bg-red text-white p-2 mb-2" style={{ borderRadius: '4px' }}>
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col col-6">
|
<div className="col col-6">
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label className="form-label">Customer ID</label>
|
<label className="form-label">Kunde</label>
|
||||||
<select
|
<select
|
||||||
className="form-control"
|
className="form-control"
|
||||||
value={formData.customerId}
|
value={formData.customerId}
|
||||||
onChange={(e) => handleChange('customerId', e.target.value)}
|
onChange={(e) => handleChange('customerId', e.target.value)}
|
||||||
required
|
required
|
||||||
>
|
>
|
||||||
<option value="">Affected Customer</option>
|
<option value="">Kunde wählen</option>
|
||||||
{customers.map(c => (
|
{customers.map(c => (
|
||||||
<option key={c.id} value={c.id}>({c.code}) {c.name}</option>
|
<option key={c.$id} value={c.$id}>({c.code || ''}) {c.name || 'Unnamed'}</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label className="form-label">Work Order Type</label>
|
<label className="form-label">Art</label>
|
||||||
<select
|
<select
|
||||||
className="form-control"
|
className="form-control"
|
||||||
value={formData.type}
|
value={formData.type}
|
||||||
onChange={(e) => handleChange('type', e.target.value)}
|
onChange={(e) => handleChange('type', e.target.value)}
|
||||||
>
|
>
|
||||||
{TICKET_TYPES.map(type => (
|
{TICKET_TYPES.map(type => (
|
||||||
<option key={type} value={type}>{type}</option>
|
<option key={type.value} value={type.value}>{type.label}</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label className="form-label">Affected System</label>
|
<label style={{ display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }}>
|
||||||
<select
|
<input
|
||||||
className="form-control"
|
type="checkbox"
|
||||||
value={formData.systemType}
|
checked={formData.urgent}
|
||||||
onChange={(e) => handleChange('systemType', e.target.value)}
|
onChange={(e) => handleChange('urgent', e.target.checked)}
|
||||||
required
|
/>
|
||||||
>
|
Dringend
|
||||||
<option value="">Affected System</option>
|
</label>
|
||||||
{SYSTEMS.map(sys => (
|
|
||||||
<option key={sys} value={sys}>{sys}</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="form-group">
|
<div className="form-group automatic-assignment-note">
|
||||||
<label className="form-label">Response Level</label>
|
<label className="form-label">Zuweisung</label>
|
||||||
<select
|
<p>
|
||||||
className="form-control"
|
{employees.length > 0
|
||||||
value={formData.responseLevel}
|
? `Automatisch an den Mitarbeiter mit der geringsten Anzahl offener Tickets (${employees.length} verfügbar).`
|
||||||
onChange={(e) => handleChange('responseLevel', e.target.value)}
|
: 'Es ist noch kein Mitarbeiter verfügbar. Das Ticket bleibt zunächst offen.'}
|
||||||
>
|
</p>
|
||||||
<option value="">Response Level</option>
|
|
||||||
{RESPONSE_LEVELS.map(level => (
|
|
||||||
<option key={level} value={level}>{level}</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="form-group">
|
|
||||||
<label className="form-label">Service Type</label>
|
|
||||||
<select
|
|
||||||
className="form-control"
|
|
||||||
value={formData.serviceType}
|
|
||||||
onChange={(e) => handleChange('serviceType', e.target.value)}
|
|
||||||
>
|
|
||||||
{SERVICE_TYPES.map(type => (
|
|
||||||
<option key={type} value={type}>{type}</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="form-group">
|
|
||||||
<label className="form-label">Priority</label>
|
|
||||||
<select
|
|
||||||
className="form-control"
|
|
||||||
value={formData.priority}
|
|
||||||
onChange={(e) => handleChange('priority', parseInt(e.target.value))}
|
|
||||||
required
|
|
||||||
>
|
|
||||||
<option value="">Priority Level</option>
|
|
||||||
{PRIORITIES.map(p => (
|
|
||||||
<option key={p.value} value={p.value}>{p.label}</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col col-6">
|
<div className="col col-6">
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label className="form-label">Topic</label>
|
<label className="form-label">Betreff</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
className="form-control"
|
className="form-control"
|
||||||
placeholder="Topic"
|
placeholder="Worum geht es?"
|
||||||
value={formData.topic}
|
value={formData.topic}
|
||||||
onChange={(e) => handleChange('topic', e.target.value)}
|
onChange={(e) => handleChange('topic', e.target.value)}
|
||||||
required
|
required
|
||||||
@@ -205,113 +201,50 @@ export default function CreateTicketModal({ isOpen, onClose, onCreate, customers
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label className="form-label">Requested by</label>
|
<label className="form-label" htmlFor="ticket-requested-by">Angefragt von</label>
|
||||||
<input
|
<AutocompleteInput
|
||||||
type="text"
|
id="ticket-requested-by"
|
||||||
className="form-control"
|
|
||||||
placeholder="Name"
|
placeholder="Name"
|
||||||
value={formData.requestedBy}
|
value={formData.requestedBy}
|
||||||
onChange={(e) => handleChange('requestedBy', e.target.value)}
|
onChange={(value) => handleChange('requestedBy', value)}
|
||||||
|
getSuggestions={getSuggestions}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label className="form-label">Requested for</label>
|
<label className="form-label" htmlFor="ticket-start-date">Beginn</label>
|
||||||
<input
|
<DateTimeInput
|
||||||
type="text"
|
id="ticket-start-date"
|
||||||
className="form-control"
|
timeLabel="Uhrzeit Beginn"
|
||||||
placeholder="Name"
|
date={formData.startDate}
|
||||||
value={formData.requestedFor}
|
time={formData.startTime}
|
||||||
onChange={(e) => handleChange('requestedFor', e.target.value)}
|
onDateChange={handleStartDateChange}
|
||||||
|
onTimeChange={(value) => handleChange('startTime', value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label className="form-label">Start Date & Time</label>
|
<label className="form-label" htmlFor="ticket-end-date">Deadline</label>
|
||||||
<div style={{ display: 'flex', gap: '8px' }}>
|
<DateTimeInput
|
||||||
<input
|
id="ticket-end-date"
|
||||||
type="text"
|
timeLabel="Uhrzeit Deadline"
|
||||||
className="form-control"
|
min={formData.startDate}
|
||||||
placeholder="dd.mm.yyyy"
|
date={formData.deadline}
|
||||||
value={formData.startDate}
|
time={formData.endTime}
|
||||||
onChange={(e) => handleChange('startDate', e.target.value)}
|
onDateChange={(value) => handleChange('deadline', value)}
|
||||||
style={{ flex: 1 }}
|
onTimeChange={(value) => handleChange('endTime', value)}
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="form-control"
|
|
||||||
placeholder="hhmm"
|
|
||||||
value={formData.startTime}
|
|
||||||
onChange={(e) => handleChange('startTime', e.target.value)}
|
|
||||||
style={{ flex: 1 }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="form-group">
|
|
||||||
<label className="form-label">End Date & Time (Deadline)</label>
|
|
||||||
<div style={{ display: 'flex', gap: '8px' }}>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="form-control"
|
|
||||||
placeholder="dd.mm.yyyy"
|
|
||||||
value={formData.deadline}
|
|
||||||
onChange={(e) => handleChange('deadline', e.target.value)}
|
|
||||||
style={{ flex: 1 }}
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="form-control"
|
|
||||||
placeholder="hhmm"
|
|
||||||
value={formData.endTime}
|
|
||||||
onChange={(e) => handleChange('endTime', e.target.value)}
|
|
||||||
style={{ flex: 1 }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="form-group">
|
|
||||||
<label className="form-label">Workload Estimate</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="form-control"
|
|
||||||
placeholder="xx minutes"
|
|
||||||
value={formData.estimate}
|
|
||||||
onChange={(e) => handleChange('estimate', e.target.value)}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="form-group">
|
|
||||||
<label className="form-label">Notification Recipient(s)</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="form-control"
|
|
||||||
placeholder="name@domain.xx"
|
|
||||||
value={formData.mailCopyTo}
|
|
||||||
onChange={(e) => handleChange('mailCopyTo', e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="form-group">
|
|
||||||
<label style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={formData.sendNotification}
|
|
||||||
onChange={(e) => handleChange('sendNotification', e.target.checked)}
|
|
||||||
/>
|
|
||||||
Send Notification?
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label className="form-label">Details</label>
|
<label className="form-label">Beschreibung</label>
|
||||||
<textarea
|
<textarea
|
||||||
className="form-control"
|
className="form-control"
|
||||||
rows={5}
|
rows={5}
|
||||||
placeholder="Details"
|
placeholder="Was soll gemacht werden?"
|
||||||
value={formData.details}
|
value={formData.details}
|
||||||
onChange={(e) => handleChange('details', e.target.value)}
|
onChange={(e) => handleChange('details', e.target.value)}
|
||||||
/>
|
/>
|
||||||
@@ -323,7 +256,7 @@ export default function CreateTicketModal({ isOpen, onClose, onCreate, customers
|
|||||||
className="btn btn-dark"
|
className="btn btn-dark"
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
{loading ? 'Creating...' : 'CREATE NOW'}
|
{loading ? 'Wird angelegt …' : 'Ticket anlegen'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -0,0 +1,283 @@
|
|||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
import { FaTimes } from 'react-icons/fa'
|
||||||
|
import { useAuth } from '../context/AuthContext'
|
||||||
|
import { SERVICE_TYPES, todayIso, isoToGermanDate, timeToWoms, minutesBetween } from '../lib/ticketForm'
|
||||||
|
import { TICKET_STATUSES, statusLabel } from '../lib/ticketStatus'
|
||||||
|
import { DateTimeInput } from './ui'
|
||||||
|
|
||||||
|
// Akquise-Tickets: Ergebnis des Kundenkontakts (statt technischer Status)
|
||||||
|
const ACQUISITION_STATUS = ['In Kontakt', 'Zugesagt', 'Abgesagt']
|
||||||
|
|
||||||
|
// Die sechs Status; ein alter Status (z. B. "Assigned") bleibt als aktuelle Auswahl sichtbar
|
||||||
|
function statusOptionsFor(current) {
|
||||||
|
if (!current || TICKET_STATUSES.some((s) => s.value === current)) return TICKET_STATUSES
|
||||||
|
return [{ value: current, label: `${statusLabel(current)} (bisher)` }, ...TICKET_STATUSES]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Datum als "yyyy-mm-dd", Uhrzeit als "hh:mm" (Werte der Kalender-Felder).
|
||||||
|
// Die Service-Art kommt vom Ticket; das alte "Off Site" zaehlt als Fernwartung.
|
||||||
|
const initialForm = (workorder, newStatus) => ({
|
||||||
|
serviceType: SERVICE_TYPES.some(t => t.value === workorder?.serviceType)
|
||||||
|
? workorder.serviceType
|
||||||
|
: SERVICE_TYPES[0].value,
|
||||||
|
newStatus,
|
||||||
|
totalTime: 0,
|
||||||
|
startDate: todayIso(),
|
||||||
|
startTime: '',
|
||||||
|
endDate: todayIso(),
|
||||||
|
endTime: '',
|
||||||
|
details: '',
|
||||||
|
isComment: false,
|
||||||
|
countsToPlan: false
|
||||||
|
})
|
||||||
|
|
||||||
|
export default function CreateWorksheetModal({ isOpen, onClose, workorder, onCreate }) {
|
||||||
|
const { user } = useAuth()
|
||||||
|
|
||||||
|
const isAcquisition = workorder?.type === 'Akquise'
|
||||||
|
const statusOptions = isAcquisition
|
||||||
|
? ACQUISITION_STATUS.map((s) => ({ value: s, label: s }))
|
||||||
|
: statusOptionsFor(workorder?.status)
|
||||||
|
const defaultStatus = isAcquisition
|
||||||
|
? (ACQUISITION_STATUS.includes(workorder?.status) ? workorder.status : 'In Kontakt')
|
||||||
|
: (workorder?.status || 'Open')
|
||||||
|
|
||||||
|
const [formData, setFormData] = useState(() => initialForm(workorder, defaultStatus))
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
const [error, setError] = useState('')
|
||||||
|
const [autoCalculate, setAutoCalculate] = useState(true)
|
||||||
|
|
||||||
|
// Reset form wenn Modal geöffnet wird
|
||||||
|
useEffect(() => {
|
||||||
|
if (isOpen && workorder) {
|
||||||
|
setFormData(initialForm(workorder, defaultStatus))
|
||||||
|
setError('')
|
||||||
|
setAutoCalculate(true)
|
||||||
|
}
|
||||||
|
}, [isOpen, workorder, defaultStatus])
|
||||||
|
|
||||||
|
// Automatische Zeitberechnung
|
||||||
|
useEffect(() => {
|
||||||
|
if (!autoCalculate || formData.isComment) return
|
||||||
|
const minutes = minutesBetween(formData.startTime, formData.endTime)
|
||||||
|
if (minutes !== null) {
|
||||||
|
setFormData(prev => ({ ...prev, totalTime: minutes }))
|
||||||
|
}
|
||||||
|
}, [formData.startTime, formData.endTime, formData.isComment, autoCalculate])
|
||||||
|
|
||||||
|
const handleChange = (field, value) => {
|
||||||
|
setFormData(prev => ({ ...prev, [field]: value }))
|
||||||
|
|
||||||
|
// Wenn totalTime manuell geändert wird, deaktiviere Auto-Berechnung
|
||||||
|
if (field === 'totalTime') {
|
||||||
|
setAutoCalculate(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Das Ende darf nicht vor dem Start liegen, deshalb rueckt es mit
|
||||||
|
const handleStartDateChange = (value) => {
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
startDate: value,
|
||||||
|
endDate: value && prev.endDate && prev.endDate < value ? value : prev.endDate
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
setLoading(true)
|
||||||
|
setError('')
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!formData.details.trim()) {
|
||||||
|
setError('Bitte Details eingeben')
|
||||||
|
setLoading(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const worksheetData = {
|
||||||
|
woid: workorder.woid,
|
||||||
|
workorderId: workorder.$id,
|
||||||
|
serviceType: formData.serviceType,
|
||||||
|
oldStatus: workorder.status,
|
||||||
|
newStatus: formData.newStatus,
|
||||||
|
totalTime: formData.isComment ? 0 : parseInt(formData.totalTime) || 0,
|
||||||
|
startDate: isoToGermanDate(formData.startDate),
|
||||||
|
startTime: timeToWoms(formData.startTime),
|
||||||
|
endDate: isoToGermanDate(formData.endDate),
|
||||||
|
endTime: timeToWoms(formData.endTime),
|
||||||
|
details: formData.details,
|
||||||
|
isComment: formData.isComment,
|
||||||
|
countsToPlan: !formData.isComment && formData.countsToPlan,
|
||||||
|
employeeShort: user?.prefs?.shortCode || '' // Aus User-Preferences
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await onCreate(worksheetData, user)
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
onClose()
|
||||||
|
} else {
|
||||||
|
setError(result.error || 'Fehler beim Speichern des Arbeitsblatts')
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error creating worksheet:', err)
|
||||||
|
setError(err.message || 'Ein unerwarteter Fehler ist aufgetreten')
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isOpen || !workorder) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="overlay">
|
||||||
|
<span className="overlay-close" onClick={onClose}>
|
||||||
|
<FaTimes />
|
||||||
|
</span>
|
||||||
|
<div className="overlay-content">
|
||||||
|
<h2 className="mb-2">Neues Arbeitsblatt – Ticket {workorder.woid}</h2>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<div className="bg-red text-white p-2 mb-2" style={{ borderRadius: '4px' }}>
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<div className="row">
|
||||||
|
<div className="col col-6">
|
||||||
|
<div className="form-group">
|
||||||
|
<label className="form-label">Art</label>
|
||||||
|
<select
|
||||||
|
className="form-control"
|
||||||
|
value={formData.serviceType}
|
||||||
|
onChange={(e) => handleChange('serviceType', e.target.value)}
|
||||||
|
required
|
||||||
|
>
|
||||||
|
{SERVICE_TYPES.map(type => (
|
||||||
|
<option key={type.value} value={type.value}>{type.label}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group">
|
||||||
|
<label className="form-label">{isAcquisition ? 'Akquise-Ergebnis' : 'Neuer Status'}</label>
|
||||||
|
<select
|
||||||
|
className="form-control"
|
||||||
|
value={formData.newStatus}
|
||||||
|
onChange={(e) => handleChange('newStatus', e.target.value)}
|
||||||
|
required
|
||||||
|
>
|
||||||
|
{statusOptions.map(status => (
|
||||||
|
<option key={status.value} value={status.value}>{status.label}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
{isAcquisition && (
|
||||||
|
<small style={{ color: '#a0aec0', fontSize: '12px' }}>
|
||||||
|
"Zugesagt" macht den Lead automatisch zum festen Kunden.
|
||||||
|
</small>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group">
|
||||||
|
<label style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={formData.isComment}
|
||||||
|
onChange={(e) => handleChange('isComment', e.target.checked)}
|
||||||
|
/>
|
||||||
|
Nur Kommentar (keine Arbeitszeit)
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group">
|
||||||
|
<label style={{ display: 'flex', alignItems: 'center', gap: '8px' }} title="Zeit wird vom Stundenguthaben des Wartungs-Abos abgezogen und im Kundenportal angezeigt">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={formData.countsToPlan && !formData.isComment}
|
||||||
|
disabled={formData.isComment}
|
||||||
|
onChange={(e) => handleChange('countsToPlan', e.target.checked)}
|
||||||
|
/>
|
||||||
|
Vom Abo-Stundenkonto abziehen
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col col-6">
|
||||||
|
<div className="form-group">
|
||||||
|
<label className="form-label" htmlFor="worksheet-start-date">Beginn</label>
|
||||||
|
<DateTimeInput
|
||||||
|
id="worksheet-start-date"
|
||||||
|
timeLabel="Uhrzeit Beginn"
|
||||||
|
required
|
||||||
|
date={formData.startDate}
|
||||||
|
time={formData.startTime}
|
||||||
|
onDateChange={handleStartDateChange}
|
||||||
|
onTimeChange={(value) => handleChange('startTime', value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group">
|
||||||
|
<label className="form-label" htmlFor="worksheet-end-date">Ende</label>
|
||||||
|
<DateTimeInput
|
||||||
|
id="worksheet-end-date"
|
||||||
|
timeLabel="Uhrzeit Ende"
|
||||||
|
required
|
||||||
|
min={formData.startDate}
|
||||||
|
date={formData.endDate}
|
||||||
|
time={formData.endTime}
|
||||||
|
onDateChange={(value) => handleChange('endDate', value)}
|
||||||
|
onTimeChange={(value) => handleChange('endTime', value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group">
|
||||||
|
<label className="form-label">Arbeitszeit (Minuten)</label>
|
||||||
|
{/* Ganze Minuten: mit step 15 blockierte der Browser berechnete Zeiten wie 70 */}
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
className="form-control"
|
||||||
|
min="0"
|
||||||
|
step="1"
|
||||||
|
value={formData.totalTime}
|
||||||
|
onChange={(e) => handleChange('totalTime', e.target.value)}
|
||||||
|
disabled={formData.isComment}
|
||||||
|
placeholder="0"
|
||||||
|
/>
|
||||||
|
<small style={{ color: '#a0aec0', fontSize: '12px' }}>
|
||||||
|
{autoCalculate && formData.startTime && formData.endTime
|
||||||
|
? '✓ Automatisch berechnet'
|
||||||
|
: 'Manuell eingeben'}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group">
|
||||||
|
<label className="form-label">Was wurde gemacht?</label>
|
||||||
|
<textarea
|
||||||
|
className="form-control"
|
||||||
|
rows={5}
|
||||||
|
placeholder="Beschreibe die durchgeführten Arbeiten..."
|
||||||
|
value={formData.details}
|
||||||
|
onChange={(e) => handleChange('details', e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-center mt-2">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="btn btn-dark"
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
{loading ? 'Speichert …' : 'Speichern'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,44 +1,34 @@
|
|||||||
import { FaUserPlus } from 'react-icons/fa6'
|
import { FaUserPlus } from 'react-icons/fa6'
|
||||||
|
import { useEmployees } from '../hooks/useEmployees'
|
||||||
// Diese Liste sollte aus der Datenbank kommen
|
|
||||||
const EDITORS = [
|
|
||||||
{ id: 'CHLE', name: 'Christian Lehmann' },
|
|
||||||
{ id: 'DIBR', name: 'Dietmar Bruckauf' },
|
|
||||||
{ id: 'DOAR', name: 'Dominik Armata' },
|
|
||||||
{ id: 'GRVO', name: 'Gregor Vowinkel' },
|
|
||||||
{ id: 'HADW', name: 'Hasan Dwiko' },
|
|
||||||
{ id: 'JEDI', name: 'Jessica Diaz' },
|
|
||||||
{ id: 'KNSO', name: 'Kenso Grimm' },
|
|
||||||
{ id: 'LUPL', name: 'Lukas Placzek' },
|
|
||||||
{ id: 'NIKI', name: 'Nikita Gaidach' },
|
|
||||||
{ id: 'MARK', name: 'Marco Kobza' },
|
|
||||||
{ id: 'MABA', name: 'Markus Bauer' },
|
|
||||||
{ id: 'MATS', name: 'Maksim Tschetschjotkin' },
|
|
||||||
{ id: 'PASI', name: 'Pascal Siegfried' },
|
|
||||||
{ id: 'NICT', name: 'Nico Stegmann' },
|
|
||||||
{ id: 'MEQU', name: 'Melissa Quednau' },
|
|
||||||
{ id: 'SASC', name: 'Saskia Schmahl' },
|
|
||||||
{ id: 'CHPA', name: 'Christin Paulus' },
|
|
||||||
{ id: 'SOSC', name: 'Sonja Schulze' },
|
|
||||||
{ id: 'WAWA', name: 'Walter Wawer' },
|
|
||||||
{ id: 'YAFO', name: 'Yannick Föller' },
|
|
||||||
{ id: 'TODE', name: 'Tobias Decker' }
|
|
||||||
]
|
|
||||||
|
|
||||||
export default function EditorDropdown({ value, onChange }) {
|
export default function EditorDropdown({ value, onChange }) {
|
||||||
|
const { employees } = useEmployees()
|
||||||
|
|
||||||
|
// Finde den zugewiesenen Mitarbeiter anhand der userId
|
||||||
|
const assignedEmployee = employees.find(emp => emp.userId === value)
|
||||||
|
|
||||||
|
// Zeige Kürzel wenn zugewiesen, sonst Icon
|
||||||
|
const displayValue = assignedEmployee?.shortcode || <FaUserPlus size={20} />
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="dropdown">
|
<div className="dropdown">
|
||||||
<button className="btn" style={{ background: 'inherit', color: 'inherit' }}>
|
<button className="btn" style={{ background: 'inherit', color: 'inherit' }}>
|
||||||
{value || <FaUserPlus size={20} />}
|
{displayValue}
|
||||||
</button>
|
</button>
|
||||||
<div className="dropdown-content">
|
<div className="dropdown-content">
|
||||||
{EDITORS.map(editor => (
|
<span
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={() => onChange('')}
|
||||||
|
>
|
||||||
|
<em>(Unassigned)</em>
|
||||||
|
</span>
|
||||||
|
{employees.map(employee => (
|
||||||
<span
|
<span
|
||||||
key={editor.id}
|
key={employee.$id}
|
||||||
className="dropdown-item"
|
className="dropdown-item"
|
||||||
onClick={() => onChange(editor.id)}
|
onClick={() => onChange(employee.userId)}
|
||||||
>
|
>
|
||||||
{editor.name}
|
{employee.displayName}{employee.shortcode ? ` (${employee.shortcode})` : ''}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,96 +0,0 @@
|
|||||||
import { useState, useRef } from 'react'
|
|
||||||
import { FaTimes, FaSpinner } from 'react-icons/fa'
|
|
||||||
import { storage, BUCKET_ID, ID } from '../lib/appwrite'
|
|
||||||
|
|
||||||
export default function FileUploadModal({ isOpen, onClose, workorderId, onUploadComplete }) {
|
|
||||||
const [uploading, setUploading] = useState(false)
|
|
||||||
const [dragOver, setDragOver] = useState(false)
|
|
||||||
const fileInputRef = useRef(null)
|
|
||||||
|
|
||||||
const handleDrop = async (e) => {
|
|
||||||
e.preventDefault()
|
|
||||||
setDragOver(false)
|
|
||||||
|
|
||||||
const file = e.dataTransfer.files[0]
|
|
||||||
if (file) {
|
|
||||||
await uploadFile(file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleFileSelect = async (e) => {
|
|
||||||
const file = e.target.files[0]
|
|
||||||
if (file) {
|
|
||||||
await uploadFile(file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const uploadFile = async (file) => {
|
|
||||||
// Validate file type
|
|
||||||
const allowedTypes = ['application/pdf', 'image/gif', 'image/png', 'image/jpeg']
|
|
||||||
if (!allowedTypes.includes(file.type)) {
|
|
||||||
alert('Only PDF, GIF, PNG, and JPEG files are allowed.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
setUploading(true)
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await storage.createFile(
|
|
||||||
BUCKET_ID,
|
|
||||||
ID.unique(),
|
|
||||||
file
|
|
||||||
)
|
|
||||||
|
|
||||||
onUploadComplete?.(response)
|
|
||||||
onClose()
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Upload error:', error)
|
|
||||||
alert('Error uploading file: ' + error.message)
|
|
||||||
} finally {
|
|
||||||
setUploading(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isOpen) return null
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="overlay">
|
|
||||||
<span className="overlay-close" onClick={onClose}>
|
|
||||||
<FaTimes />
|
|
||||||
</span>
|
|
||||||
<div className="overlay-content">
|
|
||||||
<h2 className="text-center mb-2">UPLOAD AREA</h2>
|
|
||||||
<p className="mb-2">Upload Attachments for WOID {workorderId}</p>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className={`drop-zone ${dragOver ? 'border-green' : ''}`}
|
|
||||||
onDrop={handleDrop}
|
|
||||||
onDragOver={(e) => { e.preventDefault(); setDragOver(true) }}
|
|
||||||
onDragLeave={() => setDragOver(false)}
|
|
||||||
onClick={() => fileInputRef.current?.click()}
|
|
||||||
>
|
|
||||||
{uploading ? (
|
|
||||||
<div>
|
|
||||||
<FaSpinner className="spinner" size={32} />
|
|
||||||
<p className="mt-1">Uploading file...</p>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<p>Drop file here (.pdf, .gif, .png, .jpg, .jpeg)</p>
|
|
||||||
<p className="mt-1">or</p>
|
|
||||||
<button className="btn btn-dark mt-1">Select File</button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<input
|
|
||||||
ref={fileInputRef}
|
|
||||||
type="file"
|
|
||||||
accept=".pdf,.gif,.png,.jpg,.jpeg"
|
|
||||||
style={{ display: 'none' }}
|
|
||||||
onChange={handleFileSelect}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,8 @@
|
|||||||
export default function Footer() {
|
export default function Footer() {
|
||||||
return (
|
return (
|
||||||
<footer className="footer">
|
<footer className="footer">
|
||||||
<p>
|
<p className="faint" style={{ fontSize: 13, margin: 0 }}>
|
||||||
WOMS 2.0 - Work Order Management System |
|
Webklar Ticket-Plattform · © {new Date().getFullYear()}
|
||||||
Powered by <a href="https://appwrite.io" target="_blank" rel="noopener noreferrer">Appwrite</a> & React
|
|
||||||
</p>
|
|
||||||
<p className="text-small text-grey">
|
|
||||||
© {new Date().getFullYear()} - All rights reserved
|
|
||||||
</p>
|
</p>
|
||||||
</footer>
|
</footer>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { SiGitea } from 'react-icons/si'
|
||||||
|
|
||||||
|
export default function GiteaLinkButton({ href, title = 'In Gitea oeffnen', size = 16 }) {
|
||||||
|
if (!href) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href={href}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
title={title}
|
||||||
|
aria-label={title}
|
||||||
|
style={{
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
borderRadius: 6,
|
||||||
|
background: 'rgba(99, 102, 241, 0.15)',
|
||||||
|
border: '1px solid rgba(99, 102, 241, 0.35)',
|
||||||
|
color: '#818cf8',
|
||||||
|
textDecoration: 'none',
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SiGitea size={size} />
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,262 @@
|
|||||||
|
import { useState, useEffect, useCallback } from 'react'
|
||||||
|
import { FaFilePdf, FaPlus, FaLink, FaSpinner, FaRotate } from 'react-icons/fa6'
|
||||||
|
import { databases, DATABASE_ID, COLLECTIONS } from '../lib/appwrite'
|
||||||
|
import { integrationsApi, formatMoney, INVOICE_STATUS } from '../lib/integrationsApi'
|
||||||
|
import { useInvoices } from '../hooks/useInvoices'
|
||||||
|
import { customerStage, isInvoiceReady } from '../lib/leads'
|
||||||
|
|
||||||
|
const card = {
|
||||||
|
background: 'rgba(45, 55, 72, 0.5)',
|
||||||
|
borderRadius: '12px',
|
||||||
|
padding: '16px',
|
||||||
|
border: '1px solid rgba(16, 185, 129, 0.2)',
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function InvoicePanel({ ticket }) {
|
||||||
|
const [customer, setCustomer] = useState(null)
|
||||||
|
const [customerLoading, setCustomerLoading] = useState(true)
|
||||||
|
const [customerError, setCustomerError] = useState(null)
|
||||||
|
|
||||||
|
const loadCustomer = useCallback(async () => {
|
||||||
|
if (!ticket.customerId) {
|
||||||
|
setCustomer(null)
|
||||||
|
setCustomerLoading(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setCustomerLoading(true)
|
||||||
|
try {
|
||||||
|
const doc = await databases.getDocument(DATABASE_ID, COLLECTIONS.CUSTOMERS, ticket.customerId)
|
||||||
|
setCustomer(doc)
|
||||||
|
setCustomerError(null)
|
||||||
|
} catch (err) {
|
||||||
|
setCustomerError(err.message)
|
||||||
|
setCustomer(null)
|
||||||
|
} finally {
|
||||||
|
setCustomerLoading(false)
|
||||||
|
}
|
||||||
|
}, [ticket.customerId])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadCustomer()
|
||||||
|
}, [loadCustomer])
|
||||||
|
|
||||||
|
const clientId = customer?.invoiceNinjaClientId || null
|
||||||
|
const { invoices, loading, error, refresh } = useInvoices(clientId)
|
||||||
|
|
||||||
|
if (customerLoading) {
|
||||||
|
return (
|
||||||
|
<div style={card}>
|
||||||
|
<FaSpinner className="spinner" /> Kunde wird geladen...
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ticket.customerId) {
|
||||||
|
return (
|
||||||
|
<div style={card}>
|
||||||
|
<p style={{ color: '#a0aec0', margin: 0 }}>Kein Kunde am Ticket verknüpft, keine Rechnungen verfügbar.</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={card}>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '12px' }}>
|
||||||
|
<h5 style={{ color: 'var(--dark-text)', fontWeight: 'bold', margin: 0, fontSize: '16px' }}>
|
||||||
|
Rechnungen {customer?.name ? `- ${customer.name}` : ''}
|
||||||
|
</h5>
|
||||||
|
{clientId && (
|
||||||
|
<button className="btn" onClick={refresh} title="Aktualisieren"
|
||||||
|
style={{ background: '#374151', color: 'white', border: 'none', padding: '6px 10px', borderRadius: '6px', cursor: 'pointer' }}>
|
||||||
|
<FaRotate />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{customerError && <div className="text-red" style={{ marginBottom: 8 }}>{customerError}</div>}
|
||||||
|
|
||||||
|
{customer && customerStage(customer) === 'lead' ? (
|
||||||
|
<p style={{ color: '#a0aec0', margin: 0 }}>
|
||||||
|
Dieser Kunde ist noch ein Lead. Erst zum festen Kunden machen (im Kunden-Bereich, vollständige Daten nötig), dann sind Rechnungen möglich.
|
||||||
|
</p>
|
||||||
|
) : !clientId && !isInvoiceReady(customer) ? (
|
||||||
|
<p style={{ color: '#a0aec0', margin: 0 }}>
|
||||||
|
Rechnungsdaten unvollständig. Bitte im Kunden-Bereich Unternehmen, E-Mail, Straße, PLZ und Stadt ergänzen.
|
||||||
|
</p>
|
||||||
|
) : !clientId ? (
|
||||||
|
<LinkClient customer={customer} onLinked={loadCustomer} />
|
||||||
|
) : (
|
||||||
|
<InvoiceList invoices={invoices} loading={loading} error={error} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------- Verknuepfung -----------
|
||||||
|
function LinkClient({ customer, onLinked }) {
|
||||||
|
const [search, setSearch] = useState(customer?.name || '')
|
||||||
|
const [results, setResults] = useState([])
|
||||||
|
const [busy, setBusy] = useState(false)
|
||||||
|
const [error, setError] = useState(null)
|
||||||
|
const [saving, setSaving] = useState(false)
|
||||||
|
|
||||||
|
const doSearch = async () => {
|
||||||
|
setBusy(true)
|
||||||
|
setError(null)
|
||||||
|
try {
|
||||||
|
const data = await integrationsApi.listClients(search)
|
||||||
|
setResults(data.clients || [])
|
||||||
|
} catch (err) {
|
||||||
|
setError(err.message)
|
||||||
|
} finally {
|
||||||
|
setBusy(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
doSearch()
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const saveMapping = async (clientId) => {
|
||||||
|
setSaving(true)
|
||||||
|
setError(null)
|
||||||
|
try {
|
||||||
|
await databases.updateDocument(DATABASE_ID, COLLECTIONS.CUSTOMERS, customer.$id, {
|
||||||
|
invoiceNinjaClientId: clientId,
|
||||||
|
})
|
||||||
|
onLinked()
|
||||||
|
} catch (err) {
|
||||||
|
setError(err.message)
|
||||||
|
setSaving(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createAndLink = async () => {
|
||||||
|
setSaving(true)
|
||||||
|
setError(null)
|
||||||
|
try {
|
||||||
|
const data = await integrationsApi.createClient({
|
||||||
|
name: customer.name || customer.companyName || 'Neuer Kunde',
|
||||||
|
email: customer.email || '',
|
||||||
|
phone: customer.phone || '',
|
||||||
|
address: customer.location || '',
|
||||||
|
})
|
||||||
|
if (data.client?.id) {
|
||||||
|
await saveMapping(data.client.id)
|
||||||
|
} else {
|
||||||
|
throw new Error('Client konnte nicht angelegt werden')
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
setError(err.message)
|
||||||
|
setSaving(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<p style={{ color: '#a0aec0', fontSize: '13px', marginTop: 0 }}>
|
||||||
|
Dieser Kunde ist noch nicht mit InvoiceNinja verknüpft.
|
||||||
|
</p>
|
||||||
|
<div style={{ display: 'flex', gap: '8px', marginBottom: '10px' }}>
|
||||||
|
<input
|
||||||
|
className="form-control"
|
||||||
|
style={{ margin: 0 }}
|
||||||
|
placeholder="InvoiceNinja-Kunde suchen …"
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
|
onKeyDown={(e) => e.key === 'Enter' && doSearch()}
|
||||||
|
/>
|
||||||
|
<button className="btn btn-green" style={{ margin: 0 }} onClick={doSearch} disabled={busy}>
|
||||||
|
{busy ? <FaSpinner className="spinner" /> : 'Suchen'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{error && <div className="text-red" style={{ marginBottom: 8 }}>{error}</div>}
|
||||||
|
|
||||||
|
<div style={{ maxHeight: '180px', overflowY: 'auto', marginBottom: '10px' }}>
|
||||||
|
{results.map((c) => (
|
||||||
|
<div key={c.id} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '8px', borderBottom: '1px solid rgba(255,255,255,0.06)' }}>
|
||||||
|
<div>
|
||||||
|
<strong style={{ color: 'var(--dark-text)' }}>{c.name}</strong>
|
||||||
|
{c.number ? <span style={{ color: '#a0aec0' }}> · {c.number}</span> : null}
|
||||||
|
{c.email ? <div style={{ color: '#a0aec0', fontSize: '12px' }}>{c.email}</div> : null}
|
||||||
|
</div>
|
||||||
|
<button className="btn btn-green" style={{ margin: 0 }} disabled={saving} onClick={() => saveMapping(c.id)}>
|
||||||
|
<FaLink /> Verknüpfen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{!busy && results.length === 0 && (
|
||||||
|
<p style={{ color: '#a0aec0', fontSize: '13px' }}>Keine Treffer.</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button className="btn" style={{ background: '#3b82f6', color: 'white', border: 'none', padding: '10px 16px', borderRadius: '8px', cursor: 'pointer' }} disabled={saving} onClick={createAndLink}>
|
||||||
|
{saving ? <FaSpinner className="spinner" /> : <><FaPlus /> Neuen InvoiceNinja-Kunden anlegen & verknüpfen</>}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------- Rechnungsliste ---------
|
||||||
|
// Nur anzeigen: neue Rechnungen entstehen direkt in InvoiceNinja
|
||||||
|
function InvoiceList({ invoices, loading, error }) {
|
||||||
|
const [openingId, setOpeningId] = useState(null)
|
||||||
|
|
||||||
|
const openPdf = async (id) => {
|
||||||
|
setOpeningId(id)
|
||||||
|
try {
|
||||||
|
await integrationsApi.openInvoicePdf(id)
|
||||||
|
} catch (err) {
|
||||||
|
alert('PDF konnte nicht geöffnet werden: ' + err.message)
|
||||||
|
} finally {
|
||||||
|
setOpeningId(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{error && <div className="text-red" style={{ marginBottom: 8 }}>{error}</div>}
|
||||||
|
|
||||||
|
{loading ? (
|
||||||
|
<p style={{ color: '#a0aec0' }}><FaSpinner className="spinner" /> Rechnungen werden geladen...</p>
|
||||||
|
) : invoices.length === 0 ? (
|
||||||
|
<p style={{ color: '#a0aec0', fontSize: '13px' }}>Noch keine Rechnungen für diesen Kunden.</p>
|
||||||
|
) : (
|
||||||
|
<div style={{ overflowX: 'auto' }}>
|
||||||
|
<table className="table" style={{ width: '100%', fontSize: '13px' }}>
|
||||||
|
<thead>
|
||||||
|
<tr style={{ color: '#a0aec0', textAlign: 'left' }}>
|
||||||
|
<th>Nr.</th><th>Datum</th><th>Fällig</th><th>Betrag</th><th>Offen</th><th>Status</th><th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{invoices.map((inv) => {
|
||||||
|
const st = INVOICE_STATUS[inv.status_id] || { label: '–', color: '#a0aec0' }
|
||||||
|
const overdue = Number(inv.balance) > 0 && inv.due_date && new Date(inv.due_date) < new Date()
|
||||||
|
return (
|
||||||
|
<tr key={inv.id}>
|
||||||
|
<td style={{ color: 'var(--dark-text)' }}>{inv.number || inv.id?.slice(-6)}</td>
|
||||||
|
<td style={{ color: '#cbd5e0' }}>{inv.date || '-'}</td>
|
||||||
|
<td style={{ color: overdue ? '#ef4444' : '#cbd5e0' }}>{inv.due_date || '-'}</td>
|
||||||
|
<td style={{ color: 'var(--dark-text)' }}>{formatMoney(inv.amount)}</td>
|
||||||
|
<td style={{ color: Number(inv.balance) > 0 ? '#f59e0b' : '#10b981' }}>{formatMoney(inv.balance)}</td>
|
||||||
|
<td><span style={{ color: st.color, fontWeight: 'bold' }}>{st.label}{overdue ? ' (überfällig)' : ''}</span></td>
|
||||||
|
<td>
|
||||||
|
<button className="btn" title="PDF öffnen" disabled={openingId === inv.id}
|
||||||
|
style={{ background: '#ef4444', color: 'white', border: 'none', padding: '6px 10px', borderRadius: '6px', cursor: 'pointer' }}
|
||||||
|
onClick={() => openPdf(inv.id)}>
|
||||||
|
{openingId === inv.id ? <FaSpinner className="spinner" /> : <FaFilePdf />}
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,245 @@
|
|||||||
|
import { useRef, useEffect } from 'react';
|
||||||
|
|
||||||
|
const LetterGlitch = ({
|
||||||
|
glitchColors = ['#2b4539', '#61dca3', '#61b3dc'],
|
||||||
|
className = '',
|
||||||
|
glitchSpeed = 50,
|
||||||
|
centerVignette = false,
|
||||||
|
outerVignette = true,
|
||||||
|
smooth = true,
|
||||||
|
characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$&*()-_+=/[]{};:<>.,0123456789'
|
||||||
|
}) => {
|
||||||
|
const canvasRef = useRef(null);
|
||||||
|
const animationRef = useRef(null);
|
||||||
|
const letters = useRef([]);
|
||||||
|
const grid = useRef({ columns: 0, rows: 0 });
|
||||||
|
const context = useRef(null);
|
||||||
|
const lastGlitchTime = useRef(Date.now());
|
||||||
|
|
||||||
|
const lettersAndSymbols = Array.from(characters);
|
||||||
|
|
||||||
|
const fontSize = 16;
|
||||||
|
const charWidth = 10;
|
||||||
|
const charHeight = 20;
|
||||||
|
|
||||||
|
const getRandomChar = () => {
|
||||||
|
return lettersAndSymbols[Math.floor(Math.random() * lettersAndSymbols.length)];
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRandomColor = () => {
|
||||||
|
return glitchColors[Math.floor(Math.random() * glitchColors.length)];
|
||||||
|
};
|
||||||
|
|
||||||
|
const hexToRgb = hex => {
|
||||||
|
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
|
||||||
|
hex = hex.replace(shorthandRegex, (m, r, g, b) => {
|
||||||
|
return r + r + g + g + b + b;
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||||
|
return result
|
||||||
|
? {
|
||||||
|
r: parseInt(result[1], 16),
|
||||||
|
g: parseInt(result[2], 16),
|
||||||
|
b: parseInt(result[3], 16)
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const interpolateColor = (start, end, factor) => {
|
||||||
|
const result = {
|
||||||
|
r: Math.round(start.r + (end.r - start.r) * factor),
|
||||||
|
g: Math.round(start.g + (end.g - start.g) * factor),
|
||||||
|
b: Math.round(start.b + (end.b - start.b) * factor)
|
||||||
|
};
|
||||||
|
return `rgb(${result.r}, ${result.g}, ${result.b})`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const calculateGrid = (width, height) => {
|
||||||
|
const columns = Math.ceil(width / charWidth);
|
||||||
|
const rows = Math.ceil(height / charHeight);
|
||||||
|
return { columns, rows };
|
||||||
|
};
|
||||||
|
|
||||||
|
const initializeLetters = (columns, rows) => {
|
||||||
|
grid.current = { columns, rows };
|
||||||
|
const totalLetters = columns * rows;
|
||||||
|
letters.current = Array.from({ length: totalLetters }, () => ({
|
||||||
|
char: getRandomChar(),
|
||||||
|
color: getRandomColor(),
|
||||||
|
targetColor: getRandomColor(),
|
||||||
|
colorProgress: 1
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const resizeCanvas = () => {
|
||||||
|
const canvas = canvasRef.current;
|
||||||
|
if (!canvas) return;
|
||||||
|
const parent = canvas.parentElement;
|
||||||
|
if (!parent) return;
|
||||||
|
|
||||||
|
const dpr = window.devicePixelRatio || 1;
|
||||||
|
const rect = parent.getBoundingClientRect();
|
||||||
|
|
||||||
|
canvas.width = rect.width * dpr;
|
||||||
|
canvas.height = rect.height * dpr;
|
||||||
|
|
||||||
|
canvas.style.width = `${rect.width}px`;
|
||||||
|
canvas.style.height = `${rect.height}px`;
|
||||||
|
|
||||||
|
if (context.current) {
|
||||||
|
context.current.setTransform(dpr, 0, 0, dpr, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { columns, rows } = calculateGrid(rect.width, rect.height);
|
||||||
|
initializeLetters(columns, rows);
|
||||||
|
|
||||||
|
drawLetters();
|
||||||
|
};
|
||||||
|
|
||||||
|
const drawLetters = () => {
|
||||||
|
if (!context.current || letters.current.length === 0) return;
|
||||||
|
const ctx = context.current;
|
||||||
|
const { width, height } = canvasRef.current.getBoundingClientRect();
|
||||||
|
ctx.clearRect(0, 0, width, height);
|
||||||
|
ctx.font = `${fontSize}px monospace`;
|
||||||
|
ctx.textBaseline = 'top';
|
||||||
|
|
||||||
|
letters.current.forEach((letter, index) => {
|
||||||
|
const x = (index % grid.current.columns) * charWidth;
|
||||||
|
const y = Math.floor(index / grid.current.columns) * charHeight;
|
||||||
|
ctx.fillStyle = letter.color;
|
||||||
|
ctx.fillText(letter.char, x, y);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateLetters = () => {
|
||||||
|
if (!letters.current || letters.current.length === 0) return;
|
||||||
|
|
||||||
|
const updateCount = Math.max(1, Math.floor(letters.current.length * 0.05));
|
||||||
|
|
||||||
|
for (let i = 0; i < updateCount; i++) {
|
||||||
|
const index = Math.floor(Math.random() * letters.current.length);
|
||||||
|
if (!letters.current[index]) continue;
|
||||||
|
|
||||||
|
letters.current[index].char = getRandomChar();
|
||||||
|
letters.current[index].targetColor = getRandomColor();
|
||||||
|
|
||||||
|
if (!smooth) {
|
||||||
|
letters.current[index].color = letters.current[index].targetColor;
|
||||||
|
letters.current[index].colorProgress = 1;
|
||||||
|
} else {
|
||||||
|
letters.current[index].colorProgress = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSmoothTransitions = () => {
|
||||||
|
let needsRedraw = false;
|
||||||
|
letters.current.forEach(letter => {
|
||||||
|
if (letter.colorProgress < 1) {
|
||||||
|
letter.colorProgress += 0.05;
|
||||||
|
if (letter.colorProgress > 1) letter.colorProgress = 1;
|
||||||
|
|
||||||
|
const startRgb = hexToRgb(letter.color);
|
||||||
|
const endRgb = hexToRgb(letter.targetColor);
|
||||||
|
if (startRgb && endRgb) {
|
||||||
|
letter.color = interpolateColor(startRgb, endRgb, letter.colorProgress);
|
||||||
|
needsRedraw = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (needsRedraw) {
|
||||||
|
drawLetters();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const animate = () => {
|
||||||
|
const now = Date.now();
|
||||||
|
if (now - lastGlitchTime.current >= glitchSpeed) {
|
||||||
|
updateLetters();
|
||||||
|
drawLetters();
|
||||||
|
lastGlitchTime.current = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (smooth) {
|
||||||
|
handleSmoothTransitions();
|
||||||
|
}
|
||||||
|
|
||||||
|
animationRef.current = requestAnimationFrame(animate);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const canvas = canvasRef.current;
|
||||||
|
if (!canvas) return;
|
||||||
|
|
||||||
|
context.current = canvas.getContext('2d');
|
||||||
|
resizeCanvas();
|
||||||
|
animate();
|
||||||
|
|
||||||
|
let resizeTimeout;
|
||||||
|
|
||||||
|
const handleResize = () => {
|
||||||
|
clearTimeout(resizeTimeout);
|
||||||
|
resizeTimeout = setTimeout(() => {
|
||||||
|
cancelAnimationFrame(animationRef.current);
|
||||||
|
resizeCanvas();
|
||||||
|
animate();
|
||||||
|
}, 100);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cancelAnimationFrame(animationRef.current);
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
};
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [glitchSpeed, smooth]);
|
||||||
|
|
||||||
|
const containerStyle = {
|
||||||
|
position: 'relative',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
backgroundColor: '#000000',
|
||||||
|
overflow: 'hidden'
|
||||||
|
};
|
||||||
|
|
||||||
|
const canvasStyle = {
|
||||||
|
display: 'block',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%'
|
||||||
|
};
|
||||||
|
|
||||||
|
const outerVignetteStyle = {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
pointerEvents: 'none',
|
||||||
|
background: 'radial-gradient(circle, rgba(0,0,0,0) 60%, rgba(0,0,0,1) 100%)'
|
||||||
|
};
|
||||||
|
|
||||||
|
const centerVignetteStyle = {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
pointerEvents: 'none',
|
||||||
|
background: 'radial-gradient(circle, rgba(0,0,0,0.8) 0%, rgba(0,0,0,0) 60%)'
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={containerStyle} className={className}>
|
||||||
|
<canvas ref={canvasRef} style={canvasStyle} />
|
||||||
|
{outerVignette && <div style={outerVignetteStyle}></div>}
|
||||||
|
{centerVignette && <div style={centerVignetteStyle}></div>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LetterGlitch;
|
||||||
|
|
||||||
+94
-32
@@ -1,49 +1,111 @@
|
|||||||
import { Link, useNavigate } from 'react-router-dom'
|
import { useState } from 'react'
|
||||||
|
import { Link, useLocation, useNavigate } from 'react-router-dom'
|
||||||
import { useAuth } from '../context/AuthContext'
|
import { useAuth } from '../context/AuthContext'
|
||||||
import { FaUser } from 'react-icons/fa'
|
import {
|
||||||
|
IconTicket,
|
||||||
|
IconUsers,
|
||||||
|
IconTargetArrow,
|
||||||
|
IconReportMoney,
|
||||||
|
IconFolders,
|
||||||
|
IconCalendar,
|
||||||
|
IconRoute,
|
||||||
|
IconSettings,
|
||||||
|
IconLogout,
|
||||||
|
IconLayoutSidebarLeftCollapse,
|
||||||
|
IconLayoutSidebarLeftExpand,
|
||||||
|
} from '@tabler/icons-react'
|
||||||
|
|
||||||
export default function Navbar() {
|
export default function Sidebar() {
|
||||||
const { user, logout } = useAuth()
|
const { user, logout, isAdmin } = useAuth()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
const location = useLocation()
|
||||||
|
const [collapsed, setCollapsed] = useState(false)
|
||||||
|
|
||||||
const handleLogout = async () => {
|
const handleLogout = async () => {
|
||||||
await logout()
|
await logout()
|
||||||
navigate('/login')
|
navigate('/login')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const groups = [
|
||||||
|
{
|
||||||
|
label: 'Betrieb',
|
||||||
|
links: [
|
||||||
|
{ label: 'Tickets', href: '/tickets', icon: <IconTicket /> },
|
||||||
|
{ label: 'Planboard', href: '/planboard', icon: <IconCalendar /> },
|
||||||
|
{ label: 'Roadmaps', href: '/roadmaps', icon: <IconRoute /> },
|
||||||
|
{ label: 'Projekte', href: '/projects', icon: <IconFolders /> },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'CRM',
|
||||||
|
links: [
|
||||||
|
{ label: 'Kunden', href: '/customers', icon: <IconUsers /> },
|
||||||
|
{ label: 'Leads', href: '/leads', icon: <IconTargetArrow /> },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Finanzen',
|
||||||
|
links: [{ label: 'Finanzen', href: '/finance', icon: <IconReportMoney /> }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'System',
|
||||||
|
links: [
|
||||||
|
...(isAdmin ? [{ label: 'Admin', href: '/admin', icon: <IconSettings /> }] : []),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const isActive = (href) =>
|
||||||
|
location.pathname === href || location.pathname.startsWith(href + '/')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className="navbar">
|
<aside className={`side ${collapsed ? 'collapsed' : ''}`}>
|
||||||
<div className="navbar-brand">
|
<div className="side-head">
|
||||||
<span>NetWEB</span>
|
<Link to="/" className="side-logo" title="Webklar">W</Link>
|
||||||
<img src="/logo.png" alt="" height="18" width="18" />
|
<div className="side-brand">
|
||||||
<span>Systems</span>
|
Webklar
|
||||||
|
<small>Ticket-Plattform</small>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul className="navbar-nav">
|
<nav className="side-nav">
|
||||||
<li><Link to="/tickets" className="nav-link">Tickets</Link></li>
|
{/* Gruppen ohne Eintrag (System fuer Nicht-Admins) nicht anzeigen */}
|
||||||
<li><Link to="/assets" className="nav-link">Assets</Link></li>
|
{groups.filter((group) => group.links.length > 0).map((group) => (
|
||||||
<li><Link to="/dashboard" className="nav-link">Dashboard</Link></li>
|
<div className="side-group" key={group.label}>
|
||||||
<li><Link to="/planboard" className="nav-link">Planboard</Link></li>
|
<div className="side-group-label">{group.label}</div>
|
||||||
<li><Link to="/projects" className="nav-link">Projects</Link></li>
|
{group.links.map((link) => (
|
||||||
<li><Link to="/reports" className="nav-link">Reports</Link></li>
|
<Link
|
||||||
<li><Link to="/docs" className="nav-link">Docs</Link></li>
|
key={link.href}
|
||||||
</ul>
|
to={link.href}
|
||||||
|
className={`side-link ${isActive(link.href) ? 'active' : ''}`}
|
||||||
|
title={link.label}
|
||||||
|
>
|
||||||
|
{link.icon}
|
||||||
|
<span className="side-link-label">{link.label}</span>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
|
||||||
<div className="nav-right">
|
<div className="side-foot">
|
||||||
{user ? (
|
{user && (
|
||||||
<button
|
<div className="side-link" title={user.name || user.email} style={{ cursor: 'default' }}>
|
||||||
onClick={handleLogout}
|
<div className="side-logo" style={{ width: 28, height: 28, fontSize: 12, background: 'linear-gradient(135deg,#667eea,#764ba2)' }}>
|
||||||
className="nav-link"
|
{(user.name || user.email || '?').charAt(0).toUpperCase()}
|
||||||
style={{ background: 'none', border: 'none', cursor: 'pointer' }}
|
</div>
|
||||||
>
|
<span className="side-link-label text-ellipsis">{user.name || user.email}</span>
|
||||||
<FaUser /> Logout, {user.name || user.email}
|
</div>
|
||||||
</button>
|
|
||||||
) : (
|
|
||||||
<Link to="/login" className="nav-link">
|
|
||||||
<FaUser /> Login
|
|
||||||
</Link>
|
|
||||||
)}
|
)}
|
||||||
|
<button className="side-toggle" onClick={handleLogout} title="Logout">
|
||||||
|
<IconLogout size={20} />
|
||||||
|
<span className="side-link-label">Logout</span>
|
||||||
|
</button>
|
||||||
|
<button className="side-toggle" onClick={() => setCollapsed((c) => !c)} title="Menue ein-/ausklappen">
|
||||||
|
{collapsed ? <IconLayoutSidebarLeftExpand size={20} /> : <IconLayoutSidebarLeftCollapse size={20} />}
|
||||||
|
<span className="side-link-label">Einklappen</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</aside>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { FaEye } from 'react-icons/fa6'
|
||||||
|
|
||||||
|
export default function PreviewLinkButton({ href, title = 'Preview oeffnen', size = 16 }) {
|
||||||
|
if (!href) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href={href}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
title={title}
|
||||||
|
aria-label={title}
|
||||||
|
style={{
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
borderRadius: 6,
|
||||||
|
background: 'rgba(16, 185, 129, 0.15)',
|
||||||
|
border: '1px solid rgba(16, 185, 129, 0.35)',
|
||||||
|
color: '#34d399',
|
||||||
|
textDecoration: 'none',
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FaEye size={size} />
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
const PRIORITIES = [
|
|
||||||
{ value: 0, label: 'None' },
|
|
||||||
{ value: 1, label: 'Low' },
|
|
||||||
{ value: 2, label: 'Medium' },
|
|
||||||
{ value: 3, label: 'High' },
|
|
||||||
{ value: 4, label: 'Critical' }
|
|
||||||
]
|
|
||||||
|
|
||||||
const PRIORITY_LABELS = {
|
|
||||||
0: 'NONE',
|
|
||||||
1: 'LOW',
|
|
||||||
2: 'MEDIUM',
|
|
||||||
3: 'HIGH',
|
|
||||||
4: 'CRITICAL'
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function PriorityDropdown({ value, onChange }) {
|
|
||||||
return (
|
|
||||||
<div className="dropdown">
|
|
||||||
<button className="btn" style={{ background: 'inherit', color: 'inherit' }}>
|
|
||||||
{PRIORITY_LABELS[value] || 'LOW'}
|
|
||||||
</button>
|
|
||||||
<div className="dropdown-content">
|
|
||||||
{PRIORITIES.map(priority => (
|
|
||||||
<span
|
|
||||||
key={priority.value}
|
|
||||||
className="dropdown-item"
|
|
||||||
onClick={() => onChange(priority.value)}
|
|
||||||
>
|
|
||||||
{priority.label}
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
import { IconFolders } from '@tabler/icons-react'
|
||||||
|
import { StatusPill } from './ui/StatusPill'
|
||||||
|
import { databases, DATABASE_ID, COLLECTIONS, Query } from '../lib/appwrite'
|
||||||
|
import { fetchRoadmapByTicketId } from '../lib/roadmaps'
|
||||||
|
import { useWebsiteProjects } from '../hooks/useWebsiteProjects'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Projekt-Kontextbox im Ticket: zeigt automatisch das zugehoerige Projekt,
|
||||||
|
* alle anderen Tickets desselben Kunden (klickbar) und den Link zum aktiven Plan.
|
||||||
|
* Rendert nichts, wenn das Ticket keinen Projektbezug hat.
|
||||||
|
*/
|
||||||
|
export default function ProjectContextPanel({ ticket }) {
|
||||||
|
const { fetchByTicketId } = useWebsiteProjects()
|
||||||
|
const [context, setContext] = useState(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let cancelled = false
|
||||||
|
async function load() {
|
||||||
|
try {
|
||||||
|
// Projektbezug: Roadmap dieses Tickets oder direkt zugewiesenes Projekt
|
||||||
|
const [roadmap, assignedProjects] = await Promise.all([
|
||||||
|
fetchRoadmapByTicketId(ticket.$id).catch(() => null),
|
||||||
|
fetchByTicketId(ticket.$id),
|
||||||
|
])
|
||||||
|
const projectName = roadmap?.projectName || assignedProjects[0]?.projectName || ''
|
||||||
|
if (!roadmap && !assignedProjects.length) {
|
||||||
|
if (!cancelled) setContext(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let otherTickets = []
|
||||||
|
if (ticket.customerId) {
|
||||||
|
const res = await databases.listDocuments(DATABASE_ID, COLLECTIONS.WORKORDERS, [
|
||||||
|
Query.equal('customerId', ticket.customerId),
|
||||||
|
Query.orderDesc('$createdAt'),
|
||||||
|
Query.limit(50),
|
||||||
|
])
|
||||||
|
otherTickets = res.documents.filter((t) => t.$id !== ticket.$id)
|
||||||
|
}
|
||||||
|
if (!cancelled) setContext({ roadmap, projectName, otherTickets })
|
||||||
|
} catch {
|
||||||
|
if (!cancelled) setContext(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
load()
|
||||||
|
return () => { cancelled = true }
|
||||||
|
}, [ticket.$id, ticket.customerId, fetchByTicketId])
|
||||||
|
|
||||||
|
if (!context) return null
|
||||||
|
const { roadmap, projectName, otherTickets } = context
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="ui-card pad" style={{ marginTop: 16 }}>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12, flexWrap: 'wrap', marginBottom: 10 }}>
|
||||||
|
<h5 style={{ fontWeight: 700, margin: 0, display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||||
|
<IconFolders size={18} /> Projekt: {projectName || '-'}
|
||||||
|
</h5>
|
||||||
|
{roadmap && (
|
||||||
|
<Link to={`/roadmaps/${roadmap.$id}`} className="ui-btn">
|
||||||
|
Plan: {roadmap.title}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!ticket.customerId ? (
|
||||||
|
<p className="faint" style={{ fontSize: 13, margin: 0 }}>
|
||||||
|
Kein Kunde zugeordnet — andere Tickets koennen nicht automatisch verknuepft werden.
|
||||||
|
</p>
|
||||||
|
) : !otherTickets.length ? (
|
||||||
|
<p className="faint" style={{ fontSize: 13, margin: 0 }}>
|
||||||
|
Keine weiteren Tickets zu diesem Kunden/Projekt.
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||||
|
{otherTickets.map((t) => (
|
||||||
|
<Link
|
||||||
|
key={t.$id}
|
||||||
|
to={`/tickets?woid=${t.woid}`}
|
||||||
|
style={{
|
||||||
|
display: 'flex', alignItems: 'center', gap: 10, fontSize: 13,
|
||||||
|
padding: '6px 8px', borderRadius: 8, border: '1px solid var(--border)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="muted" style={{ minWidth: 48, fontWeight: 700 }}>{t.woid}</span>
|
||||||
|
<span style={{ flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
||||||
|
{t.topic || t.title || '-'}
|
||||||
|
</span>
|
||||||
|
<StatusPill status={t.status} />
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
import { FaFileLines } from 'react-icons/fa6'
|
||||||
|
import ReactMarkdown from 'react-markdown'
|
||||||
|
import { fetchProjectReadme } from '../lib/projectAdminApi'
|
||||||
|
|
||||||
|
const markdownComponents = {
|
||||||
|
h1: ({ children }) => <h1 style={{ fontSize: '1.35rem', fontWeight: 700, margin: '0 0 10px', color: '#f8fafc' }}>{children}</h1>,
|
||||||
|
h2: ({ children }) => <h2 style={{ fontSize: '1.15rem', fontWeight: 700, margin: '14px 0 8px', color: '#f1f5f9' }}>{children}</h2>,
|
||||||
|
h3: ({ children }) => <h3 style={{ fontSize: '1rem', fontWeight: 600, margin: '12px 0 6px', color: '#e2e8f0' }}>{children}</h3>,
|
||||||
|
p: ({ children }) => <p style={{ margin: '0 0 10px', lineHeight: 1.6 }}>{children}</p>,
|
||||||
|
ul: ({ children }) => <ul style={{ margin: '0 0 10px', paddingLeft: 20 }}>{children}</ul>,
|
||||||
|
ol: ({ children }) => <ol style={{ margin: '0 0 10px', paddingLeft: 20 }}>{children}</ol>,
|
||||||
|
code: ({ inline, children }) => inline ? (
|
||||||
|
<code style={{ background: 'rgba(30,41,59,0.9)', padding: '2px 6px', borderRadius: 4, fontSize: '0.9em' }}>{children}</code>
|
||||||
|
) : <code>{children}</code>,
|
||||||
|
pre: ({ children }) => <pre style={{ background: 'rgba(15,23,42,0.95)', borderRadius: 6, padding: 10, overflowX: 'auto', margin: '0 0 10px' }}>{children}</pre>,
|
||||||
|
a: ({ href, children }) => <a href={href} target="_blank" rel="noopener noreferrer" style={{ color: '#60a5fa' }}>{children}</a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ProjectReadme({ repoFullName }) {
|
||||||
|
const [state, setState] = useState({ loading: true, content: '', filename: '', error: '' })
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!repoFullName) { setState({ loading: false, content: '', filename: '', error: '' }); return }
|
||||||
|
let cancelled = false
|
||||||
|
setState({ loading: true, content: '', filename: '', error: '' })
|
||||||
|
fetchProjectReadme(repoFullName)
|
||||||
|
.then((data) => {
|
||||||
|
if (cancelled) return
|
||||||
|
if (!data.found) { setState({ loading: false, content: '', filename: '', error: '' }); return }
|
||||||
|
setState({ loading: false, content: data.content || '', filename: data.filename || 'README.md', error: '' })
|
||||||
|
})
|
||||||
|
.catch((err) => { if (!cancelled) setState({ loading: false, content: '', filename: '', error: err.message }) })
|
||||||
|
return () => { cancelled = true }
|
||||||
|
}, [repoFullName])
|
||||||
|
|
||||||
|
if (state.loading) return <p className="text-grey" style={{ fontSize: 13, marginTop: 8 }}>README wird geladen...</p>
|
||||||
|
if (state.error || !state.content) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ marginTop: 10 }}>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 6, fontSize: 13, color: '#94a3b8' }}>
|
||||||
|
<FaFileLines /><span>{state.filename}</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ background: 'rgba(15,23,42,0.6)', border: '1px solid rgba(148,163,184,0.2)', borderRadius: 8, padding: '12px 14px', fontSize: 13, color: '#e2e8f0', maxHeight: 320, overflowY: 'auto' }}>
|
||||||
|
<ReactMarkdown components={markdownComponents}>{state.content}</ReactMarkdown>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
import { FaTimes } from 'react-icons/fa'
|
|
||||||
import { format } from 'date-fns'
|
|
||||||
|
|
||||||
const STATUS_CLASSES = {
|
|
||||||
'Open': 'bg-red',
|
|
||||||
'Occupied': 'bg-blue-grey',
|
|
||||||
'Assigned': 'bg-teal',
|
|
||||||
'Awaiting': 'bg-amber',
|
|
||||||
'Closed': 'bg-grey'
|
|
||||||
}
|
|
||||||
|
|
||||||
const PRIORITY_CLASSES = {
|
|
||||||
0: 'bg-blue',
|
|
||||||
1: 'bg-green',
|
|
||||||
2: 'bg-amber',
|
|
||||||
3: 'bg-orange',
|
|
||||||
4: 'bg-red'
|
|
||||||
}
|
|
||||||
|
|
||||||
const PRIORITY_LABELS = {
|
|
||||||
0: 'NONE',
|
|
||||||
1: 'LOW',
|
|
||||||
2: 'MEDIUM',
|
|
||||||
3: 'HIGH',
|
|
||||||
4: 'CRITICAL'
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function QuickOverviewModal({ isOpen, onClose, workorders = [] }) {
|
|
||||||
if (!isOpen) return null
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="overlay">
|
|
||||||
<span className="overlay-close" onClick={onClose}>
|
|
||||||
<FaTimes />
|
|
||||||
</span>
|
|
||||||
<div className="overlay-content">
|
|
||||||
<h3 className="mb-2">WOMS Ticket Quick Overview</h3>
|
|
||||||
|
|
||||||
<table className="table" style={{ background: '#fff', color: '#000' }}>
|
|
||||||
<thead>
|
|
||||||
<tr className="bg-dark-grey text-white">
|
|
||||||
<th>WOID</th>
|
|
||||||
<th>Created</th>
|
|
||||||
<th>Type</th>
|
|
||||||
<th>System</th>
|
|
||||||
<th>CID & CLO</th>
|
|
||||||
<th>Requested by</th>
|
|
||||||
<th>Topic</th>
|
|
||||||
<th className="text-center">Priority</th>
|
|
||||||
<th className="text-right">Status</th>
|
|
||||||
<th>Editor</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{workorders.map(wo => (
|
|
||||||
<tr key={wo.$id} className="bg-white">
|
|
||||||
<td className="bg-dark-grey text-white text-small">
|
|
||||||
{wo.woid || wo.$id?.slice(-5)}
|
|
||||||
</td>
|
|
||||||
<td className="bg-light-grey text-small">
|
|
||||||
{format(new Date(wo.$createdAt || wo.createdAt), 'dd.MM.yyyy, HH:mm')}h
|
|
||||||
</td>
|
|
||||||
<td className="bg-light-grey text-small">{wo.type}</td>
|
|
||||||
<td className="bg-light-grey text-small">{wo.systemType || 'n/a'}</td>
|
|
||||||
<td className="text-small">{wo.customerCode} {wo.customerLocation}</td>
|
|
||||||
<td className="text-small">{wo.requestedBy}</td>
|
|
||||||
<td className="text-small">{wo.topic}</td>
|
|
||||||
<td className={`text-small text-center ${PRIORITY_CLASSES[wo.priority]}`}>
|
|
||||||
{PRIORITY_LABELS[wo.priority]}
|
|
||||||
</td>
|
|
||||||
<td className={`text-small text-right ${STATUS_CLASSES[wo.status]}`}>
|
|
||||||
{wo.status?.toUpperCase()}
|
|
||||||
</td>
|
|
||||||
<td className={`text-small ${STATUS_CLASSES[wo.status]}`}>
|
|
||||||
{wo.assignedTo || '-'}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<p className="mt-2 text-center">
|
|
||||||
Summary: Listed a total of {workorders.length} Workorders!
|
|
||||||
<br />EOL =)
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
const RESPONSE_LEVELS = [
|
|
||||||
{ value: 'KEY USER', label: 'Key User' },
|
|
||||||
{ value: 'Helpdesk', label: '1st Level' },
|
|
||||||
{ value: 'Support', label: '2nd Level' },
|
|
||||||
{ value: 'Admin', label: '3rd Level' },
|
|
||||||
{ value: 'FS/FE', label: 'FS/FE' },
|
|
||||||
{ value: '24/7', label: '24/7' },
|
|
||||||
{ value: 'TECH MGMT', label: 'Tech Mgmt' },
|
|
||||||
{ value: 'Backoffice', label: 'Backoffice' },
|
|
||||||
{ value: 'BUSI MGMT', label: 'Busi Mgmt' }
|
|
||||||
]
|
|
||||||
|
|
||||||
export default function ResponseDropdown({ value, onChange }) {
|
|
||||||
return (
|
|
||||||
<div className="dropdown">
|
|
||||||
<button className="btn" style={{ background: 'inherit', color: 'inherit' }}>
|
|
||||||
{value?.toUpperCase() || 'BACKOFFICE'}
|
|
||||||
</button>
|
|
||||||
<div className="dropdown-content">
|
|
||||||
{RESPONSE_LEVELS.map(level => (
|
|
||||||
<span
|
|
||||||
key={level.value}
|
|
||||||
className="dropdown-item"
|
|
||||||
onClick={() => onChange(level.value)}
|
|
||||||
>
|
|
||||||
{level.label}
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,30 +1,19 @@
|
|||||||
const STATUSES = [
|
import { TICKET_STATUSES, statusLabel } from '../lib/ticketStatus'
|
||||||
'Open',
|
|
||||||
'Occupied',
|
|
||||||
'Assigned',
|
|
||||||
'Awaiting',
|
|
||||||
'Added Info',
|
|
||||||
'In Test',
|
|
||||||
'Halted',
|
|
||||||
'Aborted',
|
|
||||||
'Cancelled',
|
|
||||||
'Closed'
|
|
||||||
]
|
|
||||||
|
|
||||||
export default function StatusDropdown({ value, onChange }) {
|
export default function StatusDropdown({ value, onChange }) {
|
||||||
return (
|
return (
|
||||||
<div className="dropdown">
|
<div className="dropdown">
|
||||||
<button className="btn" style={{ background: 'inherit', color: 'inherit' }}>
|
<button className="btn" style={{ background: 'inherit', color: 'inherit' }}>
|
||||||
{value?.toUpperCase() || 'OPEN'}
|
{statusLabel(value)}
|
||||||
</button>
|
</button>
|
||||||
<div className="dropdown-content">
|
<div className="dropdown-content">
|
||||||
{STATUSES.map(status => (
|
{TICKET_STATUSES.map(status => (
|
||||||
<span
|
<span
|
||||||
key={status}
|
key={status.value}
|
||||||
className="dropdown-item"
|
className="dropdown-item"
|
||||||
onClick={() => onChange(status)}
|
onClick={() => onChange(status.value)}
|
||||||
>
|
>
|
||||||
{status}
|
{status.label}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { useState, useEffect, useCallback } from 'react'
|
||||||
|
import { useWebsiteProjects } from '../hooks/useWebsiteProjects'
|
||||||
|
import AssignedProjectCard from './AssignedProjectCard'
|
||||||
|
|
||||||
|
export default function TicketAssignedProjectsPanel({ ticket, refreshKey = 0 }) {
|
||||||
|
const { fetchByTicketId } = useWebsiteProjects()
|
||||||
|
const [projects, setProjects] = useState([])
|
||||||
|
const [loading, setLoading] = useState(true)
|
||||||
|
|
||||||
|
const loadProjects = useCallback(async () => {
|
||||||
|
if (!ticket?.$id) return
|
||||||
|
setLoading(true)
|
||||||
|
try {
|
||||||
|
setProjects(await fetchByTicketId(ticket.$id))
|
||||||
|
} catch {
|
||||||
|
setProjects([])
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}, [ticket?.$id, fetchByTicketId])
|
||||||
|
|
||||||
|
useEffect(() => { loadProjects() }, [loadProjects, refreshKey])
|
||||||
|
|
||||||
|
if (loading || projects.length === 0) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ background: 'rgba(45,55,72,0.5)', borderRadius: 12, padding: 20, border: '1px solid rgba(59,130,246,0.3)', marginTop: 20 }}>
|
||||||
|
<h5 style={{ fontWeight: 'bold', marginBottom: 12 }}>Zugewiesene Projekte ({projects.length})</h5>
|
||||||
|
{projects.map((project) => (
|
||||||
|
<AssignedProjectCard key={project.$id} project={project} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
import { FaChevronDown, FaPlay, FaStop, FaPlus, FaPen } from 'react-icons/fa6'
|
||||||
|
import { formatDistanceToNow, format } from 'date-fns'
|
||||||
|
import { de } from 'date-fns/locale'
|
||||||
|
import StatusDropdown from './StatusDropdown'
|
||||||
|
import UrgentToggle from './UrgentToggle'
|
||||||
|
import EditorDropdown from './EditorDropdown'
|
||||||
|
import CreateWorksheetModal from './CreateWorksheetModal'
|
||||||
|
import WorksheetList from './WorksheetList'
|
||||||
|
import WorksheetTimesModal from './WorksheetTimesModal'
|
||||||
|
import WorksheetSummary from './WorksheetSummary'
|
||||||
|
import TicketAssignedProjectsPanel from './TicketAssignedProjectsPanel'
|
||||||
|
import TicketProjectsModal from './TicketProjectsModal'
|
||||||
|
import TicketRoadmapPanel from './TicketRoadmapPanel'
|
||||||
|
import ProjectContextPanel from './ProjectContextPanel'
|
||||||
|
import InvoicePanel from './InvoicePanel'
|
||||||
|
import Tabs from './ui/Tabs'
|
||||||
|
import Badge from './ui/Badge'
|
||||||
|
import { StatusPill, UrgentPill } from './ui/StatusPill'
|
||||||
|
import { useWorksheets } from '../hooks/useWorksheets'
|
||||||
|
import { useAuth } from '../context/AuthContext'
|
||||||
|
import { databases, DATABASE_ID, COLLECTIONS } from '../lib/appwrite'
|
||||||
|
import { canEditWorksheetTimes } from '../lib/ticketForm'
|
||||||
|
import { ticketTypeLabel } from '../lib/ticketStatus'
|
||||||
|
|
||||||
|
// Projekt / Preview steht mit im Details-Reiter
|
||||||
|
const TABS = [
|
||||||
|
{ id: 'details', label: 'Details' },
|
||||||
|
{ id: 'worksheets', label: 'Arbeitsblätter' },
|
||||||
|
{ id: 'invoices', label: 'Rechnungen' },
|
||||||
|
]
|
||||||
|
|
||||||
|
export default function TicketCard({ ticket, onUpdate }) {
|
||||||
|
const { user, employee } = useAuth()
|
||||||
|
const [expanded, setExpanded] = useState(false)
|
||||||
|
const [activeTab, setActiveTab] = useState('details')
|
||||||
|
const [showCreateWorksheet, setShowCreateWorksheet] = useState(false)
|
||||||
|
const [editTimesWorksheet, setEditTimesWorksheet] = useState(null)
|
||||||
|
const [showProjectsModal, setShowProjectsModal] = useState(false)
|
||||||
|
const [projectsRefreshKey, setProjectsRefreshKey] = useState(0)
|
||||||
|
|
||||||
|
const { worksheets, loading: worksheetsLoading, createWorksheet, updateWorksheet, updateWorksheetTimes, getTotalTime } = useWorksheets(
|
||||||
|
expanded ? ticket.woid : null
|
||||||
|
)
|
||||||
|
|
||||||
|
const createdAt = new Date(ticket.$createdAt || ticket.createdAt)
|
||||||
|
const elapsed = formatDistanceToNow(createdAt, { locale: de, addSuffix: true })
|
||||||
|
|
||||||
|
const stop = (e) => e.stopPropagation()
|
||||||
|
|
||||||
|
// Abo-Stunden: Arbeitszeit vom Stundenguthaben des Wartungs-Abos abziehen oder nicht
|
||||||
|
const handleTogglePlan = (ws) => updateWorksheet(ws.$id, { countsToPlan: !ws.countsToPlan })
|
||||||
|
|
||||||
|
const handleCreateWorksheet = async (worksheetData, currentUser) => {
|
||||||
|
const result = await createWorksheet(worksheetData, currentUser)
|
||||||
|
if (result.success && worksheetData.newStatus !== ticket.status) {
|
||||||
|
await onUpdate(ticket.$id, { status: worksheetData.newStatus })
|
||||||
|
}
|
||||||
|
// Akquise-Automatik: Ergebnis im Worksheet steuert den Kundenstatus
|
||||||
|
if (result.success && ticket.type === 'Akquise' && ticket.customerId) {
|
||||||
|
const outcome = worksheetData.newStatus
|
||||||
|
const next = outcome === 'Zugesagt' ? 'customer' : outcome === 'Abgesagt' ? 'lost' : null
|
||||||
|
if (next) {
|
||||||
|
try {
|
||||||
|
await databases.updateDocument(DATABASE_ID, COLLECTIONS.CUSTOMERS, ticket.customerId, {
|
||||||
|
customerStatus: next,
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
})
|
||||||
|
} catch { /* nicht kritisch */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`tcard ${expanded ? 'open' : ''}`}>
|
||||||
|
<div className="tcard-main" onClick={() => setExpanded((e) => !e)}>
|
||||||
|
<div className="tcard-woid">
|
||||||
|
<span className="id">{ticket.woid || ticket.$id?.slice(-5)}</span>
|
||||||
|
<span className="age">{elapsed}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="tcard-body">
|
||||||
|
<div className="tcard-row1">
|
||||||
|
<span className="tcard-customer">{ticket.customerName || 'Unbekannt'}</span>
|
||||||
|
{ticket.customerLocation && <span className="tcard-loc">· {ticket.customerLocation}</span>}
|
||||||
|
</div>
|
||||||
|
<div className="tcard-topic">{ticket.topic || ticket.title || 'Kein Betreff'}</div>
|
||||||
|
<div className="tcard-badges">
|
||||||
|
<StatusPill status={ticket.status} />
|
||||||
|
<UrgentPill priority={ticket.priority} />
|
||||||
|
{ticket.type && <Badge tone="muted">{ticketTypeLabel(ticket.type)}</Badge>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="tcard-side" onClick={stop}>
|
||||||
|
<div style={{ minWidth: 120 }}>
|
||||||
|
<StatusDropdown value={ticket.status} onChange={(v) => onUpdate(ticket.$id, { status: v })} />
|
||||||
|
</div>
|
||||||
|
<UrgentToggle value={ticket.priority} onChange={(v) => onUpdate(ticket.$id, { priority: v })} />
|
||||||
|
<FaChevronDown className={`chevron ${expanded ? 'up' : ''}`} onClick={() => setExpanded((e) => !e)} style={{ cursor: 'pointer' }} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{expanded && (
|
||||||
|
<div className="tcard-detail">
|
||||||
|
<div className="flex gap-3 wrap" style={{ marginBottom: 16, fontSize: 13 }}>
|
||||||
|
<span className="muted"><FaPlay style={{ color: 'var(--ok)' }} /> {ticket.startDate || format(createdAt, 'dd.MM.yyyy')}</span>
|
||||||
|
<span className="muted"><FaStop style={{ color: 'var(--danger)' }} /> {ticket.deadline || '-'}</span>
|
||||||
|
{ticket.requestedBy && <span className="muted">Angefragt von: {ticket.requestedBy}</span>}
|
||||||
|
<span className="grow" />
|
||||||
|
<div style={{ minWidth: 150 }}><EditorDropdown value={ticket.assignedTo} onChange={(v) => onUpdate(ticket.$id, { assignedTo: v })} /></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Tabs tabs={TABS} active={activeTab} onChange={setActiveTab} />
|
||||||
|
|
||||||
|
<div className="ticket-tab-content">
|
||||||
|
{activeTab === 'details' && (
|
||||||
|
<div>
|
||||||
|
<div className="ui-card pad" style={{ marginBottom: 16 }}>
|
||||||
|
<h5 style={{ fontWeight: 700, marginBottom: 12 }}>Ticket-Beschreibung</h5>
|
||||||
|
<p style={{ whiteSpace: 'pre-wrap', color: 'var(--text-muted)', lineHeight: 1.7, margin: 0 }}>
|
||||||
|
{ticket.details || 'Keine Details vorhanden.'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<ProjectContextPanel ticket={ticket} />
|
||||||
|
<TicketRoadmapPanel ticket={ticket} />
|
||||||
|
<button className="ui-btn ui-btn-primary" style={{ marginBottom: 12 }} onClick={() => setShowProjectsModal(true)}>
|
||||||
|
<FaPen /> Projekte zuweisen / bearbeiten
|
||||||
|
</button>
|
||||||
|
<TicketAssignedProjectsPanel ticket={ticket} refreshKey={projectsRefreshKey} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeTab === 'worksheets' && (
|
||||||
|
<div>
|
||||||
|
<div className="flex gap-2" style={{ marginBottom: 16 }}>
|
||||||
|
<button className="ui-btn ui-btn-primary grow" onClick={() => setShowCreateWorksheet(true)}>
|
||||||
|
<FaPlus /> Arbeitsblatt hinzufügen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<WorksheetSummary worksheets={worksheets} />
|
||||||
|
<WorksheetList
|
||||||
|
worksheets={worksheets}
|
||||||
|
totalTime={getTotalTime()}
|
||||||
|
loading={worksheetsLoading}
|
||||||
|
canEditTimes={(ws) => canEditWorksheetTimes(ws, user, employee)}
|
||||||
|
onEditTimes={setEditTimesWorksheet}
|
||||||
|
onTogglePlan={handleTogglePlan}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeTab === 'invoices' && <InvoicePanel ticket={ticket} />}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<CreateWorksheetModal isOpen={showCreateWorksheet} onClose={() => setShowCreateWorksheet(false)} workorder={ticket} onCreate={handleCreateWorksheet} />
|
||||||
|
{editTimesWorksheet && (
|
||||||
|
<WorksheetTimesModal
|
||||||
|
worksheet={editTimesWorksheet}
|
||||||
|
onClose={() => setEditTimesWorksheet(null)}
|
||||||
|
onSave={(ws, times) => updateWorksheetTimes(ws, times, user, employee)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<TicketProjectsModal isOpen={showProjectsModal} onClose={() => setShowProjectsModal(false)} ticket={ticket} onUpdated={() => setProjectsRefreshKey((k) => k + 1)} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,177 +0,0 @@
|
|||||||
import { useState } from 'react'
|
|
||||||
import { FaCaretDown } from 'react-icons/fa6'
|
|
||||||
|
|
||||||
const TICKET_TYPES = [
|
|
||||||
'Home Office', 'Holidays', 'Trip', 'Supportrequest', 'Change Request',
|
|
||||||
'Maintenance', 'Project', 'Controlling', 'Development', 'Documentation',
|
|
||||||
'Meeting/Conference', 'IT Management', 'IT Security', 'Procurement',
|
|
||||||
'Rollout', 'Emergency Call', 'Other Services'
|
|
||||||
]
|
|
||||||
|
|
||||||
const SYSTEMS = [
|
|
||||||
'Account View', 'Client', 'Cofano', 'Credentials', 'Diamant', 'Docuware',
|
|
||||||
'EDI', 'eMail', 'Employee', 'Invoice', 'LBase', 'Medical Office', 'Network',
|
|
||||||
'O365', 'PDF Viewer', 'Printer', 'Reports', 'Server', 'Time Tracking',
|
|
||||||
'TK', 'TOS', 'Vivendi NG', 'VGM', '(W)LAN', '(W)WAN', 'WOMS', 'n/a'
|
|
||||||
]
|
|
||||||
|
|
||||||
const STATUSES = [
|
|
||||||
'Open', 'Occupied', 'Assigned', 'Awaiting', 'Added Info',
|
|
||||||
'In Test', 'Halted', 'Aborted', 'Cancelled', 'Closed'
|
|
||||||
]
|
|
||||||
|
|
||||||
const PRIORITIES = [
|
|
||||||
{ value: 0, label: 'None' },
|
|
||||||
{ value: 1, label: 'Low' },
|
|
||||||
{ value: 2, label: 'Medium' },
|
|
||||||
{ value: 3, label: 'High' },
|
|
||||||
{ value: 4, label: 'Critical' }
|
|
||||||
]
|
|
||||||
|
|
||||||
export default function TicketFilters({ filters, onChange, onApply }) {
|
|
||||||
const [localFilters, setLocalFilters] = useState(filters)
|
|
||||||
|
|
||||||
const handleCheckboxChange = (category, value) => {
|
|
||||||
const current = localFilters[category] || []
|
|
||||||
const updated = current.includes(value)
|
|
||||||
? current.filter(v => v !== value)
|
|
||||||
: [...current, value]
|
|
||||||
setLocalFilters({ ...localFilters, [category]: updated })
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleInputChange = (field, value) => {
|
|
||||||
setLocalFilters({ ...localFilters, [field]: value })
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleApply = () => {
|
|
||||||
onChange(localFilters)
|
|
||||||
onApply()
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<tr className="bg-dark-grey text-white">
|
|
||||||
<td className="text-center">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="WOID"
|
|
||||||
className="form-control"
|
|
||||||
style={{ width: '80px', background: '#616161', color: '#fff', border: 'none' }}
|
|
||||||
value={localFilters.woid || ''}
|
|
||||||
onChange={(e) => handleInputChange('woid', e.target.value)}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="Created"
|
|
||||||
className="form-control"
|
|
||||||
style={{ width: '100px', background: '#616161', color: '#fff', border: 'none' }}
|
|
||||||
value={localFilters.createdDate || ''}
|
|
||||||
onChange={(e) => handleInputChange('createdDate', e.target.value)}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div className="dropdown">
|
|
||||||
<button className="btn btn-dark">
|
|
||||||
Type / Location <FaCaretDown />
|
|
||||||
</button>
|
|
||||||
<div className="dropdown-content" style={{ maxHeight: '300px', overflowY: 'auto' }}>
|
|
||||||
{TICKET_TYPES.map(type => (
|
|
||||||
<label key={type} className="dropdown-item" style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={(localFilters.type || []).includes(type)}
|
|
||||||
onChange={() => handleCheckboxChange('type', type)}
|
|
||||||
/>
|
|
||||||
{type}
|
|
||||||
</label>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div className="dropdown">
|
|
||||||
<button className="btn btn-dark">
|
|
||||||
System <FaCaretDown />
|
|
||||||
</button>
|
|
||||||
<div className="dropdown-content" style={{ maxHeight: '300px', overflowY: 'auto' }}>
|
|
||||||
{SYSTEMS.map(system => (
|
|
||||||
<label key={system} className="dropdown-item" style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={(localFilters.system || []).includes(system)}
|
|
||||||
onChange={() => handleCheckboxChange('system', system)}
|
|
||||||
/>
|
|
||||||
{system}
|
|
||||||
</label>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="Customer or Location"
|
|
||||||
className="form-control"
|
|
||||||
style={{ width: '150px', background: '#616161', color: '#fff', border: 'none' }}
|
|
||||||
value={localFilters.customer || ''}
|
|
||||||
onChange={(e) => handleInputChange('customer', e.target.value)}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="User or Topic"
|
|
||||||
className="form-control"
|
|
||||||
style={{ width: '150px', background: '#616161', color: '#fff', border: 'none' }}
|
|
||||||
value={localFilters.userTopic || ''}
|
|
||||||
onChange={(e) => handleInputChange('userTopic', e.target.value)}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div className="dropdown">
|
|
||||||
<button className="btn btn-dark">
|
|
||||||
Status <FaCaretDown />
|
|
||||||
</button>
|
|
||||||
<div className="dropdown-content">
|
|
||||||
{STATUSES.map(status => (
|
|
||||||
<label key={status} className="dropdown-item" style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={(localFilters.status || []).includes(status)}
|
|
||||||
onChange={() => handleCheckboxChange('status', status)}
|
|
||||||
/>
|
|
||||||
{status}
|
|
||||||
</label>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div className="dropdown">
|
|
||||||
<button className="btn btn-dark">
|
|
||||||
Priority <FaCaretDown />
|
|
||||||
</button>
|
|
||||||
<div className="dropdown-content">
|
|
||||||
{PRIORITIES.map(p => (
|
|
||||||
<label key={p.value} className="dropdown-item" style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={(localFilters.priority || []).includes(p.value)}
|
|
||||||
onChange={() => handleCheckboxChange('priority', p.value)}
|
|
||||||
/>
|
|
||||||
{p.label}
|
|
||||||
</label>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td className="text-center">Approval</td>
|
|
||||||
<td className="text-center">
|
|
||||||
<button className="btn btn-green" onClick={handleApply}>
|
|
||||||
Apply!
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
import { useState, useEffect, useCallback } from 'react'
|
||||||
|
import { FaPlus, FaTimes } from 'react-icons/fa'
|
||||||
|
import { useWebsiteProjects } from '../hooks/useWebsiteProjects'
|
||||||
|
import { createProjectFromTemplate } from '../lib/projectAdminApi'
|
||||||
|
import AssignedProjectCard from './AssignedProjectCard'
|
||||||
|
|
||||||
|
function slugify(v) {
|
||||||
|
return String(v || '').toLowerCase().replace(/[^a-z0-9-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TicketProjectsModal({ isOpen, onClose, ticket, onUpdated }) {
|
||||||
|
const { fetchAllProjects, fetchByTicketId, assignProjects, unassignProject } = useWebsiteProjects()
|
||||||
|
const [assigned, setAssigned] = useState([])
|
||||||
|
const [available, setAvailable] = useState([])
|
||||||
|
const [selectedIds, setSelectedIds] = useState([])
|
||||||
|
const [loading, setLoading] = useState(true)
|
||||||
|
const [actionLoading, setActionLoading] = useState(false)
|
||||||
|
const [error, setError] = useState('')
|
||||||
|
const [createForm, setCreateForm] = useState({ displayName: '', subdomain: '', repoName: '' })
|
||||||
|
|
||||||
|
const loadProjects = useCallback(async () => {
|
||||||
|
if (!ticket?.$id) return
|
||||||
|
setLoading(true)
|
||||||
|
setError('')
|
||||||
|
try {
|
||||||
|
const [all, mine] = await Promise.all([fetchAllProjects(), fetchByTicketId(ticket.$id)])
|
||||||
|
setAssigned(mine)
|
||||||
|
const assignedIds = new Set(mine.map((p) => p.$id))
|
||||||
|
setAvailable((all || []).filter((p) => p.repoFullName && !assignedIds.has(p.$id)))
|
||||||
|
} catch (err) {
|
||||||
|
setError(err.message)
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}, [ticket, fetchAllProjects, fetchByTicketId])
|
||||||
|
|
||||||
|
useEffect(() => { if (isOpen) loadProjects() }, [isOpen, loadProjects])
|
||||||
|
|
||||||
|
if (!isOpen) return null
|
||||||
|
|
||||||
|
const handleAssign = async () => {
|
||||||
|
if (!selectedIds.length) return
|
||||||
|
setActionLoading(true)
|
||||||
|
const r = await assignProjects(selectedIds, { customerId: ticket.customerId || '', ticketId: ticket.$id })
|
||||||
|
setActionLoading(false)
|
||||||
|
if (r.success) { setSelectedIds([]); await loadProjects(); onUpdated?.() } else setError(r.error)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCreate = async (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
setActionLoading(true)
|
||||||
|
setError('')
|
||||||
|
try {
|
||||||
|
await createProjectFromTemplate({
|
||||||
|
repoName: createForm.repoName || createForm.subdomain,
|
||||||
|
displayName: createForm.displayName,
|
||||||
|
subdomain: slugify(createForm.subdomain),
|
||||||
|
customerId: ticket.customerId || '',
|
||||||
|
ticketId: ticket.$id,
|
||||||
|
})
|
||||||
|
setCreateForm({ displayName: '', subdomain: '', repoName: '' })
|
||||||
|
await loadProjects()
|
||||||
|
onUpdated?.()
|
||||||
|
} catch (err) {
|
||||||
|
setError(err.message)
|
||||||
|
} finally {
|
||||||
|
setActionLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleUnassign = async (projectId) => {
|
||||||
|
setActionLoading(true)
|
||||||
|
await unassignProject(projectId)
|
||||||
|
setActionLoading(false)
|
||||||
|
await loadProjects()
|
||||||
|
onUpdated?.()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="overlay ticket-projects-overlay">
|
||||||
|
<span className="overlay-close" onClick={onClose}>{'\u00d7'}</span>
|
||||||
|
<div className="overlay-content ticket-projects-modal">
|
||||||
|
<div className="ticket-projects-modal-header">
|
||||||
|
<h2>Projekte bearbeiten</h2>
|
||||||
|
<button type="button" className="btn btn-sm" onClick={onClose} aria-label="Schliessen"><FaTimes /></button>
|
||||||
|
</div>
|
||||||
|
{error && <div className="bg-red text-white p-2 mb-2">{error}</div>}
|
||||||
|
{loading ? <p className="text-grey">Projekte werden geladen...</p> : (
|
||||||
|
<div className="ticket-projects-modal-grid">
|
||||||
|
<section className="ticket-projects-modal-section">
|
||||||
|
<h6>Zugewiesen</h6>
|
||||||
|
{assigned.length === 0 ? <p className="text-grey">Keine Projekte zugewiesen.</p> : assigned.map((p) => (
|
||||||
|
<AssignedProjectCard key={p.$id} project={p} showActions onUnassign={handleUnassign} />
|
||||||
|
))}
|
||||||
|
</section>
|
||||||
|
<div className="ticket-projects-modal-side">
|
||||||
|
<section className="ticket-projects-modal-section">
|
||||||
|
<h6>Verfuegbar zuweisen</h6>
|
||||||
|
{available.length === 0 ? <p className="text-grey">Keine weiteren Projekte verfuegbar.</p> : (
|
||||||
|
<div className="ticket-projects-available-list">
|
||||||
|
{available.map((p) => (
|
||||||
|
<label key={p.$id} className="ticket-projects-available-item">
|
||||||
|
<input type="checkbox" checked={selectedIds.includes(p.$id)} onChange={() => setSelectedIds((s) => s.includes(p.$id) ? s.filter((x) => x !== p.$id) : [...s, p.$id])} />
|
||||||
|
<span>{p.projectName} ({p.subdomain || p.repoFullName})</span>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<button type="button" className="btn btn-teal mt-2" disabled={!selectedIds.length || actionLoading} onClick={handleAssign}>Zuweisen</button>
|
||||||
|
</section>
|
||||||
|
<section className="ticket-projects-modal-section">
|
||||||
|
<h6><FaPlus /> Neues Projekt</h6>
|
||||||
|
<form onSubmit={handleCreate} className="ticket-projects-create-form">
|
||||||
|
<input className="form-control" placeholder="Anzeigename" value={createForm.displayName} onChange={(e) => setCreateForm((f) => ({ ...f, displayName: e.target.value }))} required />
|
||||||
|
<input className="form-control" placeholder="Subdomain" value={createForm.subdomain} onChange={(e) => setCreateForm((f) => ({ ...f, subdomain: e.target.value, repoName: f.repoName || slugify(e.target.value) }))} required />
|
||||||
|
<button type="submit" className="btn btn-dark" disabled={actionLoading}>{actionLoading ? 'Wird angelegt...' : 'Anlegen & zuweisen'}</button>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
import { IconRoute, IconAlertTriangle } from '@tabler/icons-react'
|
||||||
|
import RoadmapProgressBar from './roadmap/RoadmapProgressBar'
|
||||||
|
import {
|
||||||
|
fetchRoadmapByTicketId,
|
||||||
|
fetchRoadmapNodes,
|
||||||
|
computeProgress,
|
||||||
|
isNodeOverdue,
|
||||||
|
NODE_KIND,
|
||||||
|
NODE_STATUS,
|
||||||
|
ROADMAP_STATUS,
|
||||||
|
} from '../lib/roadmaps'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Roadmap-Tracking im Projektticket: Fortschritt, Mini-Uebersicht und Link zum Board.
|
||||||
|
* Rendert nichts, wenn zum Ticket keine Roadmap existiert.
|
||||||
|
*/
|
||||||
|
export default function TicketRoadmapPanel({ ticket }) {
|
||||||
|
const [roadmap, setRoadmap] = useState(null)
|
||||||
|
const [nodes, setNodes] = useState([])
|
||||||
|
const [loaded, setLoaded] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let cancelled = false
|
||||||
|
async function load() {
|
||||||
|
try {
|
||||||
|
const doc = await fetchRoadmapByTicketId(ticket.$id)
|
||||||
|
if (cancelled) return
|
||||||
|
setRoadmap(doc)
|
||||||
|
if (doc) setNodes(await fetchRoadmapNodes(doc.$id))
|
||||||
|
} catch {
|
||||||
|
/* Panel ist optional */
|
||||||
|
} finally {
|
||||||
|
if (!cancelled) setLoaded(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
load()
|
||||||
|
return () => { cancelled = true }
|
||||||
|
}, [ticket.$id])
|
||||||
|
|
||||||
|
if (!loaded || !roadmap) return null
|
||||||
|
|
||||||
|
const progress = computeProgress(nodes)
|
||||||
|
const relevant = nodes.filter((n) => n.kind !== NODE_KIND.START)
|
||||||
|
const counts = {
|
||||||
|
open: relevant.filter((n) => (n.status || NODE_STATUS.OPEN) === NODE_STATUS.OPEN).length,
|
||||||
|
inProgress: relevant.filter((n) => n.status === NODE_STATUS.IN_PROGRESS).length,
|
||||||
|
done: relevant.filter((n) => n.status === NODE_STATUS.DONE).length,
|
||||||
|
}
|
||||||
|
const nextDue = relevant
|
||||||
|
.filter((n) => n.deadline && n.status !== NODE_STATUS.DONE)
|
||||||
|
.sort((a, b) => a.deadline.localeCompare(b.deadline))
|
||||||
|
.slice(0, 3)
|
||||||
|
const closedButActive = ticket.status === 'Closed' && roadmap.status === ROADMAP_STATUS.ACTIVE
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="ui-card pad" style={{ marginBottom: 16 }}>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12, flexWrap: 'wrap', marginBottom: 10 }}>
|
||||||
|
<h5 style={{ fontWeight: 700, margin: 0, display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||||
|
<IconRoute size={18} /> Roadmap: {roadmap.title}
|
||||||
|
</h5>
|
||||||
|
<Link to={`/roadmaps/${roadmap.$id}`} className="ui-btn">Zur Roadmap</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<RoadmapProgressBar progress={progress} />
|
||||||
|
|
||||||
|
<div className="muted" style={{ display: 'flex', gap: 14, fontSize: 13, marginTop: 10, flexWrap: 'wrap' }}>
|
||||||
|
<span>Offen: {counts.open}</span>
|
||||||
|
<span>In Arbeit: {counts.inProgress}</span>
|
||||||
|
<span>Fertig: {counts.done}</span>
|
||||||
|
{roadmap.status === ROADMAP_STATUS.COMPLETED && <span style={{ color: '#34d399' }}>Plan abgeschlossen</span>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{nextDue.length > 0 && (
|
||||||
|
<div style={{ marginTop: 10, fontSize: 13 }}>
|
||||||
|
<span className="faint">Naechste Deadlines: </span>
|
||||||
|
{nextDue.map((n, i) => (
|
||||||
|
<span key={n.$id} style={isNodeOverdue(n) ? { color: 'var(--danger, #ef4444)', fontWeight: 700 } : undefined}>
|
||||||
|
{i > 0 && ' · '}{n.title} ({n.deadline.split('-').reverse().join('.')})
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{closedButActive && (
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 12, fontSize: 13, color: '#fbbf24' }}>
|
||||||
|
<IconAlertTriangle size={16} style={{ flexShrink: 0 }} />
|
||||||
|
Ticket ist geschlossen, aber der Plan ist erst zu {progress.percent}% fertig.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,171 +0,0 @@
|
|||||||
import { useState } from 'react'
|
|
||||||
import { FaLock, FaLockOpen, FaPlay, FaStop, FaTruck, FaSackDollar, FaUserGear } from 'react-icons/fa6'
|
|
||||||
import { formatDistanceToNow, format } from 'date-fns'
|
|
||||||
import { de } from 'date-fns/locale'
|
|
||||||
import StatusDropdown from './StatusDropdown'
|
|
||||||
import PriorityDropdown from './PriorityDropdown'
|
|
||||||
import EditorDropdown from './EditorDropdown'
|
|
||||||
import ResponseDropdown from './ResponseDropdown'
|
|
||||||
|
|
||||||
const PRIORITY_CLASSES = {
|
|
||||||
0: 'priority-none',
|
|
||||||
1: 'priority-low',
|
|
||||||
2: 'priority-medium',
|
|
||||||
3: 'priority-high',
|
|
||||||
4: 'priority-critical'
|
|
||||||
}
|
|
||||||
|
|
||||||
const PRIORITY_LABELS = {
|
|
||||||
0: 'NONE',
|
|
||||||
1: 'LOW',
|
|
||||||
2: 'MEDIUM',
|
|
||||||
3: 'HIGH',
|
|
||||||
4: 'CRITICAL'
|
|
||||||
}
|
|
||||||
|
|
||||||
const STATUS_CLASSES = {
|
|
||||||
'Open': 'status-open',
|
|
||||||
'Occupied': 'status-occupied',
|
|
||||||
'Assigned': 'status-assigned',
|
|
||||||
'Awaiting': 'status-awaiting',
|
|
||||||
'Closed': 'status-closed'
|
|
||||||
}
|
|
||||||
|
|
||||||
const APPROVAL_ICONS = {
|
|
||||||
'pending': FaUserGear,
|
|
||||||
'approved': FaSackDollar,
|
|
||||||
'quote': FaSackDollar,
|
|
||||||
'shipping': FaTruck
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function TicketRow({ ticket, onUpdate, onExpand }) {
|
|
||||||
const [expanded, setExpanded] = useState(false)
|
|
||||||
const [locked, setLocked] = useState(true)
|
|
||||||
|
|
||||||
const createdAt = new Date(ticket.$createdAt || ticket.createdAt)
|
|
||||||
const elapsed = formatDistanceToNow(createdAt, { locale: de })
|
|
||||||
|
|
||||||
const handleStatusChange = (newStatus) => {
|
|
||||||
onUpdate(ticket.$id, { status: newStatus })
|
|
||||||
}
|
|
||||||
|
|
||||||
const handlePriorityChange = (newPriority) => {
|
|
||||||
onUpdate(ticket.$id, { priority: newPriority })
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleEditorChange = (newEditor) => {
|
|
||||||
onUpdate(ticket.$id, { assignedTo: newEditor })
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleResponseChange = (newResponse) => {
|
|
||||||
onUpdate(ticket.$id, { responseLevel: newResponse })
|
|
||||||
}
|
|
||||||
|
|
||||||
const toggleLock = () => {
|
|
||||||
if (locked) {
|
|
||||||
setExpanded(true)
|
|
||||||
onExpand?.(ticket.$id)
|
|
||||||
} else {
|
|
||||||
setExpanded(false)
|
|
||||||
}
|
|
||||||
setLocked(!locked)
|
|
||||||
}
|
|
||||||
|
|
||||||
const ApprovalIcon = APPROVAL_ICONS[ticket.approvalStatus] || FaUserGear
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<tr className="ticket-row">
|
|
||||||
<td className="ticket-id" rowSpan={2}>
|
|
||||||
<div>{ticket.woid || ticket.$id?.slice(-5)}</div>
|
|
||||||
<div className="ticket-time">{elapsed}</div>
|
|
||||||
</td>
|
|
||||||
<td className="ticket-info" rowSpan={2}>
|
|
||||||
<strong>{format(createdAt, 'dd.MM.yyyy')}</strong>
|
|
||||||
</td>
|
|
||||||
<td className="ticket-info" rowSpan={2}>
|
|
||||||
<strong>{ticket.type}</strong>
|
|
||||||
<br />
|
|
||||||
{ticket.serviceType || 'Remote'}
|
|
||||||
<br />
|
|
||||||
<FaPlay className="text-green" /> {ticket.startDate || format(createdAt, 'dd.MM.yyyy')}
|
|
||||||
<br />
|
|
||||||
<FaStop className="text-red" /> {ticket.deadline || '-'}
|
|
||||||
</td>
|
|
||||||
<td className="ticket-info" rowSpan={2}>
|
|
||||||
<strong>{ticket.systemType || 'n/a'}</strong>
|
|
||||||
</td>
|
|
||||||
<td rowSpan={2}>
|
|
||||||
<strong>{ticket.customerName || 'Unknown'}</strong>
|
|
||||||
<br />
|
|
||||||
{ticket.customerLocation || ''}
|
|
||||||
</td>
|
|
||||||
<td rowSpan={2}>
|
|
||||||
<strong>{ticket.requestedBy || '-'}</strong>
|
|
||||||
<br />
|
|
||||||
{ticket.requestedFor && <>Requested for: {ticket.requestedFor}<br /></>}
|
|
||||||
{ticket.topic}
|
|
||||||
</td>
|
|
||||||
<td className={`text-center ${STATUS_CLASSES[ticket.status] || 'status-open'}`}>
|
|
||||||
<StatusDropdown
|
|
||||||
value={ticket.status}
|
|
||||||
onChange={handleStatusChange}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
<td className={`text-center ${PRIORITY_CLASSES[ticket.priority] || 'priority-low'}`}>
|
|
||||||
<PriorityDropdown
|
|
||||||
value={ticket.priority}
|
|
||||||
onChange={handlePriorityChange}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
<td
|
|
||||||
className={`text-center ${ticket.approvalStatus === 'approved' ? 'bg-green' : 'bg-yellow'}`}
|
|
||||||
rowSpan={2}
|
|
||||||
style={{ verticalAlign: 'middle' }}
|
|
||||||
>
|
|
||||||
<ApprovalIcon size={24} />
|
|
||||||
</td>
|
|
||||||
<td
|
|
||||||
className="bg-dark-grey text-center text-white"
|
|
||||||
rowSpan={2}
|
|
||||||
style={{ verticalAlign: 'middle', cursor: 'pointer' }}
|
|
||||||
onClick={toggleLock}
|
|
||||||
>
|
|
||||||
{locked ? <FaLock size={24} /> : <FaLockOpen size={24} />}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr className="ticket-row">
|
|
||||||
<td className={`text-center ${STATUS_CLASSES[ticket.status] || 'status-open'}`}>
|
|
||||||
<EditorDropdown
|
|
||||||
value={ticket.assignedTo}
|
|
||||||
onChange={handleEditorChange}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
<td className={`text-center ${PRIORITY_CLASSES[ticket.priority] || 'priority-low'}`}>
|
|
||||||
<ResponseDropdown
|
|
||||||
value={ticket.responseLevel}
|
|
||||||
onChange={handleResponseChange}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{expanded && (
|
|
||||||
<>
|
|
||||||
<tr>
|
|
||||||
<td colSpan={10} className="p-2">
|
|
||||||
<div className="card">
|
|
||||||
<div className="card-header">Details - WOID {ticket.woid || ticket.$id}</div>
|
|
||||||
<div className="card-body">
|
|
||||||
<p><strong>Beschreibung:</strong></p>
|
|
||||||
<p>{ticket.details || 'Keine Details vorhanden.'}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<tr className="spacer">
|
|
||||||
<td colSpan={10} style={{ height: '8px', background: '#fff' }}></td>
|
|
||||||
</tr>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { isUrgent, priorityFor } from '../lib/ticketStatus'
|
||||||
|
|
||||||
|
// Ein Klick markiert ein Ticket als dringend oder wieder als normal
|
||||||
|
export default function UrgentToggle({ value, onChange }) {
|
||||||
|
const urgent = isUrgent(value)
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`urgent-toggle${urgent ? ' on' : ''}`}
|
||||||
|
aria-pressed={urgent}
|
||||||
|
title={urgent ? 'Nicht mehr dringend' : 'Als dringend markieren'}
|
||||||
|
onClick={() => onChange(priorityFor(!urgent))}
|
||||||
|
>
|
||||||
|
Dringend
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,306 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
import { FaClock, FaUser, FaExchangeAlt, FaComment, FaChevronDown, FaChevronUp, FaPen, FaCheck } from 'react-icons/fa'
|
||||||
|
import { serviceTypeLabel, needsTimeEntry, worksheetTone } from '../lib/ticketForm'
|
||||||
|
import { statusLabel } from '../lib/ticketStatus'
|
||||||
|
|
||||||
|
// Kopffarbe je Zustand (worksheetTone): orange = Git-Zeit fehlt, gruen = Git-Zeit eingetragen
|
||||||
|
const TONES = {
|
||||||
|
'git-open': { border: '#f97316', base: 'linear-gradient(135deg, #f97316 0%, #ea580c 100%)', hover: 'linear-gradient(135deg, #ea580c 0%, #c2410c 100%)' },
|
||||||
|
'git-done': { border: '#10b981', base: 'linear-gradient(135deg, #10b981 0%, #059669 100%)', hover: 'linear-gradient(135deg, #059669 0%, #047857 100%)' },
|
||||||
|
comment: { border: '#3b82f6', base: 'linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)', hover: 'linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%)' },
|
||||||
|
work: { border: '#4a5568', base: 'linear-gradient(135deg, #4a5568 0%, #2d3748 100%)', hover: 'linear-gradient(135deg, #2d3748 0%, #1a202c 100%)' },
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hinweise im farbigen Kopf: mit der grauen Badge-Schrift waeren sie kaum lesbar
|
||||||
|
const headerBadgeStyle = { background: 'rgba(255,255,255,0.3)', color: 'white', marginLeft: '8px' }
|
||||||
|
|
||||||
|
const headerButtonStyle = {
|
||||||
|
background: 'rgba(255,255,255,0.25)',
|
||||||
|
color: 'white',
|
||||||
|
border: '1px solid rgba(255,255,255,0.4)',
|
||||||
|
borderRadius: '6px',
|
||||||
|
padding: '2px 10px',
|
||||||
|
fontSize: '0.8rem',
|
||||||
|
marginRight: '8px',
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function WorksheetList({ worksheets, totalTime, loading, onTogglePlan, canEditTimes, onEditTimes }) {
|
||||||
|
const [expandedWorksheets, setExpandedWorksheets] = useState({})
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="text-center p-4">
|
||||||
|
<div className="spinner-border" role="status">
|
||||||
|
<span className="visually-hidden">Lädt …</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!worksheets || worksheets.length === 0) {
|
||||||
|
return (
|
||||||
|
<div className="alert alert-info" role="alert">
|
||||||
|
<FaComment className="me-2" />
|
||||||
|
Noch keine Arbeitsblätter. Leg das erste mit „Arbeitsblatt hinzufügen“ an.
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatTime = (minutes) => {
|
||||||
|
if (!minutes || minutes === 0) return '-'
|
||||||
|
const hours = Math.floor(minutes / 60)
|
||||||
|
const mins = minutes % 60
|
||||||
|
return hours > 0 ? `${hours}h ${mins}min` : `${mins}min`
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatDateTime = (date, time) => {
|
||||||
|
if (!time) return date
|
||||||
|
const hours = time.substring(0, 2)
|
||||||
|
const mins = time.substring(2, 4)
|
||||||
|
return `${date} ${hours}:${mins}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleWorksheet = (wsid) => {
|
||||||
|
setExpandedWorksheets(prev => ({
|
||||||
|
...prev,
|
||||||
|
[wsid]: !prev[wsid]
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="worksheet-list">
|
||||||
|
{/* Worksheet-Einträge */}
|
||||||
|
<div className="timeline">
|
||||||
|
{worksheets.map((ws, index) => {
|
||||||
|
const isExpanded = expandedWorksheets[ws.wsid] || false
|
||||||
|
const isGit = ws.serviceType === 'GIT'
|
||||||
|
const tone = worksheetTone(ws)
|
||||||
|
const colors = TONES[tone]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={ws.$id} className="timeline-item mb-4" style={{
|
||||||
|
animation: `fadeIn 0.5s ease-in-out ${index * 0.1}s backwards`
|
||||||
|
}}>
|
||||||
|
<div className="card border-0 shadow-sm overflow-hidden" style={{
|
||||||
|
borderLeft: `4px solid ${colors.border}`,
|
||||||
|
borderRadius: '8px',
|
||||||
|
transition: 'transform 0.2s ease, box-shadow 0.2s ease'
|
||||||
|
}} onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.transform = 'translateY(-2px)'
|
||||||
|
e.currentTarget.style.boxShadow = '0 8px 16px rgba(0,0,0,0.1)'
|
||||||
|
}} onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.transform = 'translateY(0)'
|
||||||
|
e.currentTarget.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)'
|
||||||
|
}}>
|
||||||
|
{/* Header - Immer sichtbar, klickbar */}
|
||||||
|
<div
|
||||||
|
className="card-header d-flex justify-content-between align-items-center py-3"
|
||||||
|
onClick={() => toggleWorksheet(ws.wsid)}
|
||||||
|
style={{
|
||||||
|
background: colors.base,
|
||||||
|
color: 'white',
|
||||||
|
border: 'none',
|
||||||
|
cursor: 'pointer',
|
||||||
|
userSelect: 'none',
|
||||||
|
transition: 'background 0.2s ease'
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.background = colors.hover
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.background = colors.base
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="d-flex align-items-center gap-3">
|
||||||
|
{isExpanded ? (
|
||||||
|
<FaChevronUp style={{ fontSize: '0.9rem', opacity: 0.8 }} />
|
||||||
|
) : (
|
||||||
|
<FaChevronDown style={{ fontSize: '0.9rem', opacity: 0.8 }} />
|
||||||
|
)}
|
||||||
|
<div>
|
||||||
|
<strong className="fs-6">WSID {ws.wsid}</strong>
|
||||||
|
{ws.isComment && !isGit && (
|
||||||
|
<span className="badge ms-2" style={headerBadgeStyle}>
|
||||||
|
<FaComment className="me-1" /> Kommentar
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{tone === 'git-open' && (
|
||||||
|
<span className="badge ms-2" style={headerBadgeStyle}>
|
||||||
|
<FaClock className="me-1" /> Zeit offen
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{tone === 'git-done' && (
|
||||||
|
<span className="badge ms-2" style={headerBadgeStyle}>
|
||||||
|
<FaCheck className="me-1" /> Zeit eingetragen
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{ws.countsToPlan && !ws.isComment && (
|
||||||
|
<span className="badge ms-2" title="Wird vom Stundenguthaben des Wartungs-Abos abgezogen" style={{ ...headerBadgeStyle, background: '#9d3dfe' }}>
|
||||||
|
Abo-Stunden
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{/* Collapsed: Mitarbeiter & Zeit im Header */}
|
||||||
|
{!isExpanded && (
|
||||||
|
<div className="d-flex align-items-center gap-3 ms-3">
|
||||||
|
<div className="d-flex align-items-center">
|
||||||
|
<FaUser style={{ fontSize: '0.9rem', marginRight: '0.5rem' }} />
|
||||||
|
<span style={{ fontSize: '0.9rem' }}>{ws.employeeName}</span>
|
||||||
|
</div>
|
||||||
|
{!ws.isComment && (
|
||||||
|
<div className="d-flex align-items-center">
|
||||||
|
<FaClock style={{ fontSize: '0.9rem', marginRight: '0.5rem' }} />
|
||||||
|
<span style={{ fontSize: '0.9rem' }}>{formatTime(ws.totalTime)}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<span className="badge" style={{
|
||||||
|
background: 'rgba(255,255,255,0.2)',
|
||||||
|
color: 'white',
|
||||||
|
fontSize: '0.8rem'
|
||||||
|
}}>{serviceTypeLabel(ws.serviceType)}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="d-flex align-items-center gap-2">
|
||||||
|
{onEditTimes && canEditTimes?.(ws) && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-sm"
|
||||||
|
title="Beginn, Ende und Arbeitszeit ändern"
|
||||||
|
style={headerButtonStyle}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
onEditTimes(ws)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{needsTimeEntry(ws)
|
||||||
|
? <><FaClock className="me-1" /> Zeit nachtragen</>
|
||||||
|
: <><FaPen className="me-1" /> Zeiten bearbeiten</>}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{onTogglePlan && !ws.isComment && Number(ws.totalTime) > 0 && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-sm"
|
||||||
|
title={ws.countsToPlan ? 'Nicht mehr vom Abo-Stundenkonto abziehen' : 'Vom Abo-Stundenkonto des Kunden abziehen'}
|
||||||
|
style={{
|
||||||
|
background: ws.countsToPlan ? '#9d3dfe' : 'rgba(255,255,255,0.15)',
|
||||||
|
color: 'white',
|
||||||
|
border: '1px solid rgba(255,255,255,0.4)',
|
||||||
|
borderRadius: '6px',
|
||||||
|
padding: '2px 10px',
|
||||||
|
fontSize: '0.8rem',
|
||||||
|
marginRight: '8px',
|
||||||
|
}}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
onTogglePlan(ws)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{ws.countsToPlan ? '✓ Abo' : '+ Abo'}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<small style={{ opacity: 0.9 }}>
|
||||||
|
{formatDateTime(ws.startDate, ws.startTime) || formatDateTime(ws.endDate, ws.endTime)}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Body - Nur wenn expanded */}
|
||||||
|
{isExpanded && (
|
||||||
|
<div
|
||||||
|
className="card-body p-4"
|
||||||
|
style={{
|
||||||
|
animation: 'slideDown 0.3s ease-out'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Mitarbeiter & Zeit */}
|
||||||
|
<div className="row mb-3">
|
||||||
|
<div className="col-md-6">
|
||||||
|
<div className="d-flex align-items-center">
|
||||||
|
<FaUser className="me-2" style={{ color: '#10b981' }} />
|
||||||
|
<strong>{ws.employeeName}</strong>
|
||||||
|
{ws.employeeShort && (
|
||||||
|
<span className="badge ms-2" style={{
|
||||||
|
background: 'linear-gradient(135deg, #10b981 0%, #059669 100%)',
|
||||||
|
color: 'white'
|
||||||
|
}}>{ws.employeeShort}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="col-md-6 text-md-end">
|
||||||
|
{!ws.isComment && (
|
||||||
|
<div className="d-flex align-items-center justify-content-md-end">
|
||||||
|
<FaClock className="me-2" style={{ color: '#10b981' }} />
|
||||||
|
<strong className="fs-5" style={{ color: '#10b981' }}>{formatTime(ws.totalTime)}</strong>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Service Type */}
|
||||||
|
<div className="mb-3">
|
||||||
|
<span className="badge px-3 py-2" style={{
|
||||||
|
background: 'linear-gradient(135deg, #4a5568 0%, #2d3748 100%)',
|
||||||
|
color: 'white',
|
||||||
|
fontSize: '0.85rem'
|
||||||
|
}}>{serviceTypeLabel(ws.serviceType)}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Status-Änderung */}
|
||||||
|
{ws.oldStatus !== ws.newStatus && (
|
||||||
|
<div className="mb-3 p-3 rounded-3" style={{
|
||||||
|
background: 'linear-gradient(135deg, rgba(16, 185, 129, 0.1) 0%, rgba(5, 150, 105, 0.1) 100%)'
|
||||||
|
}}>
|
||||||
|
<FaExchangeAlt className="me-2" style={{ color: '#10b981' }} />
|
||||||
|
<span className="text-muted">Status:</span>{' '}
|
||||||
|
<span className="badge" style={{ background: '#6b7280', color: 'white' }}>{statusLabel(ws.oldStatus)}</span>
|
||||||
|
<span className="mx-2" style={{ color: '#10b981' }}>→</span>
|
||||||
|
<span className="badge" style={{ background: '#10b981', color: 'white' }}>{statusLabel(ws.newStatus)}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Details */}
|
||||||
|
<div className="mt-3 p-3 rounded-3" style={{
|
||||||
|
background: 'rgba(16, 185, 129, 0.05)',
|
||||||
|
border: '1px solid rgba(16, 185, 129, 0.1)'
|
||||||
|
}}>
|
||||||
|
<small className="text-dark" style={{ whiteSpace: 'pre-wrap', lineHeight: '1.6' }}>
|
||||||
|
{ws.details}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>{`
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes slideDown {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
max-height: 0;
|
||||||
|
transform: translateY(-10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
max-height: 1000px;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { needsTimeEntry } from '../lib/ticketForm'
|
||||||
|
|
||||||
|
const formatTime = (minutes) => {
|
||||||
|
const hours = Math.floor(minutes / 60)
|
||||||
|
const mins = minutes % 60
|
||||||
|
return hours > 0 ? `${hours}h ${mins}min` : `${mins}min`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Eine Zeile ueber der Arbeitsblatt-Liste: Arbeitszeit, Anzahl, offene Git-Zeiten
|
||||||
|
export default function WorksheetSummary({ worksheets }) {
|
||||||
|
if (!worksheets || worksheets.length === 0) return null
|
||||||
|
const minutes = worksheets
|
||||||
|
.filter((ws) => !ws.isComment)
|
||||||
|
.reduce((sum, ws) => sum + (Number(ws.totalTime) || 0), 0)
|
||||||
|
const open = worksheets.filter(needsTimeEntry).length
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="worksheet-summary">
|
||||||
|
<span>Arbeitszeit gesamt <strong>{formatTime(minutes)}</strong></span>
|
||||||
|
<span>{worksheets.length} {worksheets.length === 1 ? 'Arbeitsblatt' : 'Arbeitsblätter'}</span>
|
||||||
|
{open > 0 && <span className="open">{open} {open === 1 ? 'Zeit offen' : 'Zeiten offen'}</span>}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
import { FaTimes } from 'react-icons/fa'
|
||||||
|
import { isoToGermanDate, timeToWoms, germanDateToIso, womsTimeToInput, minutesBetween, isGitWorksheet, needsTimeEntry } from '../lib/ticketForm'
|
||||||
|
import { DateTimeInput } from './ui'
|
||||||
|
|
||||||
|
// Gespeichert ist dd.mm.yyyy / hhmm, die Kalender-Felder brauchen yyyy-mm-dd / hh:mm
|
||||||
|
const formFromWorksheet = (worksheet) => ({
|
||||||
|
startDate: germanDateToIso(worksheet.startDate || worksheet.endDate),
|
||||||
|
startTime: womsTimeToInput(worksheet.startTime),
|
||||||
|
endDate: germanDateToIso(worksheet.endDate || worksheet.startDate),
|
||||||
|
endTime: womsTimeToInput(worksheet.endTime)
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Beginn, Ende und Arbeitszeit eines Arbeitsblatts nachtraeglich aendern.
|
||||||
|
* Wer das darf, entscheidet canEditWorksheetTimes (lib/ticketForm).
|
||||||
|
*/
|
||||||
|
export default function WorksheetTimesModal({ worksheet, onClose, onSave }) {
|
||||||
|
const isGit = isGitWorksheet(worksheet)
|
||||||
|
const [formData, setFormData] = useState(() => formFromWorksheet(worksheet))
|
||||||
|
// null = die Arbeitszeit folgt Beginn und Ende. Passte sie schon vorher nicht dazu,
|
||||||
|
// wurde sie von Hand eingetragen (z.B. ohne Pause) und bleibt stehen.
|
||||||
|
const [manualTotal, setManualTotal] = useState(() => {
|
||||||
|
const stored = Number(worksheet.totalTime) || 0
|
||||||
|
return !stored || stored === minutesBetween(formData.startTime, formData.endTime) ? null : stored
|
||||||
|
})
|
||||||
|
const [saving, setSaving] = useState(false)
|
||||||
|
const [error, setError] = useState('')
|
||||||
|
|
||||||
|
const calculatedTotal = minutesBetween(formData.startTime, formData.endTime)
|
||||||
|
const totalTime = manualTotal ?? calculatedTotal ?? 0
|
||||||
|
|
||||||
|
const handleChange = (field, value) => {
|
||||||
|
setFormData(prev => ({ ...prev, [field]: value }))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Das Ende darf nicht vor dem Start liegen, deshalb rueckt es mit
|
||||||
|
const handleStartDateChange = (value) => {
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
startDate: value,
|
||||||
|
endDate: value && prev.endDate && prev.endDate < value ? value : prev.endDate
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
setSaving(true)
|
||||||
|
setError('')
|
||||||
|
try {
|
||||||
|
const result = await onSave(worksheet, {
|
||||||
|
startDate: isoToGermanDate(formData.startDate),
|
||||||
|
startTime: timeToWoms(formData.startTime),
|
||||||
|
endDate: isoToGermanDate(formData.endDate),
|
||||||
|
endTime: timeToWoms(formData.endTime),
|
||||||
|
totalTime: parseInt(totalTime, 10) || 0
|
||||||
|
})
|
||||||
|
if (result?.success) {
|
||||||
|
onClose()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setError(result?.error || 'Speichern fehlgeschlagen')
|
||||||
|
} catch (err) {
|
||||||
|
setError(err.message || 'Speichern fehlgeschlagen')
|
||||||
|
}
|
||||||
|
setSaving(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="overlay">
|
||||||
|
<span className="overlay-close" onClick={onClose}>
|
||||||
|
<FaTimes />
|
||||||
|
</span>
|
||||||
|
<div className="overlay-content" style={{ maxWidth: 560 }}>
|
||||||
|
{/* Kurzer Titel: auf dem Handy laeuft ein langer unter das Schliessen-X */}
|
||||||
|
<h2 className="mb-2">{needsTimeEntry(worksheet) ? 'Zeit nachtragen' : 'Zeiten bearbeiten'}</h2>
|
||||||
|
<p className="text-grey" style={{ fontSize: '14px', marginBottom: '16px' }}>
|
||||||
|
WSID {worksheet.wsid}
|
||||||
|
{isGit && <><br />Das Ende ist der Zeitpunkt des Git-Pushs. Trag ein, wann die Arbeit begonnen hat.</>}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<div className="bg-red text-white p-2 mb-2" style={{ borderRadius: '4px' }}>
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<div className="form-group">
|
||||||
|
<label className="form-label" htmlFor="worksheet-times-start">Beginn</label>
|
||||||
|
<DateTimeInput
|
||||||
|
id="worksheet-times-start"
|
||||||
|
timeLabel="Uhrzeit Beginn"
|
||||||
|
required
|
||||||
|
timeRequired={isGit}
|
||||||
|
date={formData.startDate}
|
||||||
|
time={formData.startTime}
|
||||||
|
onDateChange={handleStartDateChange}
|
||||||
|
onTimeChange={(value) => handleChange('startTime', value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group">
|
||||||
|
<label className="form-label" htmlFor="worksheet-times-end">Ende</label>
|
||||||
|
<DateTimeInput
|
||||||
|
id="worksheet-times-end"
|
||||||
|
timeLabel="Uhrzeit Ende"
|
||||||
|
required
|
||||||
|
min={formData.startDate}
|
||||||
|
date={formData.endDate}
|
||||||
|
time={formData.endTime}
|
||||||
|
onDateChange={(value) => handleChange('endDate', value)}
|
||||||
|
onTimeChange={(value) => handleChange('endTime', value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group">
|
||||||
|
<label className="form-label" htmlFor="worksheet-times-total">Arbeitszeit (Minuten)</label>
|
||||||
|
<input
|
||||||
|
id="worksheet-times-total"
|
||||||
|
type="number"
|
||||||
|
className="form-control"
|
||||||
|
min="0"
|
||||||
|
step="1"
|
||||||
|
value={totalTime}
|
||||||
|
onChange={(e) => setManualTotal(e.target.value)}
|
||||||
|
placeholder="0"
|
||||||
|
/>
|
||||||
|
<small style={{ color: '#a0aec0', fontSize: '12px' }}>
|
||||||
|
{manualTotal === null && calculatedTotal !== null
|
||||||
|
? '✓ Automatisch berechnet'
|
||||||
|
: 'Manuell eingeben'}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-center mt-2">
|
||||||
|
<button type="submit" className="btn btn-dark" disabled={saving}>
|
||||||
|
{saving ? 'Speichert...' : 'Speichern'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { IconTrash, IconX } from '@tabler/icons-react'
|
||||||
|
import { Card, Button, Field } from '../ui'
|
||||||
|
import { useEmployees } from '../../hooks/useEmployees'
|
||||||
|
import { NODE_KIND, NODE_STATUS, NODE_STATUS_LABELS } from '../../lib/roadmaps'
|
||||||
|
|
||||||
|
const STATUS_ORDER = [NODE_STATUS.OPEN, NODE_STATUS.IN_PROGRESS, NODE_STATUS.DONE]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seitliches Detail-Panel fuer den ausgewaehlten Knoten:
|
||||||
|
* Titel, Status, Zustaendiger, Deadline, Notizen, eingehende Verbindungen.
|
||||||
|
*/
|
||||||
|
export default function NodeDetailPanel({
|
||||||
|
node,
|
||||||
|
nodes,
|
||||||
|
readOnly,
|
||||||
|
onClose,
|
||||||
|
onSave,
|
||||||
|
onSetStatus,
|
||||||
|
onDelete,
|
||||||
|
onDisconnect,
|
||||||
|
}) {
|
||||||
|
const { employees } = useEmployees()
|
||||||
|
const [form, setForm] = useState({ title: '', assignedTo: '', deadline: '', notes: '' })
|
||||||
|
const [saving, setSaving] = useState(false)
|
||||||
|
const [error, setError] = useState('')
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setForm({
|
||||||
|
title: node.title || '',
|
||||||
|
assignedTo: node.assignedTo || '',
|
||||||
|
deadline: node.deadline || '',
|
||||||
|
notes: node.notes || '',
|
||||||
|
})
|
||||||
|
setError('')
|
||||||
|
}, [node.$id]) // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
|
const isStart = node.kind === NODE_KIND.START
|
||||||
|
const incoming = (node.sourceIds || [])
|
||||||
|
.map((id) => nodes.find((n) => n.$id === id))
|
||||||
|
.filter(Boolean)
|
||||||
|
|
||||||
|
const handleSave = async () => {
|
||||||
|
if (!form.title.trim()) {
|
||||||
|
setError('Titel darf nicht leer sein.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setSaving(true)
|
||||||
|
setError('')
|
||||||
|
const employee = employees.find((e) => e.userId === form.assignedTo)
|
||||||
|
const result = await onSave(node, {
|
||||||
|
title: form.title.trim(),
|
||||||
|
assignedTo: form.assignedTo,
|
||||||
|
assignedName: employee?.displayName || '',
|
||||||
|
deadline: form.deadline,
|
||||||
|
notes: form.notes,
|
||||||
|
})
|
||||||
|
setSaving(false)
|
||||||
|
if (!result.success) setError(result.error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
title={isStart ? 'Start-Knoten' : node.kind === NODE_KIND.GOAL ? 'Ziel-Knoten' : 'Schritt'}
|
||||||
|
actions={<Button variant="ghost" onClick={onClose}><IconX size={16} /></Button>}
|
||||||
|
>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||||
|
<Field label="Titel">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={form.title}
|
||||||
|
maxLength={128}
|
||||||
|
disabled={readOnly}
|
||||||
|
onChange={(e) => setForm((f) => ({ ...f, title: e.target.value }))}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
{!isStart && (
|
||||||
|
<Field label="Status">
|
||||||
|
<div style={{ display: 'flex', gap: 6 }}>
|
||||||
|
{STATUS_ORDER.map((status) => (
|
||||||
|
<Button
|
||||||
|
key={status}
|
||||||
|
variant={node.status === status ? 'primary' : 'default'}
|
||||||
|
disabled={readOnly}
|
||||||
|
onClick={() => onSetStatus(node, status)}
|
||||||
|
>
|
||||||
|
{NODE_STATUS_LABELS[status]}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Field>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isStart && (
|
||||||
|
<Field label="Zustaendig">
|
||||||
|
<select
|
||||||
|
value={form.assignedTo}
|
||||||
|
disabled={readOnly}
|
||||||
|
onChange={(e) => setForm((f) => ({ ...f, assignedTo: e.target.value }))}
|
||||||
|
>
|
||||||
|
<option value="">Niemand</option>
|
||||||
|
{employees.map((e) => (
|
||||||
|
<option key={e.userId} value={e.userId}>{e.displayName}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</Field>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isStart && (
|
||||||
|
<Field label="Deadline">
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
value={form.deadline}
|
||||||
|
disabled={readOnly}
|
||||||
|
onChange={(e) => setForm((f) => ({ ...f, deadline: e.target.value }))}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Field label="Notizen">
|
||||||
|
<textarea
|
||||||
|
rows={4}
|
||||||
|
value={form.notes}
|
||||||
|
maxLength={2000}
|
||||||
|
disabled={readOnly}
|
||||||
|
placeholder="Details, Anforderungen, Links…"
|
||||||
|
onChange={(e) => setForm((f) => ({ ...f, notes: e.target.value }))}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
{incoming.length > 0 && (
|
||||||
|
<Field label="Eingehende Verbindungen">
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||||
|
{incoming.map((source) => (
|
||||||
|
<div key={source.$id} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', fontSize: 13 }}>
|
||||||
|
<span className="muted">{source.title}</span>
|
||||||
|
{!readOnly && (
|
||||||
|
<Button variant="ghost" title="Verbindung loesen" onClick={() => onDisconnect(source.$id, node)}>
|
||||||
|
<IconX size={14} />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Field>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{error && <p style={{ color: 'var(--danger, #ef4444)', fontSize: 13 }}>{error}</p>}
|
||||||
|
|
||||||
|
{!readOnly && (
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', marginTop: 8 }}>
|
||||||
|
{!isStart ? (
|
||||||
|
<Button variant="ghost" onClick={() => onDelete(node)} title="Knoten loeschen">
|
||||||
|
<IconTrash size={15} /> Loeschen
|
||||||
|
</Button>
|
||||||
|
) : <span />}
|
||||||
|
<Button variant="primary" onClick={handleSave} disabled={saving}>
|
||||||
|
{saving ? 'Speichere…' : 'Speichern'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
import { useLayoutEffect, useRef, useState, useCallback } from 'react'
|
||||||
|
import { IconPlus } from '@tabler/icons-react'
|
||||||
|
import RoadmapNodeCard from './RoadmapNodeCard'
|
||||||
|
import { NODE_KIND } from '../../lib/roadmaps'
|
||||||
|
|
||||||
|
const COLUMN_WIDTH = 230
|
||||||
|
const COLUMN_GAP = 48
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Steckbrett: Spalten von links (Start) nach rechts (Ziele), Knoten vertikal
|
||||||
|
* gestapelt, Kanten als SVG-Overlay. Verbinden per Klick-Modus:
|
||||||
|
* Connect-Handle am Quellknoten -> gueltige Ziele (Spalte weiter rechts) anklicken.
|
||||||
|
*/
|
||||||
|
export default function RoadmapBoard({
|
||||||
|
roadmap,
|
||||||
|
nodes,
|
||||||
|
selectedNode,
|
||||||
|
readOnly,
|
||||||
|
onSelectNode,
|
||||||
|
onAddNode,
|
||||||
|
onConnect,
|
||||||
|
}) {
|
||||||
|
const containerRef = useRef(null)
|
||||||
|
const nodeRefs = useRef({})
|
||||||
|
const [edges, setEdges] = useState([])
|
||||||
|
const [connectSource, setConnectSource] = useState(null)
|
||||||
|
|
||||||
|
const columnCount = Math.max(roadmap.columnCount || 3, 2)
|
||||||
|
const columns = Array.from({ length: columnCount }, (_, c) =>
|
||||||
|
nodes.filter((n) => n.column === c).sort((a, b) => (a.row || 0) - (b.row || 0))
|
||||||
|
)
|
||||||
|
|
||||||
|
const setNodeRef = useCallback((id) => (el) => {
|
||||||
|
if (el) nodeRefs.current[id] = el
|
||||||
|
else delete nodeRefs.current[id]
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// Kanten-Geometrie nach jedem Layout neu berechnen (Anker relativ zum Board)
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
const container = containerRef.current
|
||||||
|
if (!container) return
|
||||||
|
const compute = () => {
|
||||||
|
const base = container.getBoundingClientRect()
|
||||||
|
const next = []
|
||||||
|
for (const node of nodes) {
|
||||||
|
for (const sourceId of node.sourceIds || []) {
|
||||||
|
const sourceEl = nodeRefs.current[sourceId]
|
||||||
|
const targetEl = nodeRefs.current[node.$id]
|
||||||
|
if (!sourceEl || !targetEl) continue
|
||||||
|
const s = sourceEl.getBoundingClientRect()
|
||||||
|
const t = targetEl.getBoundingClientRect()
|
||||||
|
next.push({
|
||||||
|
id: `${sourceId}-${node.$id}`,
|
||||||
|
x1: s.right - base.left,
|
||||||
|
y1: s.top + s.height / 2 - base.top,
|
||||||
|
x2: t.left - base.left,
|
||||||
|
y2: t.top + t.height / 2 - base.top,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setEdges(next)
|
||||||
|
}
|
||||||
|
compute()
|
||||||
|
const observer = new ResizeObserver(compute)
|
||||||
|
observer.observe(container)
|
||||||
|
return () => observer.disconnect()
|
||||||
|
}, [nodes, columnCount])
|
||||||
|
|
||||||
|
const handleStartConnect = (node) => setConnectSource(node)
|
||||||
|
|
||||||
|
const handleSelect = async (node) => {
|
||||||
|
if (connectSource) {
|
||||||
|
if (node.column > connectSource.column) {
|
||||||
|
await onConnect(connectSource, node)
|
||||||
|
}
|
||||||
|
setConnectSource(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
onSelectNode(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
const columnLabel = (c) => {
|
||||||
|
if (c === 0) return 'Start'
|
||||||
|
if (c === columnCount - 1) return 'Ziele'
|
||||||
|
return `Phase ${c}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ overflowX: 'auto', paddingBottom: 8 }}>
|
||||||
|
{connectSource && (
|
||||||
|
<div
|
||||||
|
className="muted"
|
||||||
|
style={{ fontSize: 13, marginBottom: 8, display: 'flex', alignItems: 'center', gap: 10 }}
|
||||||
|
>
|
||||||
|
Verbinden von <strong>{connectSource.title}</strong>: Zielknoten rechts davon anklicken.
|
||||||
|
<button type="button" className="ui-btn" onClick={() => setConnectSource(null)}>Abbrechen</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
ref={containerRef}
|
||||||
|
style={{
|
||||||
|
position: 'relative',
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: `repeat(${columnCount}, ${COLUMN_WIDTH}px)`,
|
||||||
|
gap: `16px ${COLUMN_GAP}px`,
|
||||||
|
alignItems: 'start',
|
||||||
|
minWidth: columnCount * (COLUMN_WIDTH + COLUMN_GAP),
|
||||||
|
padding: '8px 4px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', pointerEvents: 'none', zIndex: 0 }}
|
||||||
|
>
|
||||||
|
<defs>
|
||||||
|
<marker id="roadmap-arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto">
|
||||||
|
<path d="M0,0 L8,4 L0,8 Z" fill="var(--text-muted, #94a3b8)" />
|
||||||
|
</marker>
|
||||||
|
</defs>
|
||||||
|
{edges.map((e) => {
|
||||||
|
const dx = Math.max((e.x2 - e.x1) / 2, 24)
|
||||||
|
return (
|
||||||
|
<path
|
||||||
|
key={e.id}
|
||||||
|
d={`M ${e.x1} ${e.y1} C ${e.x1 + dx} ${e.y1}, ${e.x2 - dx} ${e.y2}, ${e.x2} ${e.y2}`}
|
||||||
|
fill="none"
|
||||||
|
stroke="var(--text-muted, #94a3b8)"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
markerEnd="url(#roadmap-arrow)"
|
||||||
|
opacity="0.7"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
{columns.map((columnNodes, c) => (
|
||||||
|
<div key={c} style={{ display: 'flex', flexDirection: 'column', gap: 14, zIndex: 1 }}>
|
||||||
|
<div className="faint" style={{ fontSize: 11, fontWeight: 700, textTransform: 'uppercase', letterSpacing: 0.5 }}>
|
||||||
|
{columnLabel(c)}
|
||||||
|
</div>
|
||||||
|
{columnNodes.map((node) => (
|
||||||
|
<RoadmapNodeCard
|
||||||
|
key={node.$id}
|
||||||
|
node={node}
|
||||||
|
selected={selectedNode?.$id === node.$id}
|
||||||
|
readOnly={readOnly}
|
||||||
|
connectSource={connectSource}
|
||||||
|
isValidTarget={Boolean(connectSource) && node.column > connectSource.column}
|
||||||
|
onSelect={handleSelect}
|
||||||
|
onStartConnect={handleStartConnect}
|
||||||
|
nodeRef={setNodeRef(node.$id)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{!readOnly && c > 0 && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="ui-btn"
|
||||||
|
style={{ borderStyle: 'dashed', fontSize: 12 }}
|
||||||
|
onClick={() => onAddNode(c, c === columnCount - 1 ? NODE_KIND.GOAL : NODE_KIND.STEP, columnNodes.length)}
|
||||||
|
>
|
||||||
|
<IconPlus size={14} /> {c === columnCount - 1 ? 'Ziel' : 'Schritt'}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
import { IconFlag, IconPlayerPlay, IconLink } from '@tabler/icons-react'
|
||||||
|
import { NODE_KIND, NODE_STATUS, isNodeOverdue } from '../../lib/roadmaps'
|
||||||
|
|
||||||
|
const STATUS_COLORS = {
|
||||||
|
[NODE_STATUS.OPEN]: 'var(--text-muted, #94a3b8)',
|
||||||
|
[NODE_STATUS.IN_PROGRESS]: '#fbbf24',
|
||||||
|
[NODE_STATUS.DONE]: '#34d399',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Knoten-Kachel im Steckbrett.
|
||||||
|
* connectMode: Board wartet auf Zielauswahl; isValidTarget markiert erlaubte Ziele.
|
||||||
|
*/
|
||||||
|
export default function RoadmapNodeCard({
|
||||||
|
node,
|
||||||
|
selected,
|
||||||
|
readOnly,
|
||||||
|
connectSource,
|
||||||
|
isValidTarget,
|
||||||
|
onSelect,
|
||||||
|
onStartConnect,
|
||||||
|
nodeRef,
|
||||||
|
}) {
|
||||||
|
const overdue = isNodeOverdue(node)
|
||||||
|
const isStart = node.kind === NODE_KIND.START
|
||||||
|
const isGoal = node.kind === NODE_KIND.GOAL
|
||||||
|
const connecting = Boolean(connectSource)
|
||||||
|
const isConnectSource = connectSource?.$id === node.$id
|
||||||
|
|
||||||
|
const borderColor = isConnectSource
|
||||||
|
? 'var(--accent, #3b82f6)'
|
||||||
|
: connecting && isValidTarget
|
||||||
|
? '#34d399'
|
||||||
|
: selected
|
||||||
|
? 'var(--accent, #3b82f6)'
|
||||||
|
: overdue
|
||||||
|
? 'var(--danger, #ef4444)'
|
||||||
|
: 'var(--border)'
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={nodeRef}
|
||||||
|
onClick={() => onSelect(node)}
|
||||||
|
style={{
|
||||||
|
position: 'relative',
|
||||||
|
background: isGoal ? 'rgba(59,130,246,0.10)' : 'var(--surface-2, rgba(45,55,72,0.65))',
|
||||||
|
border: `2px solid ${borderColor}`,
|
||||||
|
borderRadius: 10,
|
||||||
|
padding: '10px 12px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
opacity: connecting && !isValidTarget && !isConnectSource ? 0.45 : 1,
|
||||||
|
transition: 'border-color 0.15s ease, opacity 0.15s ease',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||||
|
{isStart && <IconPlayerPlay size={15} style={{ flexShrink: 0 }} />}
|
||||||
|
{isGoal && <IconFlag size={15} style={{ flexShrink: 0, color: '#93c5fd' }} />}
|
||||||
|
{!isStart && (
|
||||||
|
<span
|
||||||
|
title={`Status: ${node.status || 'open'}`}
|
||||||
|
style={{
|
||||||
|
width: 10, height: 10, borderRadius: 5, flexShrink: 0,
|
||||||
|
background: STATUS_COLORS[node.status] || STATUS_COLORS.open,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<span style={{ fontWeight: 600, fontSize: 13, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
||||||
|
{node.title}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{(node.assignedName || node.deadline) && (
|
||||||
|
<div style={{ display: 'flex', gap: 8, marginTop: 6, fontSize: 11 }} className="faint">
|
||||||
|
{node.assignedName && <span>{node.assignedName}</span>}
|
||||||
|
{node.deadline && (
|
||||||
|
<span style={overdue ? { color: 'var(--danger, #ef4444)', fontWeight: 700 } : undefined}>
|
||||||
|
bis {node.deadline.split('-').reverse().join('.')}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!readOnly && !isGoal && !connecting && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
title="Verbindung von hier starten"
|
||||||
|
onClick={(e) => { e.stopPropagation(); onStartConnect(node) }}
|
||||||
|
style={{
|
||||||
|
position: 'absolute', right: -11, top: '50%', transform: 'translateY(-50%)',
|
||||||
|
width: 22, height: 22, borderRadius: 11,
|
||||||
|
border: '1px solid var(--border)', background: 'var(--surface, #1e293b)',
|
||||||
|
color: 'var(--text-muted)', display: 'flex', alignItems: 'center',
|
||||||
|
justifyContent: 'center', cursor: 'crosshair', padding: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconLink size={12} />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/** Fortschrittsbalken fuer Roadmaps: Prozent + "x von y Knoten fertig". */
|
||||||
|
export default function RoadmapProgressBar({ progress, compact = false }) {
|
||||||
|
const { percent, done, total } = progress || { percent: 0, done: 0, total: 0 }
|
||||||
|
return (
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 4, minWidth: compact ? 120 : 180 }}>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 12 }}>
|
||||||
|
<span className="muted">{percent}%</span>
|
||||||
|
{!compact && <span className="faint">{done} von {total} fertig</span>}
|
||||||
|
</div>
|
||||||
|
<div style={{ height: 6, borderRadius: 3, background: 'var(--border)', overflow: 'hidden' }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: `${percent}%`,
|
||||||
|
height: '100%',
|
||||||
|
borderRadius: 3,
|
||||||
|
background: percent >= 100 ? 'var(--ok, #22c55e)' : 'var(--accent, #3b82f6)',
|
||||||
|
transition: 'width 0.3s ease',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import { useId, useState } from 'react'
|
||||||
|
|
||||||
|
/* Textfeld mit Vorschlagsliste. Freie Eingaben bleiben erlaubt;
|
||||||
|
getSuggestions(text) liefert [{ value, hint }]. */
|
||||||
|
export default function AutocompleteInput({ value, onChange, getSuggestions, id, placeholder, required }) {
|
||||||
|
const listId = useId()
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
|
const [active, setActive] = useState(-1)
|
||||||
|
|
||||||
|
const items = open ? getSuggestions(value) : []
|
||||||
|
const showList = items.length > 0
|
||||||
|
|
||||||
|
const choose = (item) => {
|
||||||
|
onChange(item.value)
|
||||||
|
setOpen(false)
|
||||||
|
setActive(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleKeyDown = (e) => {
|
||||||
|
if (e.key === 'ArrowDown') {
|
||||||
|
e.preventDefault()
|
||||||
|
setOpen(true)
|
||||||
|
setActive((i) => Math.min(i + 1, items.length - 1))
|
||||||
|
} else if (e.key === 'ArrowUp') {
|
||||||
|
e.preventDefault()
|
||||||
|
setActive((i) => Math.max(i - 1, -1))
|
||||||
|
} else if (e.key === 'Enter' && showList && items[active]) {
|
||||||
|
e.preventDefault()
|
||||||
|
choose(items[active])
|
||||||
|
} else if (e.key === 'Escape' && showList) {
|
||||||
|
e.preventDefault()
|
||||||
|
setOpen(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="autocomplete">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id={id}
|
||||||
|
className="form-control"
|
||||||
|
role="combobox"
|
||||||
|
aria-autocomplete="list"
|
||||||
|
aria-expanded={showList}
|
||||||
|
aria-controls={listId}
|
||||||
|
aria-activedescendant={showList && items[active] ? `${listId}-${active}` : undefined}
|
||||||
|
autoComplete="off"
|
||||||
|
placeholder={placeholder}
|
||||||
|
required={required}
|
||||||
|
value={value}
|
||||||
|
onChange={(e) => {
|
||||||
|
onChange(e.target.value)
|
||||||
|
setOpen(true)
|
||||||
|
setActive(-1)
|
||||||
|
}}
|
||||||
|
onFocus={() => setOpen(true)}
|
||||||
|
onBlur={() => {
|
||||||
|
setOpen(false)
|
||||||
|
setActive(-1)
|
||||||
|
}}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
/>
|
||||||
|
{showList && (
|
||||||
|
<ul className="autocomplete-list" id={listId} role="listbox">
|
||||||
|
{items.map((item, index) => (
|
||||||
|
<li
|
||||||
|
key={item.value}
|
||||||
|
id={`${listId}-${index}`}
|
||||||
|
role="option"
|
||||||
|
aria-selected={index === active}
|
||||||
|
className={`autocomplete-item${index === active ? ' active' : ''}`}
|
||||||
|
// mousedown statt click: sonst verliert das Feld vorher den Fokus und die Liste schliesst
|
||||||
|
onMouseDown={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
choose(item)
|
||||||
|
}}
|
||||||
|
onMouseEnter={() => setActive(index)}
|
||||||
|
>
|
||||||
|
<span className="autocomplete-value">{item.value}</span>
|
||||||
|
{item.hint && <span className="autocomplete-hint">{item.hint}</span>}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export default function Badge({ tone = 'muted', dot = false, children, className = '', style }) {
|
||||||
|
return (
|
||||||
|
<span className={`badge badge-${tone} ${className}`} style={style}>
|
||||||
|
{dot && <span className="dot" />}
|
||||||
|
{children}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
const VARIANTS = {
|
||||||
|
default: 'ui-btn',
|
||||||
|
primary: 'ui-btn ui-btn-primary',
|
||||||
|
ghost: 'ui-btn ui-btn-ghost',
|
||||||
|
danger: 'ui-btn ui-btn-danger',
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Button({
|
||||||
|
variant = 'default',
|
||||||
|
size,
|
||||||
|
as = 'button',
|
||||||
|
className = '',
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}) {
|
||||||
|
const cls = `${VARIANTS[variant] || VARIANTS.default}${size === 'sm' ? ' ui-btn-sm' : ''} ${className}`
|
||||||
|
const Comp = as
|
||||||
|
return (
|
||||||
|
<Comp className={cls} {...props}>
|
||||||
|
{children}
|
||||||
|
</Comp>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
export default function Card({ title, actions, children, pad = true, className = '', bodyStyle, style }) {
|
||||||
|
if (!title && !actions) {
|
||||||
|
return (
|
||||||
|
<div className={`ui-card ${pad ? 'pad' : ''} ${className}`} style={style}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className={`ui-card ${className}`} style={style}>
|
||||||
|
<div className="ui-card-head">
|
||||||
|
<h3>{title}</h3>
|
||||||
|
{actions && <div className="flex gap-2 items-center">{actions}</div>}
|
||||||
|
</div>
|
||||||
|
<div className="ui-card-body" style={bodyStyle}>{children}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
Loaded 100 of 147 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user