diff --git a/._index.html b/._index.html new file mode 100644 index 0000000..988359c Binary files /dev/null and b/._index.html differ diff --git a/._package.json b/._package.json new file mode 100644 index 0000000..988359c Binary files /dev/null and b/._package.json differ diff --git a/._postcss.config.js b/._postcss.config.js new file mode 100644 index 0000000..988359c Binary files /dev/null and b/._postcss.config.js differ diff --git a/._src b/._src new file mode 100755 index 0000000..988359c Binary files /dev/null and b/._src differ diff --git a/._tailwind.config.js b/._tailwind.config.js new file mode 100644 index 0000000..988359c Binary files /dev/null and b/._tailwind.config.js differ diff --git a/backup.sh b/backup.sh deleted file mode 100755 index f2a1d2f..0000000 --- a/backup.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/bash -# Backup-Script für Pfandsystem MySQL Datenbank - -set -e - -# Konfiguration -BACKUP_DIR="/root/backups" -DATE=$(date +%Y%m%d_%H%M%S) -BACKUP_FILE="$BACKUP_DIR/pfandsystem_backup_$DATE.sql" -RETENTION_DAYS=30 - -# Backup-Verzeichnis erstellen -mkdir -p "$BACKUP_DIR" - -echo "🗄️ Starte Datenbank-Backup..." - -# MySQL Backup erstellen -docker exec pfandsystem-mysql mysqldump \ - -u pfandsystem \ - -ppfandsystem_secure_password \ - --single-transaction \ - --routines \ - --triggers \ - pfandsystem > "$BACKUP_FILE" - -# Komprimieren -gzip "$BACKUP_FILE" -BACKUP_FILE="$BACKUP_FILE.gz" - -echo "✅ Backup erstellt: $BACKUP_FILE" - -# Backup-Größe anzeigen -SIZE=$(du -h "$BACKUP_FILE" | cut -f1) -echo "📊 Backup-Größe: $SIZE" - -# Alte Backups löschen (älter als RETENTION_DAYS) -echo "🧹 Lösche alte Backups (älter als $RETENTION_DAYS Tage)..." -find "$BACKUP_DIR" -name "pfandsystem_backup_*.sql.gz" -mtime +$RETENTION_DAYS -delete - -# Anzahl verbleibender Backups -BACKUP_COUNT=$(ls -1 "$BACKUP_DIR"/pfandsystem_backup_*.sql.gz 2>/dev/null | wc -l) -echo "📁 Anzahl Backups: $BACKUP_COUNT" - -echo "✅ Backup abgeschlossen!" diff --git a/build-and-deploy.sh b/build-and-deploy.sh deleted file mode 100755 index b35463d..0000000 --- a/build-and-deploy.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/sh -# Komplettes Build- und Docker-Deploy-Skript für PWA mit nginx - -set -e - -echo "[1/4] → npm run build..." -npm run build - -echo "[2/4] → Manifest-Link fixen..." -node fix-manifest-link.cjs - -echo "[3/4] → Docker-Image bauen..." -docker build -t pfandsystem-pwa . - -echo "[4/4] → Container starten (Port 8080 auf 80)..." -docker run -d --rm -p 8080:80 --name pfandsystem-pwa pfandsystem-pwa - -echo "Deployment erfolgreich! App läuft auf http://localhost:8080" diff --git a/certbot.sh b/certbot.sh deleted file mode 100755 index b3330f0..0000000 --- a/certbot.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash -# Erstzertifikat holen (nur einmal nötig) -docker run --rm -it \ - -v $(pwd)/certbot/conf:/etc/letsencrypt \ - -v $(pwd)/certbot/www:/var/www/certbot \ - certbot/certbot certonly --webroot \ - --webroot-path=/var/www/certbot \ - --email deine-email@domain.de \ - --agree-tos \ - --no-eff-email \ - -d pfandsystem.backdigital.de diff --git a/deploy-to-nginx.sh b/deploy-to-nginx.sh deleted file mode 100755 index 2bede46..0000000 --- a/deploy-to-nginx.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash -set -e - -# Build das Projekt -npm run build - -# Zielverzeichnis für nginx -TARGET=/usr/share/nginx/html - -# Lösche alten Inhalt (optional, vorsichtig!) -sudo rm -rf $TARGET/* - -# Kopiere neuen Build nach nginx-root -sudo cp -r dist/* $TARGET/ - -# Setze Dateirechte -sudo chown -R www-data:www-data $TARGET -sudo chmod -R 755 $TARGET - -echo "Deployment abgeschlossen. Die App ist jetzt unter nginx erreichbar." diff --git a/deploy.sh b/deploy.sh deleted file mode 100755 index 816273b..0000000 --- a/deploy.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/bash -# Deployment-Skript für Pfandsystem - -set -e - -echo "🚀 Pfandsystem Deployment gestartet..." - -# 1. Frontend bauen -echo "[1/6] Frontend wird gebaut..." -npm install -npm run build -node fix-manifest-link.cjs - -# 2. Backend Dependencies installieren -echo "[2/6] Backend Dependencies werden installiert..." -cd backend -npm install --production -cd .. - -# 3. Docker Images bauen -echo "[3/6] Docker Images werden gebaut..." -docker-compose build - -# 4. Container stoppen -echo "[4/6] Alte Container werden gestoppt..." -docker-compose down - -# 5. Container starten -echo "[5/6] Neue Container werden gestartet..." -docker-compose up -d - -# 6. Warten und Status prüfen -echo "[6/6] Warte auf Container-Start..." -sleep 10 - -echo "" -echo "✅ Deployment abgeschlossen!" -echo "" -echo "📊 Container-Status:" -docker-compose ps - -echo "" -echo "🌐 Zugriff:" -echo " Frontend: https://pfandsystem.backdigital.de" -echo " phpMyAdmin: http://localhost:8081" -echo " Backend API: http://localhost:3001/api" -echo "" -echo "📝 Logs anzeigen:" -echo " docker-compose logs -f" -echo "" diff --git a/docker-compose.nginx.yml b/docker-compose.nginx.yml deleted file mode 100644 index 4099f64..0000000 --- a/docker-compose.nginx.yml +++ /dev/null @@ -1,14 +0,0 @@ -version: '3.8' -services: - nginx: - image: nginx:1.25-alpine - container_name: pfandsystem-nginx - restart: unless-stopped - ports: - - "80:80" - - "443:443" - volumes: - - ./dist:/usr/share/nginx/html:ro - - ./nginx.conf:/etc/nginx/nginx.conf:ro - - ./certbot/www:/var/www/certbot:ro - - ./certbot/conf:/etc/letsencrypt diff --git a/docker-compose.yml b/docker-compose.yml index f1c8a8d..6fd4935 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -56,6 +56,7 @@ services: PORT: 3001 NODE_ENV: production RESEND_API_KEY: ${RESEND_API_KEY} + MAIL_FROM: ${MAIL_FROM:-Pfandsystem Demo } UPLOAD_DIR: /app/uploads MAX_FILE_SIZE: 10485760 SUPERADMIN_EMAIL: ${SUPERADMIN_EMAIL} @@ -71,11 +72,11 @@ services: condition: service_healthy frontend: - image: nginx:1.25-alpine + build: + context: . + dockerfile: Dockerfile container_name: pfandsystem-demo-frontend restart: unless-stopped - volumes: - - ./dist:/usr/share/nginx/html:ro networks: - pfandsystem-demo-net labels: @@ -86,6 +87,8 @@ services: - "traefik.http.routers.pfandsystem-demo.tls=true" - "traefik.http.routers.pfandsystem-demo.tls.certresolver=letsencrypt" - "traefik.http.services.pfandsystem-demo.loadbalancer.server.port=80" + depends_on: + - backend volumes: mysql_data: diff --git a/dokumentationen/BACKEND_SERVICES.md b/dokumentationen/BACKEND_SERVICES.md deleted file mode 100644 index 336c81d..0000000 --- a/dokumentationen/BACKEND_SERVICES.md +++ /dev/null @@ -1,121 +0,0 @@ -# 🔧 Backend Services (systemd) - -## Problem gelöst: -Backends sind nach Server-Neustart oder Crash abgestürzt. - -## Lösung: -Systemd Services erstellt - Backends starten automatisch! - ---- - -## 📋 Services: - -### **Dev-Backend:** -- **Service:** `pfandsystem-dev-backend.service` -- **Port:** 3001 -- **Verzeichnis:** `/root/entwicklung/pfandsystem/backend` -- **Log:** `/var/log/pfandsystem-dev-backend.log` - -### **Live-Backend (BorBäcker):** -- **Service:** `borbaecker-backend.service` -- **Port:** 3002 -- **Verzeichnis:** `/root/livesysteme/borbaecker/backend` -- **Log:** `/var/log/borbaecker-backend.log` - ---- - -## 🎯 Befehle: - -### **Status prüfen:** -```bash -systemctl status pfandsystem-dev-backend -systemctl status borbaecker-backend -``` - -### **Starten:** -```bash -systemctl start pfandsystem-dev-backend -systemctl start borbaecker-backend -``` - -### **Stoppen:** -```bash -systemctl stop pfandsystem-dev-backend -systemctl stop borbaecker-backend -``` - -### **Neu starten:** -```bash -systemctl restart pfandsystem-dev-backend -systemctl restart borbaecker-backend -``` - -### **Logs ansehen:** -```bash -tail -f /var/log/pfandsystem-dev-backend.log -tail -f /var/log/borbaecker-backend.log - -# Oder mit journalctl: -journalctl -u pfandsystem-dev-backend -f -journalctl -u borbaecker-backend -f -``` - -### **Autostart deaktivieren:** -```bash -systemctl disable pfandsystem-dev-backend -systemctl disable borbaecker-backend -``` - ---- - -## ✅ Features: - -- **Automatischer Start** beim Server-Neustart -- **Automatischer Neustart** bei Crash (nach 10 Sekunden) -- **Logging** in separate Dateien -- **Systemd-Integration** - Standard Linux Service Management - ---- - -## 🔄 Nach Code-Änderungen: - -```bash -# Backend neu starten -systemctl restart borbaecker-backend - -# Oder manuell (wenn Service gestoppt werden soll): -systemctl stop borbaecker-backend -cd /root/livesysteme/borbaecker/backend -npm start -``` - ---- - -## 🧪 Test: - -```bash -# Backends testen -curl http://localhost:3001/health -curl http://localhost:3002/health - -# Login testen -curl -X POST http://localhost:3002/api/auth/login \ - -H "Content-Type: application/json" \ - -d '{"email":"admin@pfandsystem.de","password":"admin123"}' -``` - ---- - -## 📊 Monitoring: - -```bash -# Beide Services überwachen -watch -n 2 'systemctl status pfandsystem-dev-backend borbaecker-backend' - -# Logs live verfolgen -tail -f /var/log/pfandsystem-dev-backend.log /var/log/borbaecker-backend.log -``` - ---- - -**Backends laufen jetzt als systemd Services und starten automatisch!** 🚀 diff --git a/dokumentationen/DEPLOYMENT_ANLEITUNG.md b/dokumentationen/DEPLOYMENT_ANLEITUNG.md deleted file mode 100644 index 17b642a..0000000 --- a/dokumentationen/DEPLOYMENT_ANLEITUNG.md +++ /dev/null @@ -1,415 +0,0 @@ -# 🚀 Deployment-Anleitung: Live-Version erstellen - -## 📋 Übersicht - -**Dev-Version:** `/root` (pfandsystem.backdigital.de) -**Live-Version:** `/var/www/pfandsystem-live` (neue-domain.de) - ---- - -## 🔧 Schritt-für-Schritt Anleitung - -### 1. **Neues Verzeichnis erstellen** - -```bash -# Live-Version Verzeichnis -sudo mkdir -p /var/www/pfandsystem-live -cd /var/www/pfandsystem-live - -# Git Repository klonen -git clone ssh://git@49.13.154.75:2244/christian/pfandsystem.git . - -# Auf main branch wechseln -git checkout main -``` - ---- - -### 2. **Umgebungsvariablen anpassen** - -#### Frontend `.env`: -```bash -cat > .env << 'EOF' -VITE_API_URL=https://DEINE-NEUE-DOMAIN.de/api -EOF -``` - -#### Backend `.env`: -```bash -cat > backend/.env << 'EOF' -# Database -DB_HOST=localhost -DB_PORT=3307 -DB_USER=pfandsystem_live -DB_PASSWORD=NEUES_SICHERES_PASSWORT -DB_NAME=pfandsystem_live - -# JWT -JWT_SECRET=NEUER_JWT_SECRET_KEY_HIER - -# Server -PORT=3002 -NODE_ENV=production - -# Resend API -RESEND_API_KEY=re_6BE6Hv6U_H7ggkAT4ApbiSZ5SCo2Cf1qA - -# File Upload -UPLOAD_DIR=/var/www/pfandsystem-live/backend/uploads -MAX_FILE_SIZE=10485760 -EOF -``` - ---- - -### 3. **Docker-Compose anpassen** - -```bash -cat > docker-compose.yml << 'EOF' -version: '3.8' - -services: - # MySQL Datenbank (Live) - mysql: - image: mysql:8.0 - container_name: pfandsystem-live-mysql - restart: unless-stopped - environment: - MYSQL_ROOT_PASSWORD: NEUES_ROOT_PASSWORT - MYSQL_DATABASE: pfandsystem_live - MYSQL_USER: pfandsystem_live - MYSQL_PASSWORD: NEUES_SICHERES_PASSWORT - volumes: - - mysql_data:/var/lib/mysql - ports: - - "3307:3306" - networks: - - pfandsystem-live-network - healthcheck: - test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] - interval: 10s - timeout: 5s - retries: 5 - - # phpMyAdmin (Live) - phpmyadmin: - image: phpmyadmin:latest - container_name: pfandsystem-live-phpmyadmin - restart: unless-stopped - environment: - PMA_HOST: mysql - PMA_PORT: 3306 - ports: - - "8082:80" - networks: - - pfandsystem-live-network - depends_on: - - mysql - - # Frontend (Live) - frontend: - image: nginx:1.25-alpine - container_name: pfandsystem-live-frontend - restart: unless-stopped - volumes: - - ./dist:/usr/share/nginx/html:ro - networks: - - pfandsystem-live-network - labels: - - "traefik.enable=true" - - "traefik.http.routers.pfandsystem-live.rule=Host(`DEINE-NEUE-DOMAIN.de`)" - - "traefik.http.routers.pfandsystem-live.entrypoints=web,websecure" - - "traefik.http.routers.pfandsystem-live.tls=true" - - "traefik.http.routers.pfandsystem-live.tls.certresolver=letsencrypt" - - "traefik.http.services.pfandsystem-live.loadbalancer.server.port=80" - -volumes: - mysql_data: - driver: local - -networks: - pfandsystem-live-network: - driver: bridge -EOF -``` - ---- - -### 4. **Traefik Dynamic Config für Backend** - -```bash -mkdir -p /var/www/pfandsystem-live/traefik-config - -cat > /var/www/pfandsystem-live/traefik-config/backend.yml << 'EOF' -http: - routers: - # Backend API Router (Live) - pfandsystem-live-api: - rule: "Host(`DEINE-NEUE-DOMAIN.de`) && PathPrefix(`/api`)" - service: pfandsystem-live-api - entryPoints: - - web - - websecure - tls: - certResolver: letsencrypt - priority: 100 - - # Backend Uploads Router (Live) - pfandsystem-live-uploads: - rule: "Host(`DEINE-NEUE-DOMAIN.de`) && PathPrefix(`/uploads`)" - service: pfandsystem-live-api - entryPoints: - - web - - websecure - tls: - certResolver: letsencrypt - priority: 100 - - services: - # Backend Service (Live) - pfandsystem-live-api: - loadBalancer: - servers: - - url: "http://172.17.0.1:3002" -EOF -``` - ---- - -### 5. **Traefik-Config in bestehenden Traefik einbinden** - -```bash -# Kopiere Config in Traefik dynamic Ordner -sudo cp /var/www/pfandsystem-live/traefik-config/backend.yml /root/traefik/dynamic/pfandsystem-live.yml - -# Traefik neu laden (erkennt automatisch neue Configs) -docker restart pfandsystem-traefik -``` - ---- - -### 6. **Datenbank initialisieren** - -```bash -# Docker Container starten -cd /var/www/pfandsystem-live -docker compose up -d mysql phpmyadmin - -# Warte bis MySQL bereit ist -sleep 30 - -# SQL Schema importieren -docker exec -i pfandsystem-live-mysql mysql -upfandsystem_live -pNEUES_SICHERES_PASSWORT pfandsystem_live < backend/schema.sql - -# Admin-User erstellen -cd backend -npm install -node scripts/create-admin.js -``` - ---- - -### 7. **Frontend bauen** - -```bash -cd /var/www/pfandsystem-live - -# Dependencies installieren -npm install - -# Frontend bauen -npm run build - -# Manifest-Links fixen -node fix-manifest-link.cjs -``` - ---- - -### 8. **Backend starten** - -```bash -cd /var/www/pfandsystem-live/backend - -# Dependencies installieren -npm install - -# Backend als Service starten (mit PM2) -npm install -g pm2 -pm2 start server.js --name pfandsystem-live -pm2 save -pm2 startup -``` - ---- - -### 9. **Frontend-Container starten** - -```bash -cd /var/www/pfandsystem-live -docker compose up -d frontend -``` - ---- - -### 10. **Logo und Branding anpassen** - -#### Logo ersetzen: -```bash -# Eigenes Logo (192x192 und 512x512) -cp /pfad/zu/deinem/logo-192.png /var/www/pfandsystem-live/public/logo-192.png -cp /pfad/zu/deinem/logo-512.png /var/www/pfandsystem-live/public/logo-512.png - -# Favicon -cp /pfad/zu/deinem/favicon.ico /var/www/pfandsystem-live/public/favicon.ico -``` - -#### Manifest anpassen: -```bash -nano /var/www/pfandsystem-live/public/manifest.json -``` - -Ändere: -```json -{ - "name": "Dein Firmenname - Pfandsystem", - "short_name": "Dein Pfandsystem", - "theme_color": "#DEINE_FARBE", - "background_color": "#DEINE_FARBE" -} -``` - -#### Farben anpassen: -```bash -nano /var/www/pfandsystem-live/src/index.css -``` - -Ändere Primary-Color, etc. - -#### Frontend neu bauen: -```bash -cd /var/www/pfandsystem-live -npm run build -node fix-manifest-link.cjs -docker restart pfandsystem-live-frontend -``` - ---- - -## 🧪 Testing - -### 1. **DNS konfigurieren:** -``` -DEINE-NEUE-DOMAIN.de → A-Record → Server-IP -``` - -### 2. **Testen:** -```bash -# SSL-Zertifikat wird automatisch von Traefik generiert -curl -I https://DEINE-NEUE-DOMAIN.de - -# Login testen -curl https://DEINE-NEUE-DOMAIN.de/api/auth/login \ - -X POST \ - -H "Content-Type: application/json" \ - -d '{"email":"admin@pfandsystem.de","password":"admin123"}' -``` - -### 3. **Im Browser öffnen:** -``` -https://DEINE-NEUE-DOMAIN.de -``` - ---- - -## 📊 Übersicht der Ports - -| Service | Dev | Live | -|---------|-----|------| -| MySQL | 3306 | 3307 | -| phpMyAdmin | 8081 | 8082 | -| Backend | 3001 | 3002 | -| Frontend | via Traefik | via Traefik | - ---- - -## 🔄 Updates deployen - -```bash -cd /var/www/pfandsystem-live - -# Neueste Version holen -git pull origin main - -# Frontend neu bauen -npm run build -node fix-manifest-link.cjs -docker restart pfandsystem-live-frontend - -# Backend neu starten -pm2 restart pfandsystem-live -``` - ---- - -## 🛡️ Sicherheit - -1. **Passwörter ändern:** - - MySQL Root-Passwort - - MySQL User-Passwort - - JWT Secret - - Admin-Passwort (nach erstem Login) - -2. **Firewall:** - ```bash - # Nur notwendige Ports öffnen - ufw allow 80/tcp - ufw allow 443/tcp - ufw allow 22/tcp - ``` - -3. **Backups:** - ```bash - # Automatisches Backup einrichten - crontab -e - # Täglich um 2 Uhr - 0 2 * * * /var/www/pfandsystem-live/backup.sh - ``` - ---- - -## ✅ Checkliste - -- [ ] Verzeichnis erstellt -- [ ] Git geklont -- [ ] .env Dateien angepasst -- [ ] docker-compose.yml angepasst -- [ ] Traefik-Config erstellt -- [ ] Datenbank initialisiert -- [ ] Admin-User erstellt -- [ ] Frontend gebaut -- [ ] Backend gestartet (PM2) -- [ ] Frontend-Container gestartet -- [ ] Logo/Branding angepasst -- [ ] DNS konfiguriert -- [ ] SSL-Zertifikat generiert -- [ ] Login getestet -- [ ] Alle Funktionen getestet - ---- - -## 🎯 Zusammenfassung - -**Dev-Version:** -- Verzeichnis: `/root` -- Domain: `pfandsystem.backdigital.de` -- MySQL Port: 3306 -- Backend Port: 3001 - -**Live-Version:** -- Verzeichnis: `/var/www/pfandsystem-live` -- Domain: `DEINE-NEUE-DOMAIN.de` -- MySQL Port: 3307 -- Backend Port: 3002 - -Beide Versionen laufen parallel auf derselben Instanz mit eigenem Traefik-Routing! diff --git a/dokumentationen/DEPLOYMENT_CHECKLIST.md b/dokumentationen/DEPLOYMENT_CHECKLIST.md deleted file mode 100644 index a5fbba8..0000000 --- a/dokumentationen/DEPLOYMENT_CHECKLIST.md +++ /dev/null @@ -1,328 +0,0 @@ -# 🚀 Deployment Checklist - Pfandsystem - -## Pre-Deployment - -### 1. Umgebungsvariablen konfigurieren - -- [ ] **Backend `.env` erstellen:** - ```bash - cp backend/.env.example backend/.env - nano backend/.env - ``` - - Zu ändern: - - [ ] `DB_PASSWORD` - Sicheres MySQL-Passwort - - [ ] `JWT_SECRET` - Mindestens 32 Zeichen, zufällig generiert - - [ ] `RESEND_API_KEY` - Prüfen ob korrekt - -- [ ] **Frontend `.env` prüfen:** - ```bash - nano .env - ``` - - Zu prüfen: - - [ ] `VITE_API_URL` - Korrekte Domain (https://pfandsystem.backdigital.de/api) - -- [ ] **docker-compose.yml anpassen:** - ```bash - nano docker-compose.yml - ``` - - Zu ändern: - - [ ] `MYSQL_ROOT_PASSWORD` - - [ ] `MYSQL_PASSWORD` (muss mit backend/.env übereinstimmen) - -### 2. SSL/TLS Zertifikate - -- [ ] Zertifikate vorhanden in `/root/certbot/conf/` -- [ ] Zertifikate gültig (nicht abgelaufen) -- [ ] Domain korrekt konfiguriert (pfandsystem.backdigital.de) - -### 3. Build & Test - -- [ ] Frontend Dependencies installieren: - ```bash - npm install - ``` - -- [ ] Frontend bauen: - ```bash - npm run build - node fix-manifest-link.cjs - ``` - -- [ ] Backend Dependencies installieren: - ```bash - cd backend && npm install && cd .. - ``` - -## Deployment - -### 4. Docker Compose starten - -- [ ] Alte Container stoppen (falls vorhanden): - ```bash - docker-compose down - ``` - -- [ ] Volumes prüfen (optional - nur bei Neuinstallation): - ```bash - docker volume ls - # Bei Bedarf alte Volumes löschen: - # docker volume rm pfandsystem_mysql_data - ``` - -- [ ] Container starten: - ```bash - docker-compose up -d - ``` - -- [ ] Container-Status prüfen: - ```bash - docker-compose ps - ``` - - Alle 4 Container sollten "Up" sein: - - pfandsystem-mysql - - pfandsystem-phpmyadmin - - pfandsystem-backend - - pfandsystem-frontend - -### 5. Logs prüfen - -- [ ] Backend-Logs prüfen: - ```bash - docker-compose logs backend - ``` - - Erwartete Ausgabe: - - ✅ MySQL Datenbankverbindung erfolgreich - - 🚀 Backend-Server läuft auf Port 3001 - -- [ ] MySQL-Logs prüfen: - ```bash - docker-compose logs mysql - ``` - - Erwartete Ausgabe: - - ready for connections - -### 6. Datenbank initialisieren - -- [ ] Admin-Benutzer erstellen: - ```bash - docker exec -it pfandsystem-backend node scripts/create-admin.js - ``` - - Notiere: - - Email: admin@pfandsystem.de - - Passwort: admin123 (ÄNDERN!) - -- [ ] Optional - Beispieldaten prüfen (via phpMyAdmin): - - URL: http://localhost:8081 - - Login: pfandsystem / - - Prüfe Tabellen: mitarbeiter, artikel - -## Post-Deployment Tests - -### 7. Frontend-Tests - -- [ ] Frontend erreichbar: https://pfandsystem.backdigital.de -- [ ] Login-Seite wird angezeigt -- [ ] Login mit Admin-Credentials funktioniert -- [ ] Dashboard wird angezeigt -- [ ] PWA-Manifest lädt (DevTools → Application → Manifest) -- [ ] Service Worker registriert (DevTools → Application → Service Workers) -- [ ] Icons werden angezeigt - -### 8. Backend-Tests - -- [ ] Health Check: - ```bash - curl http://localhost:3001/health - ``` - - Erwartete Antwort: - ```json - {"status":"OK","timestamp":"..."} - ``` - -- [ ] Login API: - ```bash - curl -X POST http://localhost:3001/api/auth/login \ - -H "Content-Type: application/json" \ - -d '{"email":"admin@pfandsystem.de","password":"admin123"}' - ``` - - Erwartete Antwort: - ```json - {"token":"...","user":{...}} - ``` - -### 9. Funktions-Tests - -- [ ] **Kundenverwaltung:** - - Neuen Kunden anlegen - - Kunde bearbeiten - - Kunde löschen - -- [ ] **Artikelverwaltung:** - - Neuen Artikel anlegen - - Artikel bearbeiten - - Artikel löschen - -- [ ] **Lieferungen:** - - Neue Lieferung erfassen - - Mit Foto hochladen - - Foto wird angezeigt - -- [ ] **Rücknahmen:** - - Neue Rücknahme erfassen - - Mit Foto hochladen - - Foto wird angezeigt - -- [ ] **Mitarbeiterverwaltung (Admin):** - - Neuen Mitarbeiter anlegen - - Rolle ändern - - Mit neuem Mitarbeiter einloggen - -### 10. Sicherheits-Tests - -- [ ] Admin-Passwort geändert (nicht mehr admin123) -- [ ] HTTPS funktioniert (kein Mixed Content) -- [ ] Nicht-authentifizierte API-Calls werden abgelehnt -- [ ] User kann keine Admin-Funktionen ausführen -- [ ] File-Upload nur für Bilder möglich -- [ ] File-Upload-Größe limitiert (max 10MB) - -## Daten-Migration (falls erforderlich) - -### 11. Daten aus Supabase exportieren - -- [ ] Kunden exportieren (CSV/JSON) -- [ ] Artikel exportieren -- [ ] Mitarbeiter exportieren -- [ ] Lieferungen exportieren -- [ ] Rücknahmen exportieren - -### 12. Daten in MySQL importieren - -- [ ] Via phpMyAdmin (http://localhost:8081) -- [ ] Oder via SQL-Script: - ```bash - docker exec -i pfandsystem-mysql mysql -u pfandsystem -p pfandsystem < import.sql - ``` - -- [ ] Daten-Integrität prüfen: - - Anzahl Kunden korrekt - - Anzahl Artikel korrekt - - Foreign Keys korrekt - -## Monitoring & Backup - -### 13. Backup einrichten - -- [ ] Datenbank-Backup-Script erstellen: - ```bash - #!/bin/bash - DATE=$(date +%Y%m%d_%H%M%S) - docker exec pfandsystem-mysql mysqldump -u pfandsystem -p pfandsystem > backup_$DATE.sql - ``` - -- [ ] Cronjob für tägliches Backup: - ```bash - crontab -e - # Täglich um 2 Uhr - 0 2 * * * /root/backup.sh - ``` - -### 14. Monitoring - -- [ ] Container-Status überwachen: - ```bash - docker-compose ps - ``` - -- [ ] Logs regelmäßig prüfen: - ```bash - docker-compose logs --tail=100 - ``` - -- [ ] Disk Space prüfen: - ```bash - df -h - docker system df - ``` - -## Troubleshooting - -### Häufige Probleme - -**Backend startet nicht:** -```bash -docker-compose logs backend -# Prüfe Datenbank-Verbindung -docker-compose restart mysql -``` - -**Frontend zeigt keine Daten:** -1. Browser-Cache leeren (Strg+Shift+R) -2. Service Worker deregistrieren (DevTools → Application) -3. API-URL in .env prüfen - -**Datenbank-Verbindung fehlgeschlagen:** -```bash -# MySQL-Logs prüfen -docker-compose logs mysql - -# In MySQL-Container einloggen -docker exec -it pfandsystem-mysql mysql -u pfandsystem -p - -# Verbindung testen -docker exec -it pfandsystem-backend node -e "require('./config/database.js')" -``` - -**Upload funktioniert nicht:** -```bash -# Prüfe Upload-Verzeichnis -docker exec -it pfandsystem-backend ls -la /app/uploads - -# Prüfe Berechtigungen -docker exec -it pfandsystem-backend chmod 777 /app/uploads -``` - -## Rollback-Plan - -Falls Probleme auftreten: - -1. **Container stoppen:** - ```bash - docker-compose down - ``` - -2. **Alte Version wiederherstellen:** - ```bash - git checkout - docker-compose up -d - ``` - -3. **Datenbank-Backup wiederherstellen:** - ```bash - docker exec -i pfandsystem-mysql mysql -u pfandsystem -p pfandsystem < backup.sql - ``` - -## Abschluss - -- [ ] Alle Tests erfolgreich -- [ ] Dokumentation aktualisiert -- [ ] Team informiert -- [ ] Backup-Strategie aktiv -- [ ] Monitoring läuft - ---- - -**Deployment abgeschlossen am:** _________________ - -**Durchgeführt von:** _________________ - -**Notizen:** diff --git a/dokumentationen/ERFOLG.md b/dokumentationen/ERFOLG.md deleted file mode 100644 index 62ac3d9..0000000 --- a/dokumentationen/ERFOLG.md +++ /dev/null @@ -1,85 +0,0 @@ -# ✅ ERFOLG! Alle Probleme gelöst - -## 1. Login funktioniert! ✅ - -**Problem war:** Backend konnte MySQL-Host "mysql" nicht auflösen (läuft lokal, nicht in Docker) - -**Lösung:** -- `DB_HOST=localhost` in `/root/backend/.env` -- Admin-Benutzer neu erstellt -- Login getestet und funktioniert! - -**Test:** -```bash -curl -X POST http://localhost:3001/api/auth/login \ - -H "Content-Type: application/json" \ - -d '{"email":"admin@pfandsystem.de","password":"admin123"}' -``` - -**Ergebnis:** Token wird zurückgegeben! ✅ - -## 2. Git konfiguriert ✅ - -- User: christian (vonperfall@r83.io) -- Remote: ssh://git@49.13.154.75:2244/christian/pfandsystem.git -- Branches: main, development, testing -- Alle Änderungen committed - -**Zum Pushen (mit Passwort):** -```bash -git push -u origin main -# Passwort eingeben -git push origin development -git push origin testing -``` - -## 3. SSL - Nächster Schritt - -SSL-Zertifikate sind vorhanden. Du musst nur den externen nginx konfigurieren um auf das Backend zu proxyen. - -## 🚀 System läuft jetzt! - -### Aktive Services: -- ✅ MySQL (Docker, Port 3306) -- ✅ phpMyAdmin (Docker, Port 8081) -- ✅ Backend API (lokal, Port 3001) -- ✅ Frontend (nginx Docker, Port 80) - -### Login-Daten: -- **Email:** admin@pfandsystem.de -- **Passwort:** admin123 - -### URLs: -- **Frontend:** http://pfandsystem.backdigital.de -- **Backend API:** http://localhost:3001/api -- **phpMyAdmin:** http://localhost:8081 -- **Health Check:** http://localhost:3001/health - -## 📋 Nächste Schritte - -1. **Git pushen** (manuell mit Passwort) - ```bash - git push -u origin main - ``` - -2. **Frontend testen** - ```bash - # Browser öffnen - http://pfandsystem.backdigital.de - # Login mit: admin@pfandsystem.de / admin123 - ``` - -3. **SSL konfigurieren** (optional) - - Externer nginx muss auf Backend proxyen - - Zertifikate sind vorhanden - -## 🎉 Fertig! - -Das System ist jetzt voll funktionsfähig: -- ✅ Datenbank läuft -- ✅ Backend läuft -- ✅ Login funktioniert -- ✅ Git konfiguriert -- ✅ Alle Commits erstellt - -**Du kannst jetzt mit dem System arbeiten!** diff --git a/dokumentationen/ERFOLG_FINAL.md b/dokumentationen/ERFOLG_FINAL.md deleted file mode 100644 index bdd9436..0000000 --- a/dokumentationen/ERFOLG_FINAL.md +++ /dev/null @@ -1,97 +0,0 @@ -# 🎉 ERFOLG! Alle Probleme gelöst! - -## ✅ Was funktioniert: - -### 1. **Login** ✅ -- Backend läuft auf Port 3001 -- Login-Endpoint funktioniert -- Test: `curl https://pfandsystem.backdigital.de/api/auth/login` -- **Credentials:** admin@pfandsystem.de / admin123 - -### 2. **Frontend** ✅ -- Erreichbar unter: **https://pfandsystem.backdigital.de** -- HTTP 200 Status -- Über Traefik geroutet -- SSL aktiv (Let's Encrypt) - -### 3. **SSL/HTTPS** ✅ -- Traefik konfiguriert -- Let's Encrypt Zertifikate automatisch -- Alle Verbindungen verschlüsselt - -## 🚀 URLs: - -- **Frontend:** https://pfandsystem.backdigital.de -- **Backend API:** https://pfandsystem.backdigital.de/api -- **phpMyAdmin:** http://localhost:8081 -- **Login:** admin@pfandsystem.de / admin123 - -## 📋 Git Push (noch zu tun): - -```bash -git push -u origin main -# Passwort eingeben - -git push origin development -git push origin testing -``` - -## 🔧 Technische Details: - -### Traefik-Konfiguration: -- Datei: `/data/coolify/proxy/dynamic/pfandsystem.yaml` -- Frontend-Router: Host-basiert -- API-Router: PathPrefix `/api` mit Priorität 100 -- Uploads-Router: PathPrefix `/uploads` mit Priorität 100 - -### Services: -- **MySQL:** Docker, Port 3306 -- **phpMyAdmin:** Docker, Port 8081 -- **Backend:** Lokal, Port 3001 -- **Frontend:** Docker (pfandsystem-frontend-internal) -- **Traefik:** Coolify Proxy (Port 80/443) - -### Netzwerk: -- Backend: Host-IP 51.15.249.253:3001 -- Frontend: Docker-Netzwerk (pfandsystem-frontend-internal) -- Traefik: Verbunden mit root_pfandsystem-network - -## ✅ Checkliste: - -- [x] MySQL läuft -- [x] Backend läuft -- [x] Frontend läuft -- [x] Login funktioniert -- [x] SSL funktioniert -- [x] Traefik konfiguriert -- [x] Git konfiguriert -- [ ] Git gepusht (manuell mit Passwort) - -## 🎯 Nächste Schritte: - -1. **Git pushen:** - ```bash - git push -u origin main - git push origin development - git push origin testing - ``` - -2. **Im Browser testen:** - - Öffne: https://pfandsystem.backdigital.de - - Login: admin@pfandsystem.de / admin123 - - Teste alle Funktionen - -3. **Optional - Backend in Docker:** - - Aktuell läuft Backend lokal (funktioniert aber!) - - Kann später in Docker verschoben werden - -## 🎉 FERTIG! - -Das System ist vollständig funktionsfähig: -- ✅ Datenbank -- ✅ Backend mit Login -- ✅ Frontend über HTTPS -- ✅ SSL/TLS -- ✅ Alle APIs erreichbar - -**Du kannst jetzt mit dem System arbeiten!** diff --git a/dokumentationen/FINAL_STATUS.md b/dokumentationen/FINAL_STATUS.md deleted file mode 100644 index 453a955..0000000 --- a/dokumentationen/FINAL_STATUS.md +++ /dev/null @@ -1,129 +0,0 @@ -# 🎯 Finaler Status - Pfandsystem - -## ✅ Was läuft: - -1. **MySQL** - Port 3306 (Docker) -2. **phpMyAdmin** - http://localhost:8081 (Docker) -3. **Backend API** - http://localhost:3001 (lokal, nicht Docker) -4. **Frontend** - Intern im Docker-Netzwerk - -## ⚠️ Wichtig: Traefik Reverse Proxy - -Auf dem Server läuft **Traefik** auf Port 80/443. -Das Frontend muss über Traefik geroutet werden! - -## 📋 Nächste Schritte: - -### 1. Git Push (MANUELL) - -```bash -# Du musst das manuell machen mit deinem Passwort: -git push -u origin main -# Username: christian -# Passwort: [dein Passwort] - -# Dann die anderen Branches: -git push origin development -git push origin testing -``` - -### 2. Frontend über Traefik routen - -Da Traefik läuft, musst du eine Traefik-Konfiguration erstellen: - -**Option A: Traefik Labels (empfohlen)** -```yaml -# In docker-compose.yml für frontend: -labels: - - "traefik.enable=true" - - "traefik.http.routers.pfandsystem.rule=Host(`pfandsystem.backdigital.de`)" - - "traefik.http.routers.pfandsystem.entrypoints=websecure" - - "traefik.http.routers.pfandsystem.tls=true" - - "traefik.http.routers.pfandsystem.tls.certresolver=letsencrypt" - - "traefik.http.services.pfandsystem.loadbalancer.server.port=80" -``` - -**Option B: Traefik Config File** -Erstelle `/etc/traefik/dynamic/pfandsystem.yml`: -```yaml -http: - routers: - pfandsystem: - rule: "Host(`pfandsystem.backdigital.de`)" - service: pfandsystem - entryPoints: - - websecure - tls: - certResolver: letsencrypt - - services: - pfandsystem: - loadBalancer: - servers: - - url: "http://pfandsystem-frontend-internal:80" -``` - -### 3. Backend über Traefik routen (für API) - -```yaml -http: - routers: - pfandsystem-api: - rule: "Host(`pfandsystem.backdigital.de`) && PathPrefix(`/api`)" - service: pfandsystem-api - entryPoints: - - websecure - tls: - certResolver: letsencrypt - - services: - pfandsystem-api: - loadBalancer: - servers: - - url: "http://host.docker.internal:3001" -``` - -## 🔧 Aktuelle Konfiguration: - -### Backend (.env) -``` -DB_HOST=localhost # Wichtig! Nicht "mysql" weil Backend lokal läuft -DB_PORT=3306 -DB_USER=pfandsystem -DB_PASSWORD=pfandsystem_secure_password -``` - -### Frontend (.env) -``` -VITE_API_URL=http://localhost:3001/api # Muss auf /api zeigen wenn über Traefik -``` - -## 🚀 Schnellstart Backend: - -```bash -# Backend starten (falls gestoppt) -cd /root/backend && nohup npm start > /tmp/backend.log 2>&1 & - -# Backend-Logs sehen -tail -f /tmp/backend.log - -# Backend testen -curl http://localhost:3001/health -``` - -## 📊 Login-Daten: - -- **Email:** admin@pfandsystem.de -- **Passwort:** admin123 - -## 🎯 TODO: - -- [ ] Git pushen (manuell mit Passwort) -- [ ] Traefik-Routing für Frontend konfigurieren -- [ ] Traefik-Routing für Backend-API konfigurieren -- [ ] Frontend-API-URL anpassen (auf /api über Traefik) -- [ ] Testen - -## 💡 Hinweis: - -Da Traefik läuft, ist SSL automatisch verfügbar wenn die Routing-Konfiguration stimmt! diff --git a/dokumentationen/FIXES.md b/dokumentationen/FIXES.md deleted file mode 100644 index cbfd35b..0000000 --- a/dokumentationen/FIXES.md +++ /dev/null @@ -1,86 +0,0 @@ -# Sofortige Fixes für die 3 Hauptprobleme - -## Problem 1: Login geht nicht ✅ GELÖST - -**Ursache:** Backend crashed beim Login-Request -**Lösung:** Backend läuft jetzt lokal (außerhalb Docker) bis Docker-Problem behoben - -```bash -# Backend lokal starten (temporär) -cd /root/backend -npm start & - -# Test: -curl http://localhost:3001/health -# Sollte: {"status":"OK",...} -``` - -## Problem 2: SSL geht nicht ⚠️ IN ARBEIT - -**Ursache:** nginx im Docker hat keine SSL-Zertifikate gemountet -**Lösung:** Verwende externen nginx (bereits läuft) oder mounte Zertifikate - -**Temporäre Lösung - HTTP verwenden:** -```bash -# Frontend .env anpassen -echo "VITE_API_URL=http://pfandsystem.backdigital.de/api" > /root/.env - -# Neu bauen -npm run build -node fix-manifest-link.cjs -``` - -**Permanente Lösung - Externer nginx:** -Nginx läuft bereits auf dem Host und hat SSL. Wir müssen nur Proxy konfigurieren. - -## Problem 3: Git Repository ✅ GELÖST - -```bash -# Git konfiguriert -git config user.email "vonperfall@r83.io" -git config user.name "christian" -git remote add origin ssh://git@49.13.154.75:2244/christian/pfandsystem.git - -# Branches erstellt -git branch -M main -git branch development -git branch testing - -# Pushen: -git push -u origin main -git push origin development -git push origin testing -``` - -## Schnellstart-Befehle - -```bash -# 1. MySQL & phpMyAdmin starten -docker compose up -d mysql phpmyadmin - -# 2. Backend lokal starten -cd /root/backend && npm start & - -# 3. Frontend neu bauen -cd /root -npm run build -node fix-manifest-link.cjs - -# 4. Testen -curl http://localhost:3001/health -curl -X POST http://localhost:3001/api/auth/login \ - -H "Content-Type: application/json" \ - -d '{"email":"admin@pfandsystem.de","password":"admin123"}' - -# 5. Git pushen -git add -A -git commit -m "Fix: Backend läuft lokal, Git konfiguriert" -git push -u origin main -``` - -## Nächste Schritte - -1. **Backend-Docker-Problem beheben** (später) -2. **SSL konfigurieren** (externer nginx) -3. **Frontend deployen** -4. **Testen** diff --git a/dokumentationen/MIGRATION.md b/dokumentationen/MIGRATION.md deleted file mode 100644 index 982d449..0000000 --- a/dokumentationen/MIGRATION.md +++ /dev/null @@ -1,243 +0,0 @@ -# Migration von Supabase zu MySQL - Zusammenfassung - -## ✅ Durchgeführte Änderungen - -### 1. Backend (Node.js/Express) - -**Neu erstellt:** -- `/backend/server.js` - Hauptserver mit Express -- `/backend/config/database.js` - MySQL-Verbindung -- `/backend/middleware/auth.js` - JWT-Authentifizierung -- `/backend/middleware/upload.js` - Multer File-Upload -- `/backend/routes/` - Alle API-Routen (auth, kunden, artikel, lieferungen, ruecknahmen, mitarbeiter) -- `/backend/database/schema.sql` - MySQL-Schema (konvertiert von PostgreSQL) -- `/backend/scripts/create-admin.js` - Admin-Benutzer erstellen - -**Features:** -- ✅ JWT-basierte Authentifizierung (ersetzt Supabase Auth) -- ✅ Bcrypt Passwort-Hashing -- ✅ Rollen-basierte Zugriffskontrolle (user/admin) -- ✅ File-Upload mit Multer (ersetzt S3/Supabase Storage) -- ✅ Resend Email-Integration -- ✅ MySQL2 mit Connection Pooling - -### 2. Frontend (React) - -**Geänderte Dateien:** -- `/src/api/client.js` - Neuer API-Client (ersetzt Supabase Client) -- `/src/App.jsx` - Auth-Flow angepasst -- `/src/components/Auth/Login.jsx` - Login mit neuem API -- `/src/components/Auth/Logout.jsx` - Logout angepasst -- `/src/components/KundenVerwaltung.jsx` - API-Calls angepasst -- `/src/components/MitarbeiterVerwaltung.jsx` - API-Calls + Passwort-Feld -- `/src/components/Eingabe/VorgangForm.jsx` - File-Upload angepasst -- `/src/components/Dashboard/VorgangsListe.jsx` - Datenstruktur angepasst -- `/src/components/Dashboard/AdminDashboard.jsx` - API-Calls angepasst - -**Entfernt:** -- `@supabase/supabase-js` Dependency -- `/src/supabase/client.js` (bleibt für Referenz, wird nicht mehr verwendet) - -### 3. Datenbank - -**MySQL-Schema:** -- `mitarbeiter` - Mit email/password_hash statt supabase_user_id -- `kunden` - Unverändert -- `artikel` - Unverändert -- `lieferungen` - Foto-URL zeigt auf lokalen Upload-Ordner -- `ruecknahmen` - Foto-URL zeigt auf lokalen Upload-Ordner - -**Wichtige Änderungen:** -- UUID → CHAR(36) mit UUID() -- SERIAL → INT AUTO_INCREMENT -- timestamp with time zone → TIMESTAMP -- Foreign Keys auf auth.users entfernt - -### 4. Docker & Deployment - -**docker-compose.yml:** -- MySQL 8.0 Container -- phpMyAdmin Container -- Backend API Container -- Frontend nginx Container -- Volumes für Datenbank und Uploads - -**nginx.conf:** -- Proxy für `/api/` → Backend -- Proxy für `/uploads/` → Backend -- HTTPS-Konfiguration beibehalten -- Let's Encrypt Integration - -### 5. Umgebungsvariablen - -**Frontend (.env):** -``` -VITE_API_URL=https://pfandsystem.backdigital.de/api -``` - -**Backend (.env):** -``` -DB_HOST=mysql -DB_PORT=3306 -DB_USER=pfandsystem -DB_PASSWORD=*** -DB_NAME=pfandsystem -JWT_SECRET=*** -RESEND_API_KEY=re_6BE6Hv6U_H7ggkAT4ApbiSZ5SCo2Cf1qA -``` - -## 🚀 Deployment-Anleitung - -### Erstmaliges Setup - -1. **Umgebungsvariablen konfigurieren:** - ```bash - # Backend .env anpassen - nano backend/.env - - # Frontend .env anpassen - nano .env - ``` - -2. **Docker Compose starten:** - ```bash - docker-compose up -d - ``` - -3. **Admin-Benutzer erstellen:** - ```bash - docker exec -it pfandsystem-backend node scripts/create-admin.js - ``` - -4. **Login testen:** - - URL: https://pfandsystem.backdigital.de - - Email: admin@pfandsystem.de - - Passwort: admin123 (ÄNDERN!) - -### Updates deployen - -```bash -./deploy.sh -``` - -Oder manuell: -```bash -npm run build -node fix-manifest-link.cjs -docker-compose build -docker-compose up -d -``` - -## 🔐 Sicherheit - -### Wichtige Änderungen - -1. **Passwörter:** Alle Passwörter werden mit bcrypt gehasht (10 Rounds) -2. **JWT:** Tokens sind 24h gültig -3. **File Upload:** Nur Bilder erlaubt, max 10MB -4. **CORS:** Konfiguriert für Frontend-Domain - -### Zu ändern vor Produktion - -- [ ] `JWT_SECRET` in backend/.env -- [ ] MySQL Root-Passwort in docker-compose.yml -- [ ] MySQL User-Passwort in docker-compose.yml und backend/.env -- [ ] Admin-Passwort nach erstem Login -- [ ] HTTPS-Zertifikate prüfen - -## 📊 API-Änderungen - -### Authentifizierung - -**Vorher (Supabase):** -```javascript -const { data, error } = await supabase.auth.signInWithPassword({ email, password }); -``` - -**Nachher (Custom API):** -```javascript -const data = await api.login(email, password); -``` - -### Daten abrufen - -**Vorher (Supabase):** -```javascript -const { data } = await supabase.from('kunden').select('*'); -``` - -**Nachher (Custom API):** -```javascript -const data = await api.getKunden(); -``` - -### File Upload - -**Vorher (Supabase Storage):** -```javascript -const { data } = await supabase.storage.from('belegfotos').upload(fileName, file); -``` - -**Nachher (Multer):** -```javascript -await api.createLieferung({ kunde_id, artikel_id, anzahl }, foto); -``` - -## 🐛 Bekannte Probleme & Lösungen - -### Problem: Backend startet nicht - -**Lösung:** -```bash -docker-compose logs backend -# Prüfe Datenbank-Verbindung -docker-compose restart mysql -``` - -### Problem: Frontend zeigt keine Daten - -**Lösung:** -1. API-URL in `.env` prüfen -2. Browser-Cache leeren -3. Service Worker deregistrieren - -### Problem: Login funktioniert nicht - -**Lösung:** -1. Admin-Benutzer erstellen: `docker exec -it pfandsystem-backend node scripts/create-admin.js` -2. JWT_SECRET in backend/.env prüfen -3. Backend-Logs prüfen: `docker-compose logs backend` - -## 📝 Nächste Schritte - -### Sofort - -- [ ] Alle Passwörter ändern -- [ ] Admin-Login testen -- [ ] Daten aus Supabase exportieren und importieren -- [ ] Funktionstest aller Features - -### Optional - -- [ ] Email-Benachrichtigungen mit Resend testen -- [ ] Backup-Strategie einrichten -- [ ] Monitoring einrichten -- [ ] CI/CD Pipeline aufsetzen - -## 📧 Support - -Bei Fragen zur Migration: -- Backend-Logs: `docker-compose logs -f backend` -- Frontend-Logs: Browser DevTools Console -- Datenbank: phpMyAdmin auf http://localhost:8081 - -## 🎉 Fertig! - -Die Migration ist abgeschlossen. Das System ist jetzt vollständig unabhängig von Supabase und läuft mit: -- ✅ MySQL Datenbank -- ✅ Node.js/Express Backend -- ✅ React Frontend -- ✅ Docker Compose -- ✅ HTTPS/SSL -- ✅ phpMyAdmin -- ✅ Lokaler File Storage diff --git a/dokumentationen/MIGRATION_NEUER_SERVER.md b/dokumentationen/MIGRATION_NEUER_SERVER.md deleted file mode 100644 index 2b3feb5..0000000 --- a/dokumentationen/MIGRATION_NEUER_SERVER.md +++ /dev/null @@ -1,473 +0,0 @@ -# 🚀 Migration auf neuen Server - Komplette Anleitung - -## 📋 Was muss mit? - -### 1. **Verzeichnisstruktur:** -``` -/root/ -├── entwicklung/ -│ └── pfandsystem/ # Git-Repository -├── livesysteme/ -│ └── borbaecker/ # Git-Repository -├── traefik/ # Zentrale Traefik-Konfiguration -│ ├── traefik.yml -│ ├── dynamic/ -│ │ ├── pfandsystem-dev.yml -│ │ └── borbaecker.yml -│ └── letsencrypt/ -│ └── acme.json -└── backups/ - ├── entwicklung/ - │ └── pfandsystem/ - └── livesysteme/ - └── borbaecker/ -``` - ---- - -## 🔧 Schritt-für-Schritt Anleitung - -### **Schritt 1: Server vorbereiten** - -```bash -# System aktualisieren -apt update && apt upgrade -y - -# Docker installieren -curl -fsSL https://get.docker.com -o get-docker.sh -sh get-docker.sh - -# Docker Compose installieren -apt install docker-compose-plugin -y - -# Node.js installieren (für Backend) -curl -fsSL https://deb.nodesource.com/setup_20.x | bash - -apt install -y nodejs - -# Git installieren -apt install -y git - -# Verzeichnisse erstellen -mkdir -p /root/entwicklung -mkdir -p /root/livesysteme -mkdir -p /root/backups/entwicklung/pfandsystem -mkdir -p /root/backups/livesysteme/borbaecker -mkdir -p /root/traefik/dynamic -mkdir -p /root/traefik/letsencrypt -``` - ---- - -### **Schritt 2: SSH-Key für Git einrichten** - -```bash -# SSH-Key generieren -ssh-keygen -t ed25519 -C "vonperfall@r83.io" -f ~/.ssh/gitea_ed25519 -N "" - -# SSH-Config erstellen -cat > ~/.ssh/config << 'EOF' -Host 49.13.154.75 - Port 2244 - User git - IdentityFile ~/.ssh/gitea_ed25519 - StrictHostKeyChecking accept-new -EOF - -chmod 600 ~/.ssh/config - -# Public Key anzeigen (zum Hinzufügen in Gitea) -cat ~/.ssh/gitea_ed25519.pub -``` - -**→ Public Key in Gitea hinzufügen:** http://49.13.154.75:4000/user/settings/keys - ---- - -### **Schritt 3: Git-Repositories klonen** - -```bash -# Dev-System klonen -cd /root/entwicklung -git clone ssh://git@49.13.154.75:2244/christian/pfandsystem.git pfandsystem - -# Live-System klonen -cd /root/livesysteme -git clone ssh://git@49.13.154.75:2244/christian/borbaecker-pfandsystem.git borbaecker - -# Git-User konfigurieren -cd /root/entwicklung/pfandsystem -git config user.email "vonperfall@r83.io" -git config user.name "Christian von Perfall" - -cd /root/livesysteme/borbaecker -git config user.email "vonperfall@r83.io" -git config user.name "Christian von Perfall" -``` - ---- - -### **Schritt 4: Traefik-Konfiguration erstellen** - -#### **Haupt-Konfiguration:** -```bash -cat > /root/traefik/traefik.yml << 'EOF' -api: - dashboard: true - insecure: true - -entryPoints: - web: - address: ":80" - websecure: - address: ":443" - -certificatesResolvers: - letsencrypt: - acme: - email: vonperfall@r83.io - storage: /letsencrypt/acme.json - httpChallenge: - entryPoint: web - -providers: - docker: - endpoint: "unix:///var/run/docker.sock" - exposedByDefault: false - file: - directory: /etc/traefik/dynamic - watch: true - -log: - level: INFO - -accessLog: - filePath: "/var/log/access.log" -EOF -``` - -#### **Dev-System Routing:** -```bash -cat > /root/traefik/dynamic/pfandsystem-dev.yml << 'EOF' -http: - routers: - pfandsystem-dev-api: - rule: "Host(`pfandsystem.backdigital.de`) && PathPrefix(`/api`)" - service: pfandsystem-dev-api - entryPoints: - - web - - websecure - tls: - certResolver: letsencrypt - priority: 100 - - pfandsystem-dev-uploads: - rule: "Host(`pfandsystem.backdigital.de`) && PathPrefix(`/uploads`)" - service: pfandsystem-dev-api - entryPoints: - - web - - websecure - tls: - certResolver: letsencrypt - priority: 100 - - services: - pfandsystem-dev-api: - loadBalancer: - servers: - - url: "http://172.17.0.1:3001" -EOF -``` - -#### **Live-System Routing:** -```bash -cat > /root/traefik/dynamic/borbaecker.yml << 'EOF' -http: - routers: - borbaecker-api: - rule: "Host(`borbaecker-pfand.dockly.de`) && PathPrefix(`/api`)" - service: borbaecker-api - entryPoints: - - web - - websecure - tls: - certResolver: letsencrypt - priority: 100 - - borbaecker-uploads: - rule: "Host(`borbaecker-pfand.dockly.de`) && PathPrefix(`/uploads`)" - service: borbaecker-api - entryPoints: - - web - - websecure - tls: - certResolver: letsencrypt - priority: 100 - - services: - borbaecker-api: - loadBalancer: - servers: - - url: "http://172.17.0.1:3002" -EOF -``` - -#### **acme.json erstellen:** -```bash -touch /root/traefik/letsencrypt/acme.json -chmod 600 /root/traefik/letsencrypt/acme.json -``` - ---- - -### **Schritt 5: Dev-System starten** - -```bash -cd /root/entwicklung/pfandsystem - -# .env Dateien anpassen (falls nötig) -nano .env -nano backend/.env - -# Docker Container starten -docker compose up -d - -# Warten bis MySQL bereit ist -sleep 30 - -# Datenbank initialisieren -docker exec -i pfandsystem-mysql mysql -upfandsystem -ppfandsystem_secure_password pfandsystem < database/schema.sql - -# Admin-User erstellen -cd backend -npm install -node scripts/create-admin.js - -# Backend starten -npm start > /tmp/backend-dev.log 2>&1 & - -# Frontend bauen -cd .. -npm install -npm run build -node fix-manifest-link.cjs -docker restart pfandsystem-frontend -``` - ---- - -### **Schritt 6: Live-System starten** - -```bash -cd /root/livesysteme/borbaecker - -# .env Dateien anpassen -cat > .env << 'EOF' -VITE_API_URL=https://borbaecker-pfand.dockly.de/api -EOF - -cat > backend/.env << 'EOF' -DB_HOST=localhost -DB_PORT=3307 -DB_USER=borbaecker -DB_PASSWORD=borbaecker_secure_2024! -DB_NAME=borbaecker -JWT_SECRET=borbaecker_jwt_secret_key_2024_very_secure -PORT=3002 -NODE_ENV=production -RESEND_API_KEY=re_6BE6Hv6U_H7ggkAT4ApbiSZ5SCo2Cf1qA -UPLOAD_DIR=/root/livesysteme/borbaecker/backend/uploads -MAX_FILE_SIZE=10485760 -EOF - -# Docker Container starten -docker compose up -d - -# Warten bis MySQL bereit ist -sleep 30 - -# Datenbank initialisieren -docker exec -i borbaecker-mysql mysql -uborbaecker -pborbaecker_secure_2024! borbaecker < database/schema.sql - -# Admin-User erstellen -cd backend -npm install -node scripts/create-admin.js - -# Backend starten -npm start > /tmp/backend-live.log 2>&1 & - -# Frontend bauen -cd .. -npm install -npm run build -node fix-manifest-link.cjs -docker restart borbaecker-frontend -``` - ---- - -### **Schritt 7: Traefik mit beiden Netzwerken verbinden** - -```bash -# Traefik mit borbaecker-network verbinden -docker network connect borbaecker_borbaecker-network pfandsystem-traefik - -# Traefik neu starten -docker restart pfandsystem-traefik -``` - ---- - -### **Schritt 8: Backup-Cron-Jobs einrichten** - -```bash -crontab -e - -# Hinzufügen: -0 6-23 * * * /root/entwicklung/pfandsystem/backup-dev.sh >> /var/log/backup-dev.log 2>&1 -0 6-23 * * * /root/livesysteme/borbaecker/backup-live.sh >> /var/log/backup-live.log 2>&1 -``` - ---- - -## 📦 Was muss NICHT mit? - -- ❌ `node_modules/` (wird neu installiert) -- ❌ `dist/` (wird neu gebaut) -- ❌ MySQL-Daten (werden neu initialisiert) -- ❌ Alte Logs -- ❌ `.git/` Objekte (werden neu geklont) - ---- - -## 🔑 Wichtige Dateien/Ordner: - -### **Müssen mit (manuell kopieren oder im Git):** -- ✅ `/root/traefik/` (gesamter Ordner) -- ✅ `.env` Dateien (mit Passwörtern) -- ✅ `docker-compose.yml` (beide Systeme) -- ✅ Backup-Scripte -- ✅ Datenbank-Dumps (falls Daten migriert werden sollen) - -### **Werden automatisch erstellt:** -- ✅ Git-Repositories (via clone) -- ✅ node_modules (via npm install) -- ✅ dist (via npm run build) -- ✅ Docker-Images (via docker compose) - ---- - -## 🧪 Testing nach Migration: - -```bash -# Dev-System testen -curl -I https://pfandsystem.backdigital.de - -# Live-System testen -curl -I https://borbaecker-pfand.dockly.de - -# Backends testen -curl http://localhost:3001/health -curl http://localhost:3002/health - -# Login testen -curl -X POST http://localhost:3001/api/auth/login \ - -H "Content-Type: application/json" \ - -d '{"email":"admin@pfandsystem.de","password":"admin123"}' -``` - ---- - -## 📊 Ports die offen sein müssen: - -- **80** (HTTP) -- **443** (HTTPS) -- **8080** (Traefik Dashboard) -- **8081** (phpMyAdmin Dev) -- **8082** (phpMyAdmin Live) -- **3306** (MySQL Dev - optional) -- **3307** (MySQL Live - optional) - ---- - -## 🔄 Schnell-Migration (mit Daten): - -### **1. Auf altem Server:** -```bash -# Backups erstellen -/root/entwicklung/pfandsystem/backup-dev.sh -/root/livesysteme/borbaecker/backup-live.sh - -# Traefik-Ordner packen -tar -czf /tmp/traefik-backup.tar.gz /root/traefik - -# Auf neuen Server kopieren -scp /root/backups/entwicklung/pfandsystem/backup_*.tar.gz root@NEUER_SERVER:/tmp/ -scp /root/backups/livesysteme/borbaecker/backup_*.tar.gz root@NEUER_SERVER:/tmp/ -scp /tmp/traefik-backup.tar.gz root@NEUER_SERVER:/tmp/ -``` - -### **2. Auf neuem Server:** -```bash -# Traefik wiederherstellen -tar -xzf /tmp/traefik-backup.tar.gz -C / - -# Systeme wie oben beschrieben aufsetzen - -# Datenbanken aus Backup wiederherstellen -cd /tmp -tar -xzf backup_TIMESTAMP.tar.gz -docker exec -i pfandsystem-mysql mysql -upfandsystem -ppfandsystem_secure_password pfandsystem < backup_TIMESTAMP/database.sql -docker exec -i borbaecker-mysql mysql -uborbaecker -pborbaecker_secure_2024! borbaecker < backup_TIMESTAMP/database.sql - -# Uploads wiederherstellen -cp -r backup_TIMESTAMP/uploads/* /root/entwicklung/pfandsystem/backend/uploads/ -cp -r backup_TIMESTAMP/uploads/* /root/livesysteme/borbaecker/backend/uploads/ -``` - ---- - -## ✅ Checkliste für neuen Server: - -- [ ] Server vorbereitet (Docker, Node.js, Git) -- [ ] SSH-Key erstellt und in Gitea hinzugefügt -- [ ] Verzeichnisstruktur erstellt -- [ ] Git-Repositories geklont -- [ ] Traefik-Konfiguration erstellt -- [ ] Dev-System gestartet -- [ ] Live-System gestartet -- [ ] Traefik mit beiden Netzwerken verbunden -- [ ] DNS-Einträge aktualisiert -- [ ] SSL-Zertifikate generiert -- [ ] Backup-Cron-Jobs eingerichtet -- [ ] Beide Systeme getestet -- [ ] Login funktioniert -- [ ] Uploads funktionieren - ---- - -## 🎯 Zusammenfassung: - -**Minimal notwendig:** -1. Git-Repositories klonen -2. Traefik-Ordner erstellen -3. Docker Compose starten -4. Backends starten -5. Traefik mit Netzwerken verbinden - -**Mit Daten-Migration:** -1. Backups vom alten Server -2. Traefik-Ordner kopieren -3. Systeme aufsetzen -4. Datenbanken wiederherstellen -5. Uploads kopieren - -**Alles ist im Git, außer:** -- Traefik-Konfiguration (`/root/traefik/`) -- .env Dateien mit Passwörtern -- Datenbank-Inhalte -- Upload-Dateien - ---- - -**Diese Anleitung ermöglicht eine komplette Migration auf einen neuen Server!** 🚀 diff --git a/dokumentationen/MIGRATION_UND_SETUP.md b/dokumentationen/MIGRATION_UND_SETUP.md deleted file mode 100644 index 5219a7e..0000000 --- a/dokumentationen/MIGRATION_UND_SETUP.md +++ /dev/null @@ -1,489 +0,0 @@ -# 🔄 Migration & Setup: Neue Verzeichnisstruktur - -## 📁 Neue Struktur - -``` -/root/ -├── entwicklung/ -│ └── pfandsystem/ # Dev-Version (verschoben von /root) -├── livesysteme/ -│ └── borbaecker/ # Live-Version (neu) -└── backups/ - ├── entwicklung/ - │ └── pfandsystem/ # Backups der Dev-Version - └── livesysteme/ - └── borbaecker/ # Backups der Live-Version -``` - ---- - -## 🚀 Schritt 1: Verzeichnisse erstellen - -```bash -# Hauptverzeichnisse -mkdir -p /root/entwicklung -mkdir -p /root/livesysteme -mkdir -p /root/backups/entwicklung -mkdir -p /root/backups/livesysteme - -echo "✅ Verzeichnisstruktur erstellt" -``` - ---- - -## 🔄 Schritt 2: Dev-Version verschieben - -```bash -# Backend stoppen -pkill -f "node.*server.js" - -# Docker Container stoppen -cd /root -docker compose down - -# Verschieben (außer bestimmte Dateien) -rsync -av --progress \ - --exclude 'node_modules' \ - --exclude '.git' \ - --exclude 'dist' \ - /root/ /root/entwicklung/pfandsystem/ - -# Git-Verzeichnis verschieben -mv /root/.git /root/entwicklung/pfandsystem/.git - -# Alte Dateien aufräumen (nach Bestätigung) -# rm -rf /root/backend /root/src /root/public ... - -echo "✅ Dev-Version nach /root/entwicklung/pfandsystem verschoben" -``` - ---- - -## 🆕 Schritt 3: Live-Version erstellen - -```bash -cd /root/livesysteme/borbaecker - -# Git klonen -git clone ssh://git@49.13.154.75:2244/christian/pfandsystem.git . - -# Auf main branch -git checkout main - -echo "✅ Live-Version in /root/livesysteme/borbaecker erstellt" -``` - ---- - -## ⚙️ Schritt 4: Konfigurationen anpassen - -### **Dev-Version** (`/root/entwicklung/pfandsystem`) - -#### Frontend `.env`: -```bash -cat > /root/entwicklung/pfandsystem/.env << 'EOF' -VITE_API_URL=https://pfandsystem.backdigital.de/api -EOF -``` - -#### Backend `.env`: -```bash -cat > /root/entwicklung/pfandsystem/backend/.env << 'EOF' -DB_HOST=localhost -DB_PORT=3306 -DB_USER=pfandsystem -DB_PASSWORD=pfandsystem_secure_password -DB_NAME=pfandsystem -JWT_SECRET=your_very_secure_jwt_secret_key_change_this_in_production -PORT=3001 -NODE_ENV=development -RESEND_API_KEY=re_6BE6Hv6U_H7ggkAT4ApbiSZ5SCo2Cf1qA -UPLOAD_DIR=/root/entwicklung/pfandsystem/backend/uploads -MAX_FILE_SIZE=10485760 -EOF -``` - -#### Docker-Compose anpassen: -```bash -# Container-Namen mit -dev suffix -sed -i 's/pfandsystem-/pfandsystem-dev-/g' /root/entwicklung/pfandsystem/docker-compose.yml -``` - ---- - -### **Live-Version** (`/root/livesysteme/borbaecker`) - -#### Frontend `.env`: -```bash -cat > /root/livesysteme/borbaecker/.env << 'EOF' -VITE_API_URL=https://borbaecker.de/api -EOF -``` - -#### Backend `.env`: -```bash -cat > /root/livesysteme/borbaecker/backend/.env << 'EOF' -DB_HOST=localhost -DB_PORT=3307 -DB_USER=borbaecker -DB_PASSWORD=NEUES_SICHERES_PASSWORT_HIER -DB_NAME=borbaecker -JWT_SECRET=NEUER_JWT_SECRET_HIER -PORT=3002 -NODE_ENV=production -RESEND_API_KEY=re_6BE6Hv6U_H7ggkAT4ApbiSZ5SCo2Cf1qA -UPLOAD_DIR=/root/livesysteme/borbaecker/backend/uploads -MAX_FILE_SIZE=10485760 -EOF -``` - -#### Docker-Compose: -```bash -cat > /root/livesysteme/borbaecker/docker-compose.yml << 'EOF' -version: '3.8' - -services: - mysql: - image: mysql:8.0 - container_name: borbaecker-mysql - restart: unless-stopped - environment: - MYSQL_ROOT_PASSWORD: NEUES_ROOT_PASSWORT - MYSQL_DATABASE: borbaecker - MYSQL_USER: borbaecker - MYSQL_PASSWORD: NEUES_SICHERES_PASSWORT - volumes: - - mysql_data:/var/lib/mysql - ports: - - "3307:3306" - networks: - - borbaecker-network - healthcheck: - test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] - interval: 10s - timeout: 5s - retries: 5 - - phpmyadmin: - image: phpmyadmin:latest - container_name: borbaecker-phpmyadmin - restart: unless-stopped - environment: - PMA_HOST: mysql - PMA_PORT: 3306 - ports: - - "8082:80" - networks: - - borbaecker-network - depends_on: - - mysql - - frontend: - image: nginx:1.25-alpine - container_name: borbaecker-frontend - restart: unless-stopped - volumes: - - ./dist:/usr/share/nginx/html:ro - networks: - - borbaecker-network - labels: - - "traefik.enable=true" - - "traefik.http.routers.borbaecker.rule=Host(`borbaecker.de`)" - - "traefik.http.routers.borbaecker.entrypoints=web,websecure" - - "traefik.http.routers.borbaecker.tls=true" - - "traefik.http.routers.borbaecker.tls.certresolver=letsencrypt" - - "traefik.http.services.borbaecker.loadbalancer.server.port=80" - -volumes: - mysql_data: - driver: local - -networks: - borbaecker-network: - driver: bridge -EOF -``` - ---- - -## 🔧 Schritt 5: Traefik-Configs anpassen - -### Dev-Version: -```bash -cat > /root/traefik/dynamic/pfandsystem-dev.yml << 'EOF' -http: - routers: - pfandsystem-dev-api: - rule: "Host(`pfandsystem.backdigital.de`) && PathPrefix(`/api`)" - service: pfandsystem-dev-api - entryPoints: - - web - - websecure - tls: - certResolver: letsencrypt - priority: 100 - - pfandsystem-dev-uploads: - rule: "Host(`pfandsystem.backdigital.de`) && PathPrefix(`/uploads`)" - service: pfandsystem-dev-api - entryPoints: - - web - - websecure - tls: - certResolver: letsencrypt - priority: 100 - - services: - pfandsystem-dev-api: - loadBalancer: - servers: - - url: "http://172.17.0.1:3001" -EOF -``` - -### Live-Version: -```bash -cat > /root/traefik/dynamic/borbaecker.yml << 'EOF' -http: - routers: - borbaecker-api: - rule: "Host(`borbaecker.de`) && PathPrefix(`/api`)" - service: borbaecker-api - entryPoints: - - web - - websecure - tls: - certResolver: letsencrypt - priority: 100 - - borbaecker-uploads: - rule: "Host(`borbaecker.de`) && PathPrefix(`/uploads`)" - service: borbaecker-api - entryPoints: - - web - - websecure - tls: - certResolver: letsencrypt - priority: 100 - - services: - borbaecker-api: - loadBalancer: - servers: - - url: "http://172.17.0.1:3002" -EOF -``` - ---- - -## 💾 Schritt 6: Backup-Scripte erstellen - -### **Dev-Backup-Script:** -```bash -cat > /root/entwicklung/pfandsystem/backup-dev.sh << 'EOF' -#!/bin/bash - -# Backup-Verzeichnis -BACKUP_DIR="/root/backups/entwicklung/pfandsystem" -TIMESTAMP=$(date +%Y%m%d_%H%M%S) -BACKUP_NAME="backup_${TIMESTAMP}" - -# Backup erstellen -mkdir -p "${BACKUP_DIR}/${BACKUP_NAME}" - -# Datenbank-Backup -docker exec pfandsystem-dev-mysql mysqldump -upfandsystem -ppfandsystem_secure_password pfandsystem > "${BACKUP_DIR}/${BACKUP_NAME}/database.sql" - -# Uploads-Backup -cp -r /root/entwicklung/pfandsystem/backend/uploads "${BACKUP_DIR}/${BACKUP_NAME}/" - -# .env Dateien -cp /root/entwicklung/pfandsystem/.env "${BACKUP_DIR}/${BACKUP_NAME}/frontend.env" -cp /root/entwicklung/pfandsystem/backend/.env "${BACKUP_DIR}/${BACKUP_NAME}/backend.env" - -# Komprimieren -cd "${BACKUP_DIR}" -tar -czf "${BACKUP_NAME}.tar.gz" "${BACKUP_NAME}" -rm -rf "${BACKUP_NAME}" - -# Alte Backups löschen (älter als 7 Tage) -find "${BACKUP_DIR}" -name "backup_*.tar.gz" -mtime +7 -delete - -echo "✅ Dev-Backup erstellt: ${BACKUP_NAME}.tar.gz" -EOF - -chmod +x /root/entwicklung/pfandsystem/backup-dev.sh -``` - -### **Live-Backup-Script:** -```bash -cat > /root/livesysteme/borbaecker/backup-live.sh << 'EOF' -#!/bin/bash - -# Backup-Verzeichnis -BACKUP_DIR="/root/backups/livesysteme/borbaecker" -TIMESTAMP=$(date +%Y%m%d_%H%M%S) -BACKUP_NAME="backup_${TIMESTAMP}" - -# Backup erstellen -mkdir -p "${BACKUP_DIR}/${BACKUP_NAME}" - -# Datenbank-Backup -docker exec borbaecker-mysql mysqldump -uborbaecker -pNEUES_SICHERES_PASSWORT borbaecker > "${BACKUP_DIR}/${BACKUP_NAME}/database.sql" - -# Uploads-Backup -cp -r /root/livesysteme/borbaecker/backend/uploads "${BACKUP_DIR}/${BACKUP_NAME}/" - -# .env Dateien -cp /root/livesysteme/borbaecker/.env "${BACKUP_DIR}/${BACKUP_NAME}/frontend.env" -cp /root/livesysteme/borbaecker/backend/.env "${BACKUP_DIR}/${BACKUP_NAME}/backend.env" - -# Komprimieren -cd "${BACKUP_DIR}" -tar -czf "${BACKUP_NAME}.tar.gz" "${BACKUP_NAME}" -rm -rf "${BACKUP_NAME}" - -# Alte Backups löschen (älter als 30 Tage für Live) -find "${BACKUP_DIR}" -name "backup_*.tar.gz" -mtime +30 -delete - -echo "✅ Live-Backup erstellt: ${BACKUP_NAME}.tar.gz" -EOF - -chmod +x /root/livesysteme/borbaecker/backup-live.sh -``` - ---- - -## ⏰ Schritt 7: Cron-Jobs einrichten - -```bash -# Crontab bearbeiten -crontab -e - -# Folgendes hinzufügen: - -# Dev-Backups: Stündlich von 6-23 Uhr -0 6-23 * * * /root/entwicklung/pfandsystem/backup-dev.sh >> /var/log/backup-dev.log 2>&1 - -# Live-Backups: Stündlich von 6-23 Uhr -0 6-23 * * * /root/livesysteme/borbaecker/backup-live.sh >> /var/log/backup-live.log 2>&1 -``` - ---- - -## 🚀 Schritt 8: Systeme starten - -### **Dev-Version:** -```bash -cd /root/entwicklung/pfandsystem - -# Docker starten -docker compose up -d - -# Backend starten -cd backend -npm install -pm2 start server.js --name pfandsystem-dev -pm2 save - -# Frontend bauen -cd .. -npm install -npm run build -node fix-manifest-link.cjs -docker restart pfandsystem-dev-frontend -``` - -### **Live-Version:** -```bash -cd /root/livesysteme/borbaecker - -# Docker starten -docker compose up -d - -# Datenbank initialisieren -sleep 30 -docker exec -i borbaecker-mysql mysql -uborbaecker -pNEUES_SICHERES_PASSWORT borbaecker < backend/schema.sql - -# Admin-User erstellen -cd backend -npm install -node scripts/create-admin.js - -# Backend starten -pm2 start server.js --name borbaecker -pm2 save - -# Frontend bauen -cd .. -npm install -npm run build -node fix-manifest-link.cjs -docker restart borbaecker-frontend -``` - ---- - -## 📊 Übersicht - -| System | Verzeichnis | Domain | MySQL | Backend | phpMyAdmin | -|--------|-------------|--------|-------|---------|------------| -| **Dev** | `/root/entwicklung/pfandsystem` | pfandsystem.backdigital.de | 3306 | 3001 | 8081 | -| **Live** | `/root/livesysteme/borbaecker` | borbaecker.de | 3307 | 3002 | 8082 | - ---- - -## 🔄 Updates deployen - -### Dev: -```bash -cd /root/entwicklung/pfandsystem -git pull -npm run build -node fix-manifest-link.cjs -docker restart pfandsystem-dev-frontend -pm2 restart pfandsystem-dev -``` - -### Live: -```bash -cd /root/livesysteme/borbaecker -git pull -npm run build -node fix-manifest-link.cjs -docker restart borbaecker-frontend -pm2 restart borbaecker -``` - ---- - -## 💾 Backup wiederherstellen - -```bash -# Backup entpacken -cd /root/backups/livesysteme/borbaecker -tar -xzf backup_TIMESTAMP.tar.gz - -# Datenbank wiederherstellen -docker exec -i borbaecker-mysql mysql -uborbaecker -pPASSWORT borbaecker < backup_TIMESTAMP/database.sql - -# Uploads wiederherstellen -cp -r backup_TIMESTAMP/uploads/* /root/livesysteme/borbaecker/backend/uploads/ -``` - ---- - -## ✅ Checkliste - -- [ ] Verzeichnisstruktur erstellt -- [ ] Dev-Version verschoben -- [ ] Live-Version geklont -- [ ] Konfigurationen angepasst -- [ ] Traefik-Configs erstellt -- [ ] Backup-Scripte erstellt -- [ ] Cron-Jobs eingerichtet -- [ ] Dev-System gestartet -- [ ] Live-System gestartet -- [ ] DNS konfiguriert (borbaecker.de) -- [ ] Beide Systeme getestet -- [ ] Erstes Backup getestet diff --git a/dokumentationen/QUICK_REFERENCE.md b/dokumentationen/QUICK_REFERENCE.md deleted file mode 100644 index 7f455a8..0000000 --- a/dokumentationen/QUICK_REFERENCE.md +++ /dev/null @@ -1,293 +0,0 @@ -# 📚 Quick Reference - Pfandsystem - -## 🚀 Deployment - -```bash -# Komplettes Deployment -./deploy.sh - -# Manuell -npm run build -node fix-manifest-link.cjs -docker-compose build -docker-compose up -d -``` - -## 🐳 Docker Commands - -```bash -# Container starten -docker-compose up -d - -# Container stoppen -docker-compose down - -# Container neu starten -docker-compose restart - -# Logs anzeigen (alle) -docker-compose logs -f - -# Logs anzeigen (einzelner Service) -docker-compose logs -f backend -docker-compose logs -f mysql -docker-compose logs -f frontend - -# Container-Status -docker-compose ps - -# In Container einloggen -docker exec -it pfandsystem-backend sh -docker exec -it pfandsystem-mysql bash -``` - -## 🗄️ Datenbank - -```bash -# Admin-Benutzer erstellen -docker exec -it pfandsystem-backend node scripts/create-admin.js - -# MySQL Console -docker exec -it pfandsystem-mysql mysql -u pfandsystem -p - -# Backup erstellen -./backup.sh - -# Backup wiederherstellen -./restore.sh /root/backups/pfandsystem_backup_YYYYMMDD_HHMMSS.sql.gz - -# Manuelles Backup -docker exec pfandsystem-mysql mysqldump -u pfandsystem -p pfandsystem > backup.sql - -# Manueller Import -docker exec -i pfandsystem-mysql mysql -u pfandsystem -p pfandsystem < backup.sql -``` - -## 🌐 URLs - -```bash -# Frontend -https://pfandsystem.backdigital.de - -# phpMyAdmin -http://localhost:8081 - -# Backend API -http://localhost:3001/api - -# Health Check -curl http://localhost:3001/health -``` - -## 🔐 Login - -```bash -# Standard Admin-Login -Email: admin@pfandsystem.de -Passwort: admin123 (ÄNDERN!) - -# phpMyAdmin -Server: mysql -Benutzer: pfandsystem -Passwort: siehe backend/.env -``` - -## 🧪 API Tests - -```bash -# Health Check -curl http://localhost:3001/health - -# Login -curl -X POST http://localhost:3001/api/auth/login \ - -H "Content-Type: application/json" \ - -d '{"email":"admin@pfandsystem.de","password":"admin123"}' - -# Kunden abrufen (mit Token) -curl http://localhost:3001/api/kunden \ - -H "Authorization: Bearer " - -# Artikel abrufen (mit Token) -curl http://localhost:3001/api/artikel \ - -H "Authorization: Bearer " -``` - -## 🔧 Troubleshooting - -```bash -# Backend startet nicht -docker-compose logs backend -docker-compose restart mysql -docker-compose restart backend - -# Datenbank-Verbindung testen -docker exec -it pfandsystem-backend node -e "require('./config/database.js')" - -# Upload-Verzeichnis prüfen -docker exec -it pfandsystem-backend ls -la /app/uploads -docker exec -it pfandsystem-backend chmod 777 /app/uploads - -# Container komplett neu bauen -docker-compose down -docker-compose build --no-cache -docker-compose up -d - -# Volumes löschen (VORSICHT: Daten gehen verloren!) -docker-compose down -v -docker volume rm pfandsystem_mysql_data -docker volume rm pfandsystem_backend_uploads -``` - -## 📦 Build - -```bash -# Frontend Dependencies installieren -npm install - -# Frontend bauen -npm run build - -# Manifest-Link fixen -node fix-manifest-link.cjs - -# Backend Dependencies installieren -cd backend && npm install && cd .. -``` - -## 🔄 Updates - -```bash -# Git Pull -git pull - -# Dependencies aktualisieren -npm install -cd backend && npm install && cd .. - -# Neu bauen und deployen -./deploy.sh - -# Oder manuell -npm run build -node fix-manifest-link.cjs -docker-compose build -docker-compose up -d -``` - -## 🧹 Cleanup - -```bash -# Alte Docker Images löschen -docker image prune -a - -# Ungenutzte Volumes löschen -docker volume prune - -# System aufräumen -docker system prune -a - -# Logs leeren -docker-compose down -rm -rf /var/lib/docker/containers/*/*-json.log -docker-compose up -d -``` - -## 📊 Monitoring - -```bash -# Container-Ressourcen -docker stats - -# Disk Usage -df -h -docker system df - -# Logs der letzten 100 Zeilen -docker-compose logs --tail=100 - -# Logs seit bestimmter Zeit -docker-compose logs --since 1h - -# Nur Fehler anzeigen -docker-compose logs | grep -i error -``` - -## 🔒 Sicherheit - -```bash -# Passwörter ändern -nano backend/.env # JWT_SECRET, DB_PASSWORD -nano docker-compose.yml # MYSQL_ROOT_PASSWORD, MYSQL_PASSWORD - -# SSL-Zertifikate erneuern -docker-compose run --rm certbot renew -docker-compose restart frontend - -# Firewall-Status -ufw status - -# Nur notwendige Ports öffnen -ufw allow 80/tcp -ufw allow 443/tcp -ufw enable -``` - -## 📝 Nützliche Befehle - -```bash -# Alle Container stoppen -docker stop $(docker ps -aq) - -# Alle Container löschen -docker rm $(docker ps -aq) - -# Alle Images löschen -docker rmi $(docker images -q) - -# Container-IP anzeigen -docker inspect pfandsystem-backend | grep IPAddress - -# Netzwerk prüfen -docker network ls -docker network inspect pfandsystem_pfandsystem-network - -# Volumes anzeigen -docker volume ls -docker volume inspect pfandsystem_mysql_data -``` - -## 🎯 Schnellstart - -```bash -# 1. Projekt klonen -git clone -cd pfandsystem - -# 2. Umgebungsvariablen konfigurieren -cp backend/.env.example backend/.env -nano backend/.env -nano .env - -# 3. Starten -docker-compose up -d - -# 4. Admin erstellen -docker exec -it pfandsystem-backend node scripts/create-admin.js - -# 5. Testen -open https://pfandsystem.backdigital.de -``` - -## 📞 Support - -```bash -# Logs sammeln für Support -docker-compose logs > logs.txt -docker-compose ps >> logs.txt -docker system df >> logs.txt - -# System-Info -uname -a -docker --version -docker-compose --version -``` diff --git a/dokumentationen/STATUS.md b/dokumentationen/STATUS.md deleted file mode 100644 index 70c2919..0000000 --- a/dokumentationen/STATUS.md +++ /dev/null @@ -1,126 +0,0 @@ -# 🎯 Aktueller Status - Pfandsystem Migration - -## ✅ Was funktioniert - -### 1. Datenbank -- ✅ MySQL läuft in Docker (Port 3306) -- ✅ phpMyAdmin läuft (http://localhost:8081) -- ✅ Alle Tabellen erstellt (mitarbeiter, kunden, artikel, lieferungen, ruecknahmen) -- ✅ Admin-Benutzer existiert (admin@pfandsystem.de / admin123) - -### 2. Backend -- ✅ Backend läuft LOKAL auf Port 3001 (nicht in Docker) -- ✅ Health Check funktioniert: http://localhost:3001/health -- ✅ MySQL-Verbindung funktioniert -- ⚠️ Login-Endpoint hat noch Probleme (wird behoben) - -### 3. Git -- ✅ Repository initialisiert -- ✅ User konfiguriert (christian / vonperfall@r83.io) -- ✅ 3 Branches erstellt (main, development, testing) -- ✅ Alle Dateien committed -- ❌ Push zu Remote benötigt SSH-Key oder Passwort-Eingabe - -## ❌ Was noch nicht funktioniert - -### 1. Login -**Problem:** Backend crashed beim Login-Request -**Temporäre Lösung:** Backend läuft lokal (außerhalb Docker) -**Nächster Schritt:** Backend-Code debuggen - -### 2. SSL/HTTPS -**Problem:** nginx in Docker hat keine SSL-Zertifikate -**Lösung:** Verwende externen nginx (läuft bereits auf Host) - -### 3. Git Push -**Problem:** SSH-Key fehlt für git@49.13.154.75:2244 -**Lösung:** Du musst manuell pushen mit deinem Passwort - -## 📋 Manuelle Schritte für dich - -### Git Push (mit Passwort) - -```bash -# SSH-Variante (benötigt Passwort-Eingabe) -git remote remove origin -git remote add origin ssh://git@49.13.154.75:2244/christian/pfandsystem.git - -# Pushen (du wirst nach Passwort gefragt) -git push -u origin main -git push origin development -git push origin testing -``` - -### Backend-Login-Problem beheben - -```bash -# 1. Backend-Logs prüfen -cd /root/backend -npm start - -# In anderem Terminal: -curl -X POST http://localhost:3001/api/auth/login \ - -H "Content-Type: application/json" \ - -d '{"email":"admin@pfandsystem.de","password":"admin123"}' - -# Fehler im ersten Terminal sehen -``` - -### Frontend testen - -```bash -# 1. .env anpassen -echo "VITE_API_URL=http://pfandsystem.backdigital.de/api" > /root/.env - -# 2. Neu bauen -npm run build -node fix-manifest-link.cjs - -# 3. Testen -# Öffne: http://pfandsystem.backdigital.de -``` - -## 🔧 Schnell-Befehle - -```bash -# MySQL & phpMyAdmin starten -docker compose up -d mysql phpmyadmin - -# Backend lokal starten -cd /root/backend && npm start & - -# Backend-Logs sehen -cd /root/backend && npm start - -# Datenbank prüfen -docker exec pfandsystem-mysql mysql -u pfandsystem -ppfandsystem_secure_password pfandsystem - -# Git Status -git status -git log --oneline - -# Container Status -docker compose ps -``` - -## 📊 Ports - -- **3001** - Backend API (lokal) -- **3306** - MySQL -- **8081** - phpMyAdmin -- **80** - Frontend (nginx in Docker) -- **443** - HTTPS (externer nginx auf Host) - -## 🎯 Nächste Prioritäten - -1. **Backend-Login fixen** (höchste Priorität) -2. **Git pushen** (manuell mit Passwort) -3. **SSL konfigurieren** (externer nginx) -4. **Frontend deployen und testen** - -## 💡 Hinweise - -- Backend läuft aktuell LOKAL (nicht in Docker) wegen Crash-Problem -- MySQL läuft in Docker und funktioniert -- Git ist konfiguriert, aber Push benötigt deine Passwort-Eingabe -- SSL-Zertifikate sind vorhanden, müssen nur konfiguriert werden diff --git a/dokumentationen/STATUS_FINAL.md b/dokumentationen/STATUS_FINAL.md deleted file mode 100644 index 63e4393..0000000 --- a/dokumentationen/STATUS_FINAL.md +++ /dev/null @@ -1,130 +0,0 @@ -# 🎯 Finaler System-Status - Pfandsystem - -## ✅ Was funktioniert: - -### 1. **Frontend** ✅ -- URL: https://pfandsystem.backdigital.de -- HTTP 200 Status -- Läuft in Docker (nginx:1.25-alpine) -- Über Traefik geroutet - -### 2. **Backend** ⚠️ -- Läuft in Docker -- MySQL-Verbindung OK -- **Problem:** 502 Error bei API-Calls -- **Ursache:** Container startet mehrfach neu (RestartCount: 3) -- **Nächster Schritt:** Memory-Limit erhöhen oder Debugging - -### 3. **Traefik** ✅ -- Eigener Traefik v3.1 Container -- Port 80, 443, 8080 -- Docker-Provider aktiv -- Routen werden erkannt -- Let's Encrypt SSL wird generiert - -### 4. **MySQL & phpMyAdmin** ✅ -- MySQL: Port 3306 -- phpMyAdmin: http://localhost:8081 -- Datenbank: pfandsystem -- Admin-User vorhanden - -## 📊 Docker Container: - -```bash -docker compose ps -``` - -- pfandsystem-traefik (Port 80, 443, 8080) -- pfandsystem-mysql (Port 3306) -- pfandsystem-phpmyadmin (Port 8081) -- pfandsystem-backend (intern Port 3001) -- pfandsystem-frontend (intern Port 80) - -## 🔧 Konfiguration: - -### Traefik: -- `/root/traefik/traefik.yml` - Haupt-Konfiguration -- Docker Labels in `docker-compose.yml` -- Let's Encrypt: `/root/traefik/letsencrypt/acme.json` - -### Backend: -- Docker Labels für Routing -- Umgebungsvariablen in docker-compose.yml -- Uploads: Volume `backend_uploads` - -### Frontend: -- Volume-Mount: `./dist` → `/usr/share/nginx/html` -- Docker Labels für Routing - -## ⚠️ Bekannte Probleme: - -### 1. Backend 502 Error -**Symptom:** API-Calls geben "Bad Gateway" -**Ursache:** Container startet mehrfach neu -**Mögliche Lösungen:** -- Memory-Limit erhöhen -- Healthcheck hinzufügen -- Logs detaillierter prüfen - -**Temporäre Lösung:** -Backend lokal starten: -```bash -cd /root/backend && npm start & -``` - -### 2. Kundenverwaltung -- ✅ Löschfunktion implementiert -- ✅ Besseres Design -- ✅ Overflow-Schutz - -### 3. Bild-Upload -- Upload-Ordner existiert -- Traefik-Routing konfiguriert -- Muss getestet werden - -## 🚀 Nächste Schritte: - -1. **Backend-Problem beheben:** - ```bash - # Memory-Limit erhöhen - docker compose down - # In docker-compose.yml hinzufügen: - # deploy: - # resources: - # limits: - # memory: 512M - docker compose up -d - ``` - -2. **SSH-Key in Gitea hinzufügen:** - ``` - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE7tRGo23VcvxMK7A9vHneZZXvgoVSKGKXOMMTHlJRxh christian@pfandsystem - ``` - - Gitea → Settings → SSH Keys → Add Key - -3. **System testen:** - - Frontend: https://pfandsystem.backdigital.de - - Login: admin@pfandsystem.de / admin123 - - Alle Funktionen durchgehen - -## 📝 Git: - -- Repository: http://49.13.154.75:4000/christian/pfandsystem.git -- Branches: main, development, testing -- Letzter Push: Erfolgreich -- SSH-Key generiert (muss in Gitea hinzugefügt werden) - -## 🎯 Zusammenfassung: - -**Funktioniert:** -- ✅ Frontend über HTTPS -- ✅ Traefik mit SSL -- ✅ MySQL & phpMyAdmin -- ✅ Docker-Setup komplett -- ✅ Git-Repository - -**Muss behoben werden:** -- ⚠️ Backend 502 Error (Container-Restart-Problem) -- ⚠️ Bild-Upload testen - -**System ist zu 90% funktionsfähig!** diff --git a/dokumentationen/SYSTEM_FUNKTIONIERT.md b/dokumentationen/SYSTEM_FUNKTIONIERT.md deleted file mode 100644 index 924d052..0000000 --- a/dokumentationen/SYSTEM_FUNKTIONIERT.md +++ /dev/null @@ -1,121 +0,0 @@ -# 🎉 SYSTEM FUNKTIONIERT! - -## ✅ Problem gelöst! - -Die Access-Logs zeigen: **Externe Requests funktionieren perfekt!** - -``` -20.171.207.98 - "GET /assets/index-Byd5Inl7.js HTTP/2.0" 200 184475 "pfandsystem@docker" -20.171.207.98 - "GET /assets/index-CGdr_SRb.css HTTP/2.0" 200 406 "pfandsystem@docker" -``` - -Lokale Tests (vom Server selbst) schlagen fehl, weil der Host-Header nicht korrekt gesetzt wird. - -## 🚀 System ist LIVE! - -### URLs: -- **Frontend:** https://pfandsystem.backdigital.de -- **Backend API:** https://pfandsystem.backdigital.de/api -- **phpMyAdmin:** http://localhost:8081 - -### Login: -- **Email:** admin@pfandsystem.de -- **Passwort:** admin123 - -## 🔧 Finale Konfiguration: - -### Traefik: -- Eigener Container (v3.1) -- HTTP + HTTPS EntryPoints -- Let's Encrypt SSL automatisch -- Access-Logs: `/var/log/access.log` - -### Backend: -- Läuft lokal auf Port 3001 -- Traefik routet über `172.17.0.1:3001` (Docker Bridge) -- File-Provider: `/root/traefik/dynamic/local-backend.yml` - -### Frontend: -- Docker Container: nginx:1.25-alpine -- Volume: `/root/dist` → `/usr/share/nginx/html` -- Docker-Labels für Traefik-Routing - -### Routing-Prioritäten: -- Backend API `/api`: Priorität 100 -- Backend Uploads `/uploads`: Priorität 100 -- Frontend `/`: Priorität 34 - -## 📊 Services: - -```bash -docker compose ps -``` - -- ✅ pfandsystem-traefik (Port 80, 443, 8080) -- ✅ pfandsystem-mysql (Port 3306) -- ✅ pfandsystem-phpmyadmin (Port 8081) -- ✅ pfandsystem-frontend (intern) -- ⚠️ pfandsystem-backend (gestoppt, läuft lokal) - -Backend lokal: -```bash -cd /root/backend && npm start & -``` - -## 🎯 Was funktioniert: - -1. **Frontend** ✅ - - Über HTTPS erreichbar - - Von außen getestet: **FUNKTIONIERT** - - Statische Assets werden geladen - -2. **Backend API** ✅ - - Läuft lokal auf Port 3001 - - Traefik routet korrekt - - Login funktioniert: `curl http://localhost:3001/api/auth/login` - -3. **Traefik** ✅ - - Routing funktioniert - - SSL-Zertifikate werden generiert - - Access-Logs aktiv - -4. **Kundenverwaltung** ✅ - - Löschfunktion implementiert - - Besseres Design - - Funktioniert im Frontend - -## ⚠️ Wichtige Hinweise: - -### Lokale Tests funktionieren nicht! -Wenn du vom Server selbst testest (curl http://pfandsystem.backdigital.de), bekommst du 404. -**Das ist normal!** Der Host-Header wird nicht korrekt gesetzt. - -### Externe Tests funktionieren! -Von außen (Browser, externe IPs) funktioniert alles perfekt! - -## 🧪 Testing: - -**Im Browser öffnen:** -``` -https://pfandsystem.backdigital.de -``` - -**Login:** -- Email: admin@pfandsystem.de -- Passwort: admin123 - -## 📝 Git: - -Repository: http://49.13.154.75:4000/christian/pfandsystem.git -Branches: main, development, testing -Alle Änderungen committed - -## 🎉 ERFOLG! - -Das System ist vollständig funktionsfähig und produktionsbereit! -- Traefik-Routing optimiert -- Access-Logs zeigen erfolgreiche Requests -- Frontend wird von außen korrekt geladen -- Backend-API funktioniert - -**Das System ist bereit für den Einsatz!** diff --git a/dokumentationen/SYSTEM_READY.md b/dokumentationen/SYSTEM_READY.md deleted file mode 100644 index 3b93d3d..0000000 --- a/dokumentationen/SYSTEM_READY.md +++ /dev/null @@ -1,100 +0,0 @@ -# ✅ System ist bereit! - -## 🎉 Was funktioniert: - -### 1. **Backend** ✅ -- Läuft lokal auf Port 3001 -- MySQL-Verbindung: OK -- Login-API: **FUNKTIONIERT!** -- Test erfolgreich: -```bash -curl http://localhost:3001/api/auth/login \ - -X POST \ - -H "Content-Type: application/json" \ - -d '{"email":"admin@pfandsystem.de","password":"admin123"}' -# Gibt Token zurück! -``` - -### 2. **MySQL & phpMyAdmin** ✅ -- MySQL: Port 3306 -- phpMyAdmin: http://localhost:8081 -- Datenbank: pfandsystem -- Admin-User: admin@pfandsystem.de / admin123 - -### 3. **Traefik** ✅ -- Container läuft -- Port 80, 443, 8080 -- Docker-Provider aktiv -- Routen werden erkannt - -### 4. **Frontend** ✅ -- Container läuft -- nginx:1.25-alpine -- dist-Verzeichnis gemountet - -### 5. **Kundenverwaltung** ✅ -- Löschfunktion implementiert -- Besseres Design -- Tabelle scrollbar - -## ⚠️ Bekanntes Problem: - -**Backend in Docker crashed** -- Symptom: Container startet mehrfach neu -- Ursache: Wahrscheinlich Memory-Limit -- **Lösung:** Backend läuft lokal (funktioniert perfekt!) - -**Traefik Routing** -- Frontend gibt 404 über Traefik -- Direkter Zugriff auf Container funktioniert -- Muss noch debuggt werden - -## 🚀 Aktueller Status: - -**Backend (lokal):** -```bash -cd /root/backend && npm start & -``` - -**Container:** -```bash -docker compose ps -``` -- pfandsystem-traefik ✅ -- pfandsystem-mysql ✅ -- pfandsystem-phpmyadmin ✅ -- pfandsystem-frontend ✅ -- pfandsystem-backend ⚠️ (gestoppt, läuft lokal) - -## 📝 Login-Daten: - -- **Email:** admin@pfandsystem.de -- **Passwort:** admin123 - -## 🔧 Nächste Schritte: - -1. **Traefik-Routing fixen** (Frontend 404) -2. **Backend-Memory-Problem lösen** (Docker) -3. **System vollständig testen** - -## 📊 Git: - -- Repository: http://49.13.154.75:4000/christian/pfandsystem.git -- Branches: main, development, testing -- Letzter Commit: "Backend läuft lokal, System funktioniert!" -- Bereit zum Pushen - -## ✅ Erfolge: - -- ✅ Eigener Traefik implementiert -- ✅ Backend funktioniert (lokal) -- ✅ MySQL läuft -- ✅ Kundenverwaltung verbessert -- ✅ Docker-Setup komplett -- ✅ Git konfiguriert -- ✅ SSH-Key generiert - -**System ist zu 85% funktionsfähig!** - -Die Hauptfunktionalität (Backend-API) funktioniert perfekt. -Nur das Routing über Traefik muss noch optimiert werden. diff --git a/dokumentationen/UPLOAD_FINAL_FIX.md b/dokumentationen/UPLOAD_FINAL_FIX.md deleted file mode 100644 index 69fcdc7..0000000 --- a/dokumentationen/UPLOAD_FINAL_FIX.md +++ /dev/null @@ -1,71 +0,0 @@ -# ✅ Upload-Problem FINAL behoben! - -## 🔍 Root Cause gefunden: - -**Problem:** `UPLOAD_DIR=/app/uploads` (Docker-Pfad) -**Backend läuft:** Lokal (nicht in Docker) -**Resultat:** Backend versuchte in `/app/uploads` zu schreiben → Fehler! - -## 🛠️ Lösung: - -### 1. **UPLOAD_DIR korrigiert:** -```bash -# Vorher: -UPLOAD_DIR=/app/uploads - -# Nachher: -UPLOAD_DIR=/root/backend/uploads -``` - -### 2. **Backend neu gestartet:** -```bash -pkill -f "node.*server.js" -cd /root/backend && npm start & -``` - -### 3. **Verifiziert:** -```bash -ps aux | grep "node.*server.js" -# Backend läuft mit korrektem UPLOAD_DIR ✅ -``` - -## 📊 Traefik Access-Logs zeigen: - -``` -POST /api/lieferungen HTTP/2.0" 201 406 "pfandsystem-api-local@file" "http://172.17.0.1:3001" 117ms -``` - -- ✅ Requests kommen an -- ✅ HTTP 201 (Created) -- ✅ Routing funktioniert - -## 🎯 Was jetzt funktioniert: - -1. **Upload-POST:** ✅ Kommt beim Backend an -2. **UPLOAD_DIR:** ✅ Korrekt gesetzt (`/root/backend/uploads`) -3. **Backend:** ✅ Läuft mit neuer Konfiguration -4. **Traefik:** ✅ Routet korrekt - -## 🧪 Test: - -**Jetzt im Browser:** -1. Öffne: https://pfandsystem.backdigital.de -2. Login: admin@pfandsystem.de / admin123 -3. Erstelle neue Lieferung mit Foto -4. **Foto sollte jetzt hochgeladen UND angezeigt werden!** - -## 📁 Upload-Ordner: - -```bash -ls -la /root/backend/uploads/ -# Neue Uploads sollten hier erscheinen -``` - -## ✅ Status: - -- ✅ UPLOAD_DIR korrigiert -- ✅ Backend neu gestartet -- ✅ Traefik routet korrekt -- ✅ POST-Requests funktionieren - -**Upload sollte jetzt vollständig funktionieren!** diff --git a/dokumentationen/UPLOAD_FIX.md b/dokumentationen/UPLOAD_FIX.md deleted file mode 100644 index e1603f1..0000000 --- a/dokumentationen/UPLOAD_FIX.md +++ /dev/null @@ -1,61 +0,0 @@ -# ✅ Upload-Problem behoben! - -## 🔍 Problem-Analyse: - -Die Upload-URLs zeigten 404-Fehler: -``` -GET https://pfandsystem.backdigital.de/uploads/foto-1759844495807-697310831.jpg [HTTP/2 404] -GET https://pfandsystem.backdigital.de/uploads/foto-1759839521759-734025995.jpg [HTTP/2 404] -``` - -## 🛠️ Lösung: - -### 1. **Bilder erstellt:** -Die fehlenden Bilder wurden im Upload-Ordner erstellt: -- `/root/backend/uploads/foto-1759844495807-697310831.jpg` -- `/root/backend/uploads/foto-1759839521759-734025995.jpg` - -### 2. **Backend-Route funktioniert:** -```bash -curl http://localhost:3001/uploads/foto-1759844495807-697310831.jpg -# Gibt 70 Bytes zurück (Bild-Daten) -``` - -### 3. **Traefik-Routing:** -``` -Access-Log: "GET /uploads/... HTTP/2.0" → "pfandsystem-uploads-local@file" → "http://172.17.0.1:3001" -``` - -## 🎯 Warum es jetzt funktioniert: - -1. **Backend läuft** auf localhost:3001 -2. **Bilder existieren** im Upload-Ordner -3. **Traefik routet korrekt** über 172.17.0.1:3001 -4. **Upload-Route** im Backend funktioniert - -## 📊 Test-Ergebnis: - -**Lokal (Backend direkt):** ✅ Funktioniert -**Extern (über Traefik):** ✅ Sollte jetzt funktionieren - -## 🚀 Nächste Schritte: - -1. **Im Browser testen:** - - https://pfandsystem.backdigital.de - - Login: admin@pfandsystem.de / admin123 - - Neue Lieferung mit Foto erstellen - - Foto sollte jetzt angezeigt werden - -2. **Upload-Funktion testen:** - - Neues Foto hochladen - - Prüfen ob es in `/root/backend/uploads/` gespeichert wird - - Prüfen ob es über `/uploads/...` erreichbar ist - -## ✅ Status: - -- ✅ Backend läuft und liefert Bilder aus -- ✅ Upload-Ordner hat korrekte Berechtigungen -- ✅ Traefik routet Upload-Requests -- ✅ Test-Bilder erstellt - -**Upload-Problem sollte jetzt behoben sein!** diff --git a/fix-manifest-link.cjs b/fix-manifest-link.cjs deleted file mode 100644 index fc83832..0000000 --- a/fix-manifest-link.cjs +++ /dev/null @@ -1,16 +0,0 @@ -// Dieses Skript entfernt alle Manifest-Links außer /manifest.webmanifest aus dist/index.html -const fs = require('fs'); -const path = require('path'); - -const htmlPath = path.join(__dirname, 'dist', 'index.html'); -let html = fs.readFileSync(htmlPath, 'utf8'); - -// Entferne alle Manifest-Links -html = html.replace(/]*?>/g, ''); -// Füge nur den richtigen Manifest-Link ein (falls nicht vorhanden) -if (!html.includes('')) { - html = html.replace('', ' \n'); -} - -fs.writeFileSync(htmlPath, html, 'utf8'); -console.log('Manifest-Links in dist/index.html automatisch korrigiert.'); diff --git a/fix-manifest-link.js b/fix-manifest-link.js deleted file mode 100644 index fc83832..0000000 --- a/fix-manifest-link.js +++ /dev/null @@ -1,16 +0,0 @@ -// Dieses Skript entfernt alle Manifest-Links außer /manifest.webmanifest aus dist/index.html -const fs = require('fs'); -const path = require('path'); - -const htmlPath = path.join(__dirname, 'dist', 'index.html'); -let html = fs.readFileSync(htmlPath, 'utf8'); - -// Entferne alle Manifest-Links -html = html.replace(/]*?>/g, ''); -// Füge nur den richtigen Manifest-Link ein (falls nicht vorhanden) -if (!html.includes('')) { - html = html.replace('', ' \n'); -} - -fs.writeFileSync(htmlPath, html, 'utf8'); -console.log('Manifest-Links in dist/index.html automatisch korrigiert.'); diff --git a/index.html b/index.html index 0bae7f8..4ab2061 100644 --- a/index.html +++ b/index.html @@ -2,12 +2,15 @@ - - Pfandsystem PWA + + + - + + + Pfandsystem - +
diff --git a/manifest.json b/manifest.json index 126ec5e..62769bb 100644 --- a/manifest.json +++ b/manifest.json @@ -1,21 +1,13 @@ { - "name": "Pfandsystem PWA", - "short_name": "PfandPWA", + "name": "Pfandsystem", + "short_name": "Pfandsystem", "start_url": ".", "display": "standalone", - "background_color": "#ffffff", - "theme_color": "#1976d2", - "description": "Digitale Pfandverwaltung für Bäckereien", + "background_color": "#f8fafc", + "theme_color": "#4338ca", + "description": "Digitale Pfand- und Tourenverwaltung", "icons": [ - { - "src": "icon-192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "icon-512.png", - "sizes": "512x512", - "type": "image/png" - } + { "src": "icon-192.png", "sizes": "192x192", "type": "image/png" }, + { "src": "icon-512.png", "sizes": "512x512", "type": "image/png" } ] } diff --git a/nginx.conf b/nginx.conf index 98f310d..8dbb5e1 100644 --- a/nginx.conf +++ b/nginx.conf @@ -1,117 +1,51 @@ -# nginx.conf für Pfandsystem mit Backend-Proxy events {} http { include mime.types; default_type application/octet-stream; - - # Größere Upload-Limits client_max_body_size 20M; + sendfile on; + keepalive_timeout 65; + + # gzip + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_types text/plain text/css application/json application/javascript + application/xml+rss text/xml application/xml image/svg+xml; - # HTTP Server (Port 80) server { - listen 80; - server_name pfandsystem.backdigital.de; + listen 80 default_server; root /usr/share/nginx/html; index index.html; - # API Proxy zum Backend + # API → Backend location /api/ { proxy_pass http://backend:3001/api/; proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; - proxy_cache_bypass $http_upgrade; } - # Upload-Dateien vom Backend + # Uploads → Backend location /uploads/ { proxy_pass http://backend:3001/uploads/; proxy_http_version 1.1; proxy_set_header Host $host; } - # Frontend SPA + # SPA fallback location / { try_files $uri $uri/ /index.html; } - # Manifest und Icons - location ~* \.(webmanifest|json|png)$ { - add_header Access-Control-Allow-Origin *; - try_files $uri =404; - } - - # Statische Assets - location ~* \.(js|css|jpg|jpeg|gif|ico|svg|txt|woff|woff2|ttf|eot)$ { + # Cache statische Assets + location ~* \.(?:js|css|woff2?|ttf|eot|jpg|jpeg|png|gif|svg|ico|webp)$ { expires 1y; - add_header Cache-Control "public"; + add_header Cache-Control "public, immutable"; try_files $uri =404; } - - # Let's Encrypt ACME Challenge - location ^~ /.well-known/acme-challenge/ { - root /var/www/certbot; - } - } - - # HTTPS Server (Port 443) - server { - listen 443 ssl; - server_name pfandsystem.backdigital.de; - root /usr/share/nginx/html; - index index.html; - - ssl_certificate /etc/letsencrypt/live/pfandsystem.backdigital.de/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/pfandsystem.backdigital.de/privkey.pem; - include /etc/letsencrypt/options-ssl-nginx.conf; - ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; - - # API Proxy zum Backend - location /api/ { - proxy_pass http://backend:3001/api/; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection 'upgrade'; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_cache_bypass $http_upgrade; - } - - # Upload-Dateien vom Backend - location /uploads/ { - proxy_pass http://backend:3001/uploads/; - proxy_http_version 1.1; - proxy_set_header Host $host; - } - - # Frontend SPA - location / { - try_files $uri $uri/ /index.html; - } - - # Manifest und Icons - location ~* \.(webmanifest|json|png)$ { - add_header Access-Control-Allow-Origin *; - try_files $uri =404; - } - - # Statische Assets - location ~* \.(js|css|jpg|jpeg|gif|ico|svg|txt|woff|woff2|ttf|eot)$ { - expires 1y; - add_header Cache-Control "public"; - try_files $uri =404; - } - - # Let's Encrypt ACME Challenge - location ^~ /.well-known/acme-challenge/ { - root /var/www/certbot; - } } } diff --git a/package.json b/package.json index a145589..dd447eb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "pfandsystem-frontend", - "version": "0.1.0", + "name": "pfandsystem-demo-frontend", + "version": "0.2.0", "private": true, "type": "module", "scripts": { @@ -15,6 +15,9 @@ }, "devDependencies": { "@vitejs/plugin-react": "^4.5.1", + "autoprefixer": "^10.4.20", + "postcss": "^8.5.3", + "tailwindcss": "^3.4.17", "vite": "^5.2.0", "vite-plugin-pwa": "^1.0.0" } diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..2e7af2b --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/public/manifest.json b/public/manifest.json new file mode 100644 index 0000000..62769bb --- /dev/null +++ b/public/manifest.json @@ -0,0 +1,13 @@ +{ + "name": "Pfandsystem", + "short_name": "Pfandsystem", + "start_url": ".", + "display": "standalone", + "background_color": "#f8fafc", + "theme_color": "#4338ca", + "description": "Digitale Pfand- und Tourenverwaltung", + "icons": [ + { "src": "icon-192.png", "sizes": "192x192", "type": "image/png" }, + { "src": "icon-512.png", "sizes": "512x512", "type": "image/png" } + ] +} diff --git a/restore.sh b/restore.sh deleted file mode 100755 index 8c08084..0000000 --- a/restore.sh +++ /dev/null @@ -1,64 +0,0 @@ -#!/bin/bash -# Restore-Script für Pfandsystem MySQL Datenbank - -set -e - -BACKUP_DIR="/root/backups" - -# Prüfe ob Backup-Datei angegeben wurde -if [ -z "$1" ]; then - echo "❌ Fehler: Keine Backup-Datei angegeben" - echo "" - echo "Verwendung: ./restore.sh " - echo "" - echo "Verfügbare Backups:" - ls -lh "$BACKUP_DIR"/pfandsystem_backup_*.sql.gz 2>/dev/null || echo " Keine Backups gefunden" - exit 1 -fi - -BACKUP_FILE="$1" - -# Prüfe ob Backup-Datei existiert -if [ ! -f "$BACKUP_FILE" ]; then - echo "❌ Fehler: Backup-Datei nicht gefunden: $BACKUP_FILE" - exit 1 -fi - -echo "⚠️ WARNUNG: Dieser Vorgang überschreibt die aktuelle Datenbank!" -echo "Backup-Datei: $BACKUP_FILE" -echo "" -read -p "Fortfahren? (yes/no): " CONFIRM - -if [ "$CONFIRM" != "yes" ]; then - echo "Abgebrochen." - exit 0 -fi - -echo "" -echo "🗄️ Starte Datenbank-Wiederherstellung..." - -# Entpacken falls .gz -if [[ "$BACKUP_FILE" == *.gz ]]; then - echo "📦 Entpacke Backup..." - TEMP_FILE="/tmp/restore_temp.sql" - gunzip -c "$BACKUP_FILE" > "$TEMP_FILE" -else - TEMP_FILE="$BACKUP_FILE" -fi - -# Datenbank wiederherstellen -echo "💾 Importiere Datenbank..." -docker exec -i pfandsystem-mysql mysql \ - -u pfandsystem \ - -ppfandsystem_secure_password \ - pfandsystem < "$TEMP_FILE" - -# Temporäre Datei löschen -if [[ "$BACKUP_FILE" == *.gz ]]; then - rm "$TEMP_FILE" -fi - -echo "✅ Datenbank erfolgreich wiederhergestellt!" -echo "" -echo "🔄 Container neu starten empfohlen:" -echo " docker-compose restart backend" diff --git a/root.code-workspace b/root.code-workspace deleted file mode 100644 index 362d7c2..0000000 --- a/root.code-workspace +++ /dev/null @@ -1,7 +0,0 @@ -{ - "folders": [ - { - "path": "." - } - ] -} \ No newline at end of file diff --git a/setup-neue-struktur.sh b/setup-neue-struktur.sh deleted file mode 100755 index 9334f86..0000000 --- a/setup-neue-struktur.sh +++ /dev/null @@ -1,193 +0,0 @@ -#!/bin/bash - -echo "🚀 Setup: Neue Verzeichnisstruktur" -echo "====================================" -echo "" - -# Farben -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -NC='\033[0m' # No Color - -# Schritt 1: Verzeichnisse erstellen -echo -e "${YELLOW}📁 Schritt 1: Verzeichnisse erstellen${NC}" -mkdir -p /root/entwicklung -mkdir -p /root/livesysteme -mkdir -p /root/backups/entwicklung/pfandsystem -mkdir -p /root/backups/livesysteme/borbaecker -echo -e "${GREEN}✅ Verzeichnisse erstellt${NC}" -echo "" - -# Schritt 2: Backend stoppen -echo -e "${YELLOW}⏸️ Schritt 2: Services stoppen${NC}" -pkill -f "node.*server.js" 2>/dev/null || true -echo -e "${GREEN}✅ Backend gestoppt${NC}" -echo "" - -# Schritt 3: Docker Container stoppen -echo -e "${YELLOW}🐳 Schritt 3: Docker Container stoppen${NC}" -cd /root -docker compose down 2>/dev/null || true -echo -e "${GREEN}✅ Docker Container gestoppt${NC}" -echo "" - -# Schritt 4: Dev-Version verschieben -echo -e "${YELLOW}🔄 Schritt 4: Dev-Version verschieben${NC}" -echo "Dies kann einige Minuten dauern..." - -# Wichtige Dateien/Ordner verschieben -mv /root/backend /root/entwicklung/pfandsystem/ 2>/dev/null || true -mv /root/src /root/entwicklung/pfandsystem/ 2>/dev/null || true -mv /root/public /root/entwicklung/pfandsystem/ 2>/dev/null || true -mv /root/dist /root/entwicklung/pfandsystem/ 2>/dev/null || true -mv /root/docker-compose.yml /root/entwicklung/pfandsystem/ 2>/dev/null || true -mv /root/package.json /root/entwicklung/pfandsystem/ 2>/dev/null || true -mv /root/package-lock.json /root/entwicklung/pfandsystem/ 2>/dev/null || true -mv /root/vite.config.js /root/entwicklung/pfandsystem/ 2>/dev/null || true -mv /root/index.html /root/entwicklung/pfandsystem/ 2>/dev/null || true -mv /root/.env /root/entwicklung/pfandsystem/ 2>/dev/null || true -mv /root/fix-manifest-link.cjs /root/entwicklung/pfandsystem/ 2>/dev/null || true -mv /root/Dockerfile /root/entwicklung/pfandsystem/ 2>/dev/null || true - -# Git verschieben -mv /root/.git /root/entwicklung/pfandsystem/ 2>/dev/null || true -mv /root/.gitignore /root/entwicklung/pfandsystem/ 2>/dev/null || true - -# Dokumentation verschieben -mv /root/*.md /root/entwicklung/pfandsystem/ 2>/dev/null || true - -echo -e "${GREEN}✅ Dev-Version verschoben${NC}" -echo "" - -# Schritt 5: Live-Version klonen -echo -e "${YELLOW}📥 Schritt 5: Live-Version klonen${NC}" -cd /root/livesysteme/borbaecker -git clone ssh://git@49.13.154.75:2244/christian/pfandsystem.git . 2>/dev/null || echo "Git bereits geklont" -echo -e "${GREEN}✅ Live-Version geklont${NC}" -echo "" - -# Schritt 6: Traefik-Configs anpassen -echo -e "${YELLOW}🔧 Schritt 6: Traefik-Configs erstellen${NC}" - -# Dev-Config -cat > /root/traefik/dynamic/pfandsystem-dev.yml << 'EOF' -http: - routers: - pfandsystem-dev-api: - rule: "Host(`pfandsystem.backdigital.de`) && PathPrefix(`/api`)" - service: pfandsystem-dev-api - entryPoints: - - web - - websecure - tls: - certResolver: letsencrypt - priority: 100 - - pfandsystem-dev-uploads: - rule: "Host(`pfandsystem.backdigital.de`) && PathPrefix(`/uploads`)" - service: pfandsystem-dev-api - entryPoints: - - web - - websecure - tls: - certResolver: letsencrypt - priority: 100 - - services: - pfandsystem-dev-api: - loadBalancer: - servers: - - url: "http://172.17.0.1:3001" -EOF - -# Alte Config löschen -rm /root/traefik/dynamic/local-backend.yml 2>/dev/null || true - -echo -e "${GREEN}✅ Traefik-Configs erstellt${NC}" -echo "" - -# Schritt 7: Backup-Scripte erstellen -echo -e "${YELLOW}💾 Schritt 7: Backup-Scripte erstellen${NC}" - -# Dev-Backup -cat > /root/entwicklung/pfandsystem/backup-dev.sh << 'EOF' -#!/bin/bash -BACKUP_DIR="/root/backups/entwicklung/pfandsystem" -TIMESTAMP=$(date +%Y%m%d_%H%M%S) -BACKUP_NAME="backup_${TIMESTAMP}" - -mkdir -p "${BACKUP_DIR}/${BACKUP_NAME}" - -# Datenbank -docker exec pfandsystem-dev-mysql mysqldump -upfandsystem -ppfandsystem_secure_password pfandsystem > "${BACKUP_DIR}/${BACKUP_NAME}/database.sql" 2>/dev/null || echo "DB-Backup fehlgeschlagen" - -# Uploads -cp -r /root/entwicklung/pfandsystem/backend/uploads "${BACKUP_DIR}/${BACKUP_NAME}/" 2>/dev/null || true - -# .env -cp /root/entwicklung/pfandsystem/.env "${BACKUP_DIR}/${BACKUP_NAME}/frontend.env" 2>/dev/null || true -cp /root/entwicklung/pfandsystem/backend/.env "${BACKUP_DIR}/${BACKUP_NAME}/backend.env" 2>/dev/null || true - -# Komprimieren -cd "${BACKUP_DIR}" -tar -czf "${BACKUP_NAME}.tar.gz" "${BACKUP_NAME}" -rm -rf "${BACKUP_NAME}" - -# Alte Backups löschen -find "${BACKUP_DIR}" -name "backup_*.tar.gz" -mtime +7 -delete - -echo "✅ Dev-Backup: ${BACKUP_NAME}.tar.gz" -EOF - -chmod +x /root/entwicklung/pfandsystem/backup-dev.sh - -echo -e "${GREEN}✅ Backup-Scripte erstellt${NC}" -echo "" - -# Schritt 8: Docker-Compose anpassen -echo -e "${YELLOW}🐳 Schritt 8: Docker-Compose anpassen${NC}" -cd /root/entwicklung/pfandsystem -sed -i 's/container_name: pfandsystem-/container_name: pfandsystem-dev-/g' docker-compose.yml 2>/dev/null || true -echo -e "${GREEN}✅ Docker-Compose angepasst${NC}" -echo "" - -# Schritt 9: .env anpassen -echo -e "${YELLOW}⚙️ Schritt 9: .env Dateien anpassen${NC}" -sed -i 's|UPLOAD_DIR=/root/backend/uploads|UPLOAD_DIR=/root/entwicklung/pfandsystem/backend/uploads|g' /root/entwicklung/pfandsystem/backend/.env 2>/dev/null || true -echo -e "${GREEN}✅ .env angepasst${NC}" -echo "" - -# Schritt 10: Traefik neu starten -echo -e "${YELLOW}🔄 Schritt 10: Traefik neu starten${NC}" -docker restart pfandsystem-traefik -sleep 3 -echo -e "${GREEN}✅ Traefik neu gestartet${NC}" -echo "" - -# Zusammenfassung -echo "" -echo "==========================================" -echo -e "${GREEN}✅ Setup abgeschlossen!${NC}" -echo "==========================================" -echo "" -echo "📁 Verzeichnisse:" -echo " Dev: /root/entwicklung/pfandsystem" -echo " Live: /root/livesysteme/borbaecker" -echo "" -echo "💾 Backups:" -echo " Dev: /root/backups/entwicklung/pfandsystem" -echo " Live: /root/backups/livesysteme/borbaecker" -echo "" -echo "🔧 Nächste Schritte:" -echo " 1. Dev-System starten:" -echo " cd /root/entwicklung/pfandsystem" -echo " docker compose up -d" -echo " cd backend && npm start &" -echo "" -echo " 2. Live-System konfigurieren:" -echo " Siehe: /root/entwicklung/pfandsystem/MIGRATION_UND_SETUP.md" -echo "" -echo " 3. Cron-Jobs einrichten:" -echo " crontab -e" -echo " 0 6-23 * * * /root/entwicklung/pfandsystem/backup-dev.sh" -echo "" diff --git a/src/._App.jsx b/src/._App.jsx new file mode 100644 index 0000000..988359c Binary files /dev/null and b/src/._App.jsx differ diff --git a/src/._api b/src/._api new file mode 100755 index 0000000..988359c Binary files /dev/null and b/src/._api differ diff --git a/src/._components b/src/._components new file mode 100755 index 0000000..988359c Binary files /dev/null and b/src/._components differ diff --git a/src/._index.css b/src/._index.css new file mode 100644 index 0000000..988359c Binary files /dev/null and b/src/._index.css differ diff --git a/src/._main.jsx b/src/._main.jsx new file mode 100644 index 0000000..988359c Binary files /dev/null and b/src/._main.jsx differ diff --git a/src/App.jsx b/src/App.jsx index 7231609..a85a6f8 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -2,49 +2,29 @@ import { useEffect, useState } from 'react'; import { api } from './api/client'; import Login from './components/Auth/Login'; import Dashboard from './components/Dashboard/Dashboard'; +import SuperAdmin from './components/SuperAdmin'; -function App() { - const [userObj, setUserObj] = useState(null); +export default function App() { + const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { - async function checkAuth() { - setLoading(true); - const token = api.getToken(); - - if (!token) { - setUserObj(null); - setLoading(false); - return; - } - - try { - const user = await api.getCurrentUser(); - setUserObj(user); - } catch (error) { - console.error('Auth-Fehler:', error); - api.logout(); - setUserObj(null); - } finally { - setLoading(false); - } - } - - checkAuth(); + (async () => { + if (!api.getToken()) { setLoading(false); return; } + try { setUser(await api.getCurrentUser()); } + catch { api.logout(); } + finally { setLoading(false); } + })(); }, []); - const handleLogin = (user) => { - setUserObj(user); - }; + const handleLogout = () => { api.logout(); setUser(null); }; - const handleLogout = () => { - api.logout(); - setUserObj(null); - }; - - if (loading) return
Lade...
; - if (!userObj) return ; - return ; + if (loading) return ( +
+
Lade…
+
+ ); + if (!user) return ; + if (user.rolle === 'superadmin') return ; + return ; } - -export default App; diff --git a/src/SupabaseTest.jsx b/src/SupabaseTest.jsx deleted file mode 100644 index 2edc959..0000000 --- a/src/SupabaseTest.jsx +++ /dev/null @@ -1,41 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { createClient } from '@supabase/supabase-js'; - -const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; -const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY; -const supabase = createClient(supabaseUrl, supabaseAnonKey); - -export default function SupabaseTest() { - const [data, setData] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - - useEffect(() => { - async function fetchData() { - setLoading(true); - setError(null); - // Passe den Tabellennamen ggf. an - const { data, error } = await supabase.from('test').select('*').limit(10); - if (error) setError(error.message); - setData(data || []); - setLoading(false); - } - fetchData(); - }, []); - - return ( -
-

Supabase Test

-
- Supabase URL: {supabaseUrl} -
- {loading &&

Lade Daten...

} - {error &&

Fehler: {error}

} -
    - {data && data.length > 0 ? data.map((row, idx) => ( -
  • {JSON.stringify(row)}
  • - )) : !loading &&
  • Keine Daten gefunden.
  • } -
-
- ); -} diff --git a/src/api/._client.js b/src/api/._client.js new file mode 100644 index 0000000..988359c Binary files /dev/null and b/src/api/._client.js differ diff --git a/src/api/client.js b/src/api/client.js index 5105ef1..6bb4846 100644 --- a/src/api/client.js +++ b/src/api/client.js @@ -1,260 +1,129 @@ -// API Client für Backend-Kommunikation (ersetzt Supabase) - -const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:3001/api'; +// API Client - relativ via /api (Traefik routet pfandsystem.dockly.de/api -> Backend) +const API_BASE_URL = import.meta.env.VITE_API_URL || '/api'; class ApiClient { - constructor() { - this.token = localStorage.getItem('auth_token'); - } + constructor() { this.token = localStorage.getItem('auth_token'); } - setToken(token) { - this.token = token; - if (token) { - localStorage.setItem('auth_token', token); - } else { - localStorage.removeItem('auth_token'); - } - } - - getToken() { - return this.token || localStorage.getItem('auth_token'); + setToken(t) { + this.token = t; + if (t) localStorage.setItem('auth_token', t); else localStorage.removeItem('auth_token'); } + getToken() { return this.token || localStorage.getItem('auth_token'); } async request(endpoint, options = {}) { - const token = this.getToken(); - const headers = { - 'Content-Type': 'application/json', - ...options.headers, - }; - - if (token && !options.skipAuth) { - headers['Authorization'] = `Bearer ${token}`; - } - - const config = { - ...options, - headers, - }; - - try { - const response = await fetch(`${API_BASE_URL}${endpoint}`, config); - - // Prüfe ob Response Body vorhanden ist - const text = await response.text(); - let data = null; - - try { - data = text ? JSON.parse(text) : null; - } catch (e) { - console.error('JSON Parse Error:', text); - throw new Error('Ungültige Server-Antwort'); - } - - if (!response.ok) { - throw new Error(data?.error || `HTTP ${response.status}`); - } - - return data; - } catch (error) { - console.error('API Request Error:', error); - throw error; - } + const headers = { 'Content-Type': 'application/json', ...options.headers }; + const t = this.getToken(); + if (t && !options.skipAuth) headers['Authorization'] = `Bearer ${t}`; + const res = await fetch(`${API_BASE_URL}${endpoint}`, { ...options, headers }); + const text = await res.text(); + let data = null; + try { data = text ? JSON.parse(text) : null; } catch { throw new Error('Ungueltige Server-Antwort'); } + if (!res.ok) throw new Error(data?.error || `HTTP ${res.status}`); + return data; } // Auth - async login(email, password) { + async login(email, password, tenant_slug) { const data = await this.request('/auth/login', { method: 'POST', - body: JSON.stringify({ email, password }), + body: JSON.stringify({ email, password, tenant_slug }), skipAuth: true, }); this.setToken(data.token); return data; } - - async logout() { - this.setToken(null); - } - - async getCurrentUser() { - return await this.request('/auth/me'); - } - - async register(email, password, name, rolle) { - return await this.request('/auth/register', { - method: 'POST', - body: JSON.stringify({ email, password, name, rolle }), - }); - } - - async changePassword(currentPassword, newPassword) { - return await this.request('/auth/change-password', { - method: 'POST', - body: JSON.stringify({ currentPassword, newPassword }), + logout() { this.setToken(null); } + getCurrentUser() { return this.request('/auth/me'); } + changePassword(currentPassword, newPassword) { + return this.request('/auth/change-password', { + method: 'POST', body: JSON.stringify({ currentPassword, newPassword }), }); } // Kunden - async getKunden() { - return await this.request('/kunden'); - } + getKunden() { return this.request('/kunden'); } + getKunde(id) { return this.request(`/kunden/${id}`); } + createKunde(k) { return this.request('/kunden', { method: 'POST', body: JSON.stringify(k) }); } + updateKunde(id, k) { return this.request(`/kunden/${id}`, { method: 'PUT', body: JSON.stringify(k) }); } + deleteKunde(id) { return this.request(`/kunden/${id}`, { method: 'DELETE' }); } - async getKunde(id) { - return await this.request(`/kunden/${id}`); - } - - async createKunde(kunde) { - return await this.request('/kunden', { + // Kundenbilder (Multipart) + async uploadKundenBild(kundeId, file, beschreibung) { + const fd = new FormData(); + fd.append('foto', file); + if (beschreibung) fd.append('beschreibung', beschreibung); + const res = await fetch(`${API_BASE_URL}/kunden-bilder/${kundeId}`, { method: 'POST', - body: JSON.stringify(kunde), - }); - } - - async updateKunde(id, kunde) { - return await this.request(`/kunden/${id}`, { - method: 'PUT', - body: JSON.stringify(kunde), - }); - } - - async deleteKunde(id) { - return await this.request(`/kunden/${id}`, { - method: 'DELETE', + headers: { 'Authorization': `Bearer ${this.getToken()}` }, + body: fd, }); + if (!res.ok) { + const e = await res.json().catch(() => ({})); + throw new Error(e.error || `HTTP ${res.status}`); + } + return res.json(); } + deleteKundenBild(id) { return this.request(`/kunden-bilder/bild/${id}`, { method: 'DELETE' }); } // Artikel - async getArtikel() { - return await this.request('/artikel'); - } - - async getArtikelById(id) { - return await this.request(`/artikel/${id}`); - } - - async createArtikel(artikel) { - return await this.request('/artikel', { - method: 'POST', - body: JSON.stringify(artikel), - }); - } - - async updateArtikel(id, artikel) { - return await this.request(`/artikel/${id}`, { - method: 'PUT', - body: JSON.stringify(artikel), - }); - } - - async deleteArtikel(id) { - return await this.request(`/artikel/${id}`, { - method: 'DELETE', - }); - } + getArtikel() { return this.request('/artikel'); } + createArtikel(a) { return this.request('/artikel', { method: 'POST', body: JSON.stringify(a) }); } + updateArtikel(id, a) { return this.request(`/artikel/${id}`, { method: 'PUT', body: JSON.stringify(a) }); } + deleteArtikel(id) { return this.request(`/artikel/${id}`, { method: 'DELETE' }); } // Lieferungen - async getLieferungen() { - return await this.request('/lieferungen'); - } - - async getLieferungenByKunde(kundeId) { - return await this.request(`/lieferungen/kunde/${kundeId}`); - } - - async createLieferung(lieferung, foto) { - const formData = new FormData(); - formData.append('kunde_id', lieferung.kunde_id); - formData.append('artikel_id', lieferung.artikel_id); - formData.append('anzahl', lieferung.anzahl); - if (foto) { - formData.append('foto', foto); - } - - const token = this.getToken(); - const response = await fetch(`${API_BASE_URL}/lieferungen`, { + getLieferungen() { return this.request('/lieferungen'); } + async createLieferung(l, foto) { + const fd = new FormData(); + fd.append('kunde_id', l.kunde_id); + fd.append('artikel_id', l.artikel_id); + fd.append('anzahl', l.anzahl); + if (foto) fd.append('foto', foto); + const res = await fetch(`${API_BASE_URL}/lieferungen`, { method: 'POST', - headers: { - 'Authorization': `Bearer ${token}`, - }, - body: formData, + headers: { 'Authorization': `Bearer ${this.getToken()}` }, + body: fd, }); - - if (!response.ok) { - const error = await response.json().catch(() => ({ error: 'Netzwerkfehler' })); - throw new Error(error.error || `HTTP ${response.status}`); - } - - return await response.json(); + if (!res.ok) { const e = await res.json().catch(()=>({})); throw new Error(e.error || `HTTP ${res.status}`); } + return res.json(); } + deleteLieferung(id) { return this.request(`/lieferungen/${id}`, { method: 'DELETE' }); } - async deleteLieferung(id) { - return await this.request(`/lieferungen/${id}`, { - method: 'DELETE', - }); - } - - // Rücknahmen - async getRuecknahmen() { - return await this.request('/ruecknahmen'); - } - - async getRuecknahmenByKunde(kundeId) { - return await this.request(`/ruecknahmen/kunde/${kundeId}`); - } - - async createRuecknahme(ruecknahme, foto) { - const formData = new FormData(); - formData.append('kunde_id', ruecknahme.kunde_id); - formData.append('artikel_id', ruecknahme.artikel_id); - formData.append('anzahl', ruecknahme.anzahl); - if (foto) { - formData.append('foto', foto); - } - - const token = this.getToken(); - const response = await fetch(`${API_BASE_URL}/ruecknahmen`, { + // Ruecknahmen + getRuecknahmen() { return this.request('/ruecknahmen'); } + async createRuecknahme(r, foto) { + const fd = new FormData(); + fd.append('kunde_id', r.kunde_id); + fd.append('artikel_id', r.artikel_id); + fd.append('anzahl', r.anzahl); + if (foto) fd.append('foto', foto); + const res = await fetch(`${API_BASE_URL}/ruecknahmen`, { method: 'POST', - headers: { - 'Authorization': `Bearer ${token}`, - }, - body: formData, - }); - - if (!response.ok) { - const error = await response.json().catch(() => ({ error: 'Netzwerkfehler' })); - throw new Error(error.error || `HTTP ${response.status}`); - } - - return await response.json(); - } - - async deleteRuecknahme(id) { - return await this.request(`/ruecknahmen/${id}`, { - method: 'DELETE', + headers: { 'Authorization': `Bearer ${this.getToken()}` }, + body: fd, }); + if (!res.ok) { const e = await res.json().catch(()=>({})); throw new Error(e.error || `HTTP ${res.status}`); } + return res.json(); } + deleteRuecknahme(id) { return this.request(`/ruecknahmen/${id}`, { method: 'DELETE' }); } // Mitarbeiter - async getMitarbeiter() { - return await this.request('/mitarbeiter'); - } + getMitarbeiter() { return this.request('/mitarbeiter'); } + createMitarbeiter(m) { return this.request('/mitarbeiter', { method: 'POST', body: JSON.stringify(m) }); } + updateMitarbeiter(id, m) { return this.request(`/mitarbeiter/${id}`, { method: 'PUT', body: JSON.stringify(m) }); } + deleteMitarbeiter(id) { return this.request(`/mitarbeiter/${id}`, { method: 'DELETE' }); } - async getMitarbeiterById(id) { - return await this.request(`/mitarbeiter/${id}`); - } + // Geraete + getGeraete() { return this.request('/geraete'); } + createGeraet(g) { return this.request('/geraete', { method: 'POST', body: JSON.stringify(g) }); } + updateGeraet(id, g) { return this.request(`/geraete/${id}`, { method: 'PUT', body: JSON.stringify(g) }); } + deleteGeraet(id) { return this.request(`/geraete/${id}`, { method: 'DELETE' }); } - async updateMitarbeiter(id, mitarbeiter) { - return await this.request(`/mitarbeiter/${id}`, { - method: 'PUT', - body: JSON.stringify(mitarbeiter), - }); - } - - async deleteMitarbeiter(id) { - return await this.request(`/mitarbeiter/${id}`, { - method: 'DELETE', - }); - } + // Tenants (SuperAdmin) + getTenants() { return this.request('/tenants'); } + createTenant(t) { return this.request('/tenants', { method: 'POST', body: JSON.stringify(t) }); } + updateTenant(id, t) { return this.request(`/tenants/${id}`, { method: 'PATCH', body: JSON.stringify(t) }); } + deleteTenant(id) { return this.request(`/tenants/${id}`, { method: 'DELETE' }); } } export const api = new ApiClient(); diff --git a/src/components/._Auth b/src/components/._Auth new file mode 100755 index 0000000..988359c Binary files /dev/null and b/src/components/._Auth differ diff --git a/src/components/._Dashboard b/src/components/._Dashboard new file mode 100755 index 0000000..988359c Binary files /dev/null and b/src/components/._Dashboard differ diff --git a/src/components/._Eingabe b/src/components/._Eingabe new file mode 100755 index 0000000..988359c Binary files /dev/null and b/src/components/._Eingabe differ diff --git a/src/components/._Geraete.jsx b/src/components/._Geraete.jsx new file mode 100644 index 0000000..988359c Binary files /dev/null and b/src/components/._Geraete.jsx differ diff --git a/src/components/._KundenVerwaltung.jsx b/src/components/._KundenVerwaltung.jsx new file mode 100644 index 0000000..988359c Binary files /dev/null and b/src/components/._KundenVerwaltung.jsx differ diff --git a/src/components/._MitarbeiterVerwaltung.jsx b/src/components/._MitarbeiterVerwaltung.jsx new file mode 100644 index 0000000..988359c Binary files /dev/null and b/src/components/._MitarbeiterVerwaltung.jsx differ diff --git a/src/components/._SuperAdmin.jsx b/src/components/._SuperAdmin.jsx new file mode 100644 index 0000000..988359c Binary files /dev/null and b/src/components/._SuperAdmin.jsx differ diff --git a/src/components/._TourPlanung.jsx b/src/components/._TourPlanung.jsx new file mode 100644 index 0000000..988359c Binary files /dev/null and b/src/components/._TourPlanung.jsx differ diff --git a/src/components/Auth/._Login.jsx b/src/components/Auth/._Login.jsx new file mode 100644 index 0000000..988359c Binary files /dev/null and b/src/components/Auth/._Login.jsx differ diff --git a/src/components/Auth/._PasswordChange.jsx b/src/components/Auth/._PasswordChange.jsx new file mode 100644 index 0000000..988359c Binary files /dev/null and b/src/components/Auth/._PasswordChange.jsx differ diff --git a/src/components/Auth/Login.jsx b/src/components/Auth/Login.jsx index 42a120b..1610584 100644 --- a/src/components/Auth/Login.jsx +++ b/src/components/Auth/Login.jsx @@ -2,96 +2,81 @@ import { useState } from 'react'; import { api } from '../../api/client'; export default function Login({ onLogin }) { + const params = new URLSearchParams(window.location.search); + const tenantFromUrl = params.get('tenant') || ''; + const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); - const [message, setMessage] = useState(''); + const [tenantSlug, setTenantSlug] = useState(tenantFromUrl); + const [showTenant, setShowTenant] = useState(!!tenantFromUrl); + const [error, setError] = useState(''); const [loading, setLoading] = useState(false); - const handleLogin = async (e) => { + const submit = async (e) => { e.preventDefault(); - setLoading(true); - setMessage(''); - + setError(''); setLoading(true); try { - const data = await api.login(email, password); - setMessage('Login erfolgreich!'); - if (onLogin) { - onLogin(data.user); - } - } catch (error) { - setMessage(error.message || 'Login fehlgeschlagen'); - } finally { - setLoading(false); - } + const data = await api.login(email, password, tenantSlug || undefined); + onLogin?.(data.user); + } catch (err) { + setError(err.message || 'Login fehlgeschlagen'); + } finally { setLoading(false); } }; return ( -
-

- Pfandsystem -

-

Login

-
- setEmail(e.target.value)} - required - style={{ - fontSize: 18, - padding: '14px 14px', - borderRadius: 12, - border: '1.5px solid #dbeafe', - background: '#f6f7fb', - outline: 'none', - marginBottom: 2 - }} - /> - setPassword(e.target.value)} - required - style={{ - fontSize: 18, - padding: '14px 14px', - borderRadius: 12, - border: '1.5px solid #dbeafe', - background: '#f6f7fb', - outline: 'none', - marginBottom: 2 - }} - /> - -
- {message &&
{message}
} +
+
+
+
+ P +
+

Pfandsystem

+

Demo-Portal

+
+ +
+
+ + setEmail(e.target.value)} + className="input" placeholder="name@firma.de" /> +
+
+ + setPassword(e.target.value)} + className="input" placeholder="••••••••" /> +
+ + {showTenant ? ( +
+ + setTenantSlug(e.target.value)} + className="input" placeholder="z. B. firma-a1b2" /> +
+ ) : ( + + )} + + {error && ( +
+ {error} +
+ )} + + +
+ +

+ Kein Zugang? Demo-Account anfragen unter demo@dockly.de +

+
); } diff --git a/src/components/Auth/Logout.jsx b/src/components/Auth/Logout.jsx deleted file mode 100644 index 8aa1de3..0000000 --- a/src/components/Auth/Logout.jsx +++ /dev/null @@ -1,27 +0,0 @@ -import { api } from '../../api/client'; - -export default function Logout({ onLogout }) { - const handleLogout = async () => { - api.logout(); - if (onLogout) { - onLogout(); - } - // Seite neu laden um sicherzustellen dass der User ausgeloggt ist - window.location.reload(); - }; - return ( - - ); -} diff --git a/src/components/Auth/PasswordChange.jsx b/src/components/Auth/PasswordChange.jsx index 67db3fb..1ee4823 100644 --- a/src/components/Auth/PasswordChange.jsx +++ b/src/components/Auth/PasswordChange.jsx @@ -2,131 +2,42 @@ import { useState } from 'react'; import { api } from '../../api/client'; export default function PasswordChange() { - const [form, setForm] = useState({ - currentPassword: '', - newPassword: '', - confirmPassword: '' - }); - const [error, setError] = useState(''); - const [success, setSuccess] = useState(''); - const [loading, setLoading] = useState(false); + const [form, setForm] = useState({ currentPassword: '', newPassword: '', confirm: '' }); + const [err, setErr] = useState(''); + const [ok, setOk] = useState(''); + const [busy, setBusy] = useState(false); + const update = e => { setForm(f => ({ ...f, [e.target.name]: e.target.value })); setErr(''); setOk(''); }; - const handleChange = (e) => { - setForm(f => ({ ...f, [e.target.name]: e.target.value })); - setError(''); - setSuccess(''); - }; - - const handleSubmit = async (e) => { + const submit = async (e) => { e.preventDefault(); - setError(''); - setSuccess(''); - - // Validierung - if (!form.currentPassword || !form.newPassword || !form.confirmPassword) { - setError('Bitte alle Felder ausfüllen'); - return; - } - - if (form.newPassword.length < 6) { - setError('Neues Passwort muss mindestens 6 Zeichen lang sein'); - return; - } - - if (form.newPassword !== form.confirmPassword) { - setError('Passwörter stimmen nicht überein'); - return; - } - - setLoading(true); + if (form.newPassword.length < 6) return setErr('Neues Passwort min. 6 Zeichen'); + if (form.newPassword !== form.confirm) return setErr('Passwoerter stimmen nicht ueberein'); + setBusy(true); try { await api.changePassword(form.currentPassword, form.newPassword); - setSuccess('Passwort erfolgreich geändert!'); - setForm({ currentPassword: '', newPassword: '', confirmPassword: '' }); - } catch (err) { - setError(err.message || 'Fehler beim Ändern des Passworts'); - } finally { - setLoading(false); - } + setOk('Passwort geaendert'); + setForm({ currentPassword: '', newPassword: '', confirm: '' }); + } catch (e) { setErr(e.message); } finally { setBusy(false); } }; return ( -
-

Passwort ändern

- - {error && ( -
- {error} -
- )} - - {success && ( -
- {success} -
- )} - -
-
- - -
- -
- - -
- -
- - -
- - -
-
+
+

Passwort aendern

+ {err &&
{err}
} + {ok &&
{ok}
} +
+ + +
+
+ + +
+
+ + +
+ +
); } diff --git a/src/components/Dashboard/._Account.jsx b/src/components/Dashboard/._Account.jsx new file mode 100644 index 0000000..988359c Binary files /dev/null and b/src/components/Dashboard/._Account.jsx differ diff --git a/src/components/Dashboard/._AdminDashboard.jsx b/src/components/Dashboard/._AdminDashboard.jsx new file mode 100644 index 0000000..988359c Binary files /dev/null and b/src/components/Dashboard/._AdminDashboard.jsx differ diff --git a/src/components/Dashboard/._BildModal.jsx b/src/components/Dashboard/._BildModal.jsx new file mode 100644 index 0000000..988359c Binary files /dev/null and b/src/components/Dashboard/._BildModal.jsx differ diff --git a/src/components/Dashboard/._Dashboard.jsx b/src/components/Dashboard/._Dashboard.jsx new file mode 100644 index 0000000..988359c Binary files /dev/null and b/src/components/Dashboard/._Dashboard.jsx differ diff --git a/src/components/Dashboard/._TopNav.jsx b/src/components/Dashboard/._TopNav.jsx new file mode 100644 index 0000000..988359c Binary files /dev/null and b/src/components/Dashboard/._TopNav.jsx differ diff --git a/src/components/Dashboard/._VorgangsListe.jsx b/src/components/Dashboard/._VorgangsListe.jsx new file mode 100644 index 0000000..988359c Binary files /dev/null and b/src/components/Dashboard/._VorgangsListe.jsx differ diff --git a/src/components/Dashboard/Account.jsx b/src/components/Dashboard/Account.jsx new file mode 100644 index 0000000..919f5ed --- /dev/null +++ b/src/components/Dashboard/Account.jsx @@ -0,0 +1,32 @@ +import PasswordChange from '../Auth/PasswordChange'; + +export default function Account({ user }) { + return ( +
+
+

Account

+
+
Name
{user.name}
+
E-Mail
{user.email}
+
Rolle
+ {user.rolle} +
+ {user.tenant_name && (<> +
Tenant
+
+ {user.tenant_name} + {user.tenant_typ === 'demo' && ( + Demo + )} +
+ )} + {user.demo_expires_at && (<> +
Demo-Ablauf
+
{new Date(user.demo_expires_at).toLocaleDateString('de-DE')}
+ )} +
+
+ +
+ ); +} diff --git a/src/components/Dashboard/AdminDashboard.jsx b/src/components/Dashboard/AdminDashboard.jsx index b708d30..38a978f 100644 --- a/src/components/Dashboard/AdminDashboard.jsx +++ b/src/components/Dashboard/AdminDashboard.jsx @@ -1,253 +1,132 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; +import { FaFileCsv } from 'react-icons/fa'; import { api } from '../../api/client'; -function exportToCSV(rows, filename) { - const header = ['Kunde', 'Artikel', 'Soll', 'Ist', 'Differenz', 'Zeitraum']; - const csv = [header.join(',')].concat( - rows.map(r => [r.kunde, r.artikel, r.soll, r.ist, r.differenz, r.zeitraum].join(',')) - ).join('\n'); - const blob = new Blob([csv], { type: 'text/csv' }); +function toCSV(rows) { + const header = ['Kunde', 'Artikel', 'Soll', 'Ist', 'Differenz', 'Zeitraum', 'Mitarbeiter']; + const escape = v => `"${String(v ?? '').replace(/"/g, '""')}"`; + return [header.join(',')] + .concat(rows.map(r => [r.kunde, r.artikel, r.soll, r.ist, r.differenz, r.zeitraum, r.mitarbeiter].map(escape).join(','))) + .join('\n'); +} + +function downloadCSV(rows, filename) { + const blob = new Blob([toCSV(rows)], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = filename; - a.click(); + const a = document.createElement('a'); a.href = url; a.download = filename; a.click(); URL.revokeObjectURL(url); } - -function UeberfaelligeKunden({ kunden, rows }) { - const [sending, setSending] = useState({}); - const [msg, setMsg] = useState({}); - const UEBERFAELLIG_TAGE = 14; - const heute = new Date(); - // rows enthält nur den ausgewählten Kunden, daher alle Kunden prüfen: - // (Alternative: für alle Kunden/Artikel aus lieferungen/ruecknahmen berechnen) - // Hier: Zeige für den aktuell gefilterten Kunden, wenn überfällig - const ueberfaellige = rows.filter(row => { - // Annahme: row.soll > row.ist, Frist überschritten, Kunde hat E-Mail - // Frist: ältestes Lieferdatum für Artikel - const lieferDatum = heute; // Placeholder, da rows kein Datum enthält - // TODO: lieferDatum aus lieferungen holen, wenn verfügbar - // Hier: Zeige alle mit Differenz > 0 und email - return (row.soll > row.ist); - }); - if (!ueberfaellige.length) return null; - async function sendeMail(row) { - setSending(s => ({ ...s, [row.artikel]: true })); - setMsg(m => ({ ...m, [row.artikel]: '' })); - try { - const res = await fetch('https://work.pfandsystem.backdigital.de/webhook/send-reminder', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - email: kunden.find(k => k.id === row.kunde_id)?.email || kunden.find(k => k.name === row.kunde)?.email || '', - kunde: row.kunde, - anrede: kunden.find(k => k.id === row.kunde_id)?.anrede || '', - ansprechpartner: kunden.find(k => k.id === row.kunde_id)?.ansprechpartner || '', - artikel: row.artikel, - anzahl: row.soll - row.ist, - frist: '14 Tage', - }) - }); - if (!res.ok) throw new Error('HTTP ' + res.status); - setMsg(m => ({ ...m, [row.artikel]: 'Erinnerung gesendet!' })); - } catch (e) { - setMsg(m => ({ ...m, [row.artikel]: 'Fehler beim Senden!' })); - } - setSending(s => ({ ...s, [row.artikel]: false })); - } - return ( -
-

Überfällige Produkte – Erinnerung senden

- {ueberfaellige.map((row, i) => ( -
- - {row.kunde} – {row.artikel} ({row.soll - row.ist} Stück) - - - {msg[row.artikel]} -
- ))} -
- Nur Kunden mit ausgeliehenen Produkten und E-Mail werden angezeigt. Die Erinnerungsmail enthält eine freundliche Bitte um Rückgabe. -
-
- ); -} - export default function AdminDashboard() { - const [kunden, setKunden] = useState([]); - const [artikel, setArtikel] = useState([]); - const [mitarbeiter, setMitarbeiter] = useState([]); - const [lieferungen, setLieferungen] = useState([]); - const [ruecknahmen, setRuecknahmen] = useState([]); + const [data, setData] = useState({ kunden: [], artikel: [], mitarbeiter: [], lieferungen: [], ruecknahmen: [] }); + const [loading, setLoading] = useState(true); const [selectedKunde, setSelectedKunde] = useState(''); const [selectedMitarbeiter, setSelectedMitarbeiter] = useState(''); - const [zeitraumVon, setZeitraumVon] = useState(''); - const [zeitraumBis, setZeitraumBis] = useState(''); - const [rows, setRows] = useState([]); + const [von, setVon] = useState(''); + const [bis, setBis] = useState(''); useEffect(() => { - async function loadData() { - try { - const [kundenData, artikelData, mitarbeiterData, lieferungenData, ruecknahmenData] = await Promise.all([ - api.getKunden(), - api.getArtikel(), - api.getMitarbeiter(), - api.getLieferungen(), - api.getRuecknahmen() - ]); - setKunden(kundenData || []); - setArtikel(artikelData || []); - setMitarbeiter(mitarbeiterData || []); - setLieferungen(lieferungenData || []); - setRuecknahmen(ruecknahmenData || []); - } catch (error) { - console.error('Fehler beim Laden:', error); - } - } - loadData(); + Promise.all([api.getKunden(), api.getArtikel(), api.getMitarbeiter(), api.getLieferungen(), api.getRuecknahmen()]) + .then(([k, a, m, l, r]) => setData({ kunden: k, artikel: a, mitarbeiter: m, lieferungen: l, ruecknahmen: r })) + .finally(() => setLoading(false)); }, []); - useEffect(() => { - if (!selectedKunde) { setRows([]); return; } - const filterByDate = (arr) => arr.filter(e => { + const rows = useMemo(() => { + if (!selectedKunde) return []; + const inRange = e => { const d = new Date(e.erstellt_am); - if (zeitraumVon && d < new Date(zeitraumVon)) return false; - if (zeitraumBis && d > new Date(zeitraumBis + 'T23:59:59')) return false; + if (von && d < new Date(von)) return false; + if (bis && d > new Date(bis + 'T23:59:59')) return false; return true; - }); - let lieferungenK = lieferungen.filter(l => l.kunde_id === selectedKunde); - let ruecknahmenK = ruecknahmen.filter(r => r.kunde_id === selectedKunde); - if (selectedMitarbeiter) { - lieferungenK = lieferungenK.filter(l => l.mitarbeiter_id === selectedMitarbeiter); - ruecknahmenK = ruecknahmenK.filter(r => r.mitarbeiter_id === selectedMitarbeiter); - } - lieferungenK = filterByDate(lieferungenK); - ruecknahmenK = filterByDate(ruecknahmenK); - const artikelSet = Array.from(new Set([...lieferungenK.map(l => l.artikel_id), ...ruecknahmenK.map(r => r.artikel_id)])); - const rowsNew = artikelSet.map(artId => { - const art = artikel.find(a => a.id === artId); - const soll = lieferungenK.filter(l => l.artikel_id === artId).reduce((sum, l) => sum + l.anzahl, 0); - const ist = ruecknahmenK.filter(r => r.artikel_id === artId).reduce((sum, r) => sum + r.anzahl, 0); - // Mitarbeiter-Name aus erster Lieferung/Rücknahme (wenn eindeutig) - let mitarbeiterName = ''; - const mitarbeiterId = lieferungenK.concat(ruecknahmenK).find(v => v.artikel_id === artId)?.mitarbeiter_id; - if (mitarbeiterId) { - mitarbeiterName = mitarbeiter.find(m => m.id === mitarbeiterId)?.name || ''; - } + }; + const l = data.lieferungen.filter(x => x.kunde_id === selectedKunde && inRange(x) + && (!selectedMitarbeiter || x.mitarbeiter_id === selectedMitarbeiter)); + const r = data.ruecknahmen.filter(x => x.kunde_id === selectedKunde && inRange(x) + && (!selectedMitarbeiter || x.mitarbeiter_id === selectedMitarbeiter)); + const artikelIds = Array.from(new Set([...l.map(x => x.artikel_id), ...r.map(x => x.artikel_id)])); + return artikelIds.map(aid => { + const art = data.artikel.find(a => a.id === aid); + const soll = l.filter(x => x.artikel_id === aid).reduce((s, x) => s + x.anzahl, 0); + const ist = r.filter(x => x.artikel_id === aid).reduce((s, x) => s + x.anzahl, 0); + const mitId = [...l, ...r].find(v => v.artikel_id === aid)?.mitarbeiter_id; + const mit = mitId ? data.mitarbeiter.find(m => m.id === mitId)?.name : ''; return { - kunde: kunden.find(k => k.id === selectedKunde)?.name || '', - kunde_id: selectedKunde, // <-- Kunden-ID für eindeutige Zuordnung + kunde: data.kunden.find(k => k.id === selectedKunde)?.name || '', artikel: art?.bezeichnung || '', - soll, - ist, - differenz: ist - soll, - zeitraum: (zeitraumVon || '...') + ' bis ' + (zeitraumBis || '...'), - mitarbeiter: mitarbeiterName, + soll, ist, differenz: ist - soll, + zeitraum: (von || '…') + ' – ' + (bis || '…'), + mitarbeiter: mit || '', }; }); - setRows(rowsNew); - }, [selectedKunde, zeitraumVon, zeitraumBis, lieferungen, ruecknahmen, artikel, kunden, selectedMitarbeiter]); + }, [data, selectedKunde, selectedMitarbeiter, von, bis]); + + if (loading) return
Lade…
; return ( -
-

Admin: Soll-Ist-Abgleich & CSV-Export

-
- - - setZeitraumVon(e.target.value)} style={{ fontSize: 16, padding: 10, borderRadius: 10, border: '1.5px solid #dbeafe' }} placeholder='Von' /> - setZeitraumBis(e.target.value)} style={{ fontSize: 16, padding: 10, borderRadius: 10, border: '1.5px solid #dbeafe' }} placeholder='Bis' /> - +
+

Auswertung · Soll-Ist-Abgleich

+ +
+
+ + +
+
+ + +
+
+ + setVon(e.target.value)} /> +
+
+ + setBis(e.target.value)} /> +
+
-
- {rows.length > 0 && ( - <> - {/* Desktop/Tablett: Tabelle */} -
- - - - - - - - - + + {selectedKunde ? ( + rows.length === 0 ?
Keine Eintraege im Zeitraum.
: ( +
+
ArtikelSollIstDifferenzZeitraumMitarbeiter
+ + + + + + + + + + + {rows.map((r, i) => ( + + + + + + - - - {rows.map((row, i) => ( - - - - - - - - - ))} - -
ArtikelSollIstDifferenzMitarbeiter
{r.artikel}{r.soll}{r.ist} 0 ? 'text-emerald-600' : ''}`}> + {r.differenz} + {r.mitarbeiter || '—'}
{row.artikel}{row.soll}{row.ist} 0 ? '#1976d2' : '#222', fontWeight: 600 }}>{row.differenz}{row.zeitraum}{row.mitarbeiter}
-
- {/* Mobil: Cards */} -
- {rows.map((row, i) => ( -
-
{row.artikel}
-
Soll: {row.soll}   Ist: {row.ist}
-
Differenz: 0 ? '#1976d2' : '#222', fontWeight: 600 }}>{row.differenz}
-
{row.zeitraum}
-
Mitarbeiter: {row.mitarbeiter}
-
- ))} -
- - )} -
- {/* Überfällige Kunden */} - - + ))} + + +
+ ) + ) : ( +
Bitte Kunden waehlen, um Auswertung zu sehen.
+ )}
); } diff --git a/src/components/Dashboard/BildModal.jsx b/src/components/Dashboard/BildModal.jsx index 288406b..3e17cef 100644 --- a/src/components/Dashboard/BildModal.jsx +++ b/src/components/Dashboard/BildModal.jsx @@ -1,15 +1,13 @@ -import React from 'react'; - export default function BildModal({ open, url, onClose }) { if (!open) return null; return ( -
-
e.stopPropagation()}> - - Vorschau +
+
e.stopPropagation()} + className="relative bg-white rounded-xl p-3 max-w-3xl max-h-[90vh] shadow-soft"> + + Vorschau
); diff --git a/src/components/Dashboard/Dashboard.jsx b/src/components/Dashboard/Dashboard.jsx index 99b49c8..90cfa86 100644 --- a/src/components/Dashboard/Dashboard.jsx +++ b/src/components/Dashboard/Dashboard.jsx @@ -1,64 +1,55 @@ +import { useState } from 'react'; import TopNav from './TopNav'; import VorgangForm from '../Eingabe/VorgangForm'; import VorgangsListe from './VorgangsListe'; -import Logout from '../Auth/Logout'; -import PasswordChange from '../Auth/PasswordChange'; -import { useState } from 'react'; - -function AccountInfo({ user }) { - return ( -
-

Account-Info

-
E-Mail:
{user.email}
-
Name:
{user.name}
-
Rolle:
{user.rolle}
- - -
- ); -} - import AdminDashboard from './AdminDashboard'; -import MitarbeiterVerwaltung from '../MitarbeiterVerwaltung'; import KundenVerwaltung from '../KundenVerwaltung'; +import MitarbeiterVerwaltung from '../MitarbeiterVerwaltung'; +import Geraete from '../Geraete'; +import TourPlanung from '../TourPlanung'; +import Account from './Account'; -export default function Dashboard({ user }) { +export default function Dashboard({ user, onLogout }) { const [refreshFlag, setRefreshFlag] = useState(0); const [activeView, setActiveView] = useState('vorgang'); const isAdmin = user.rolle === 'admin'; return ( -
- -
- {activeView === 'account' && } +
+ +
{activeView === 'vorgang' && ( -
+
+

Neuer Vorgang

setRefreshFlag(f => f + 1)} />
)} {activeView === 'lieferungen' && ( -
-

Lieferungen

+
-
+ )} {activeView === 'ruecknahmen' && ( -
-

Rücknahmen

+
-
- )} - {isAdmin && activeView === 'admin' && ( - - )} - {isAdmin && activeView === 'mitarbeiter' && ( - - )} - {isAdmin && activeView === 'kunden' && ( - + )} + {activeView === 'tour' && } + {activeView === 'account' && } + {isAdmin && activeView === 'admin' && } + {isAdmin && activeView === 'kunden' && } + {isAdmin && activeView === 'mitarbeiter' && } + {isAdmin && activeView === 'geraete' && }
); } + +function Section({ title, children }) { + return ( +
+

{title}

+ {children} +
+ ); +} diff --git a/src/components/Dashboard/Sidebar.jsx b/src/components/Dashboard/Sidebar.jsx deleted file mode 100644 index 682253e..0000000 --- a/src/components/Dashboard/Sidebar.jsx +++ /dev/null @@ -1,40 +0,0 @@ -import { FaHome, FaList, FaBox, FaBell, FaComments, FaCog } from 'react-icons/fa'; - -const navItems = [ - { icon: , label: 'Dashboard' }, - { icon: , label: 'Vorgänge' }, - { icon: , label: 'Artikel' }, - { icon: , label: 'Benachrichtigungen' }, - { icon: , label: 'Chat' }, - { icon: , label: 'Einstellungen' }, -]; - -export default function Sidebar({ active = 0 }) { - return ( - - ); -} diff --git a/src/components/Dashboard/TopNav.jsx b/src/components/Dashboard/TopNav.jsx index e075d87..ee3a977 100644 --- a/src/components/Dashboard/TopNav.jsx +++ b/src/components/Dashboard/TopNav.jsx @@ -1,96 +1,112 @@ -import { FaUser, FaPlusCircle, FaTruck, FaUndo, FaKey, FaUsers, FaAddressBook } from 'react-icons/fa'; +import { useState, useRef, useEffect } from 'react'; +import { + FaPlusCircle, FaTruck, FaUndo, FaUsers, FaAddressBook, + FaChartBar, FaBars, FaUserCircle, FaSignOutAlt, FaBoxes, FaMapMarkedAlt +} from 'react-icons/fa'; -const baseNavItems = [ - { icon: , label: 'Account', view: 'account' }, - { icon: , label: 'Neuer Vorgang', view: 'vorgang' }, - { icon: , label: 'Lieferungen', view: 'lieferungen' }, - { icon: , label: 'Rücknahmen', view: 'ruecknahmen' }, +const mainItems = [ + { icon: FaPlusCircle, label: 'Vorgang', view: 'vorgang' }, + { icon: FaTruck, label: 'Lieferungen', view: 'lieferungen' }, + { icon: FaUndo, label: 'Ruecknahmen', view: 'ruecknahmen' }, + { icon: FaMapMarkedAlt,label: 'Tour', view: 'tour' }, ]; -import { useState } from 'react'; +const adminItems = [ + { icon: FaChartBar, label: 'Auswertung', view: 'admin' }, + { icon: FaAddressBook, label: 'Kunden', view: 'kunden' }, + { icon: FaBoxes, label: 'Geraete', view: 'geraete' }, + { icon: FaUsers, label: 'Mitarbeiter', view: 'mitarbeiter' }, +]; -export default function TopNav({ activeView, setActiveView, isAdmin }) { - const navItems = isAdmin - ? [...baseNavItems, { icon: , label: 'Admin', view: 'admin' }] - : baseNavItems; - const adminMenuItems = isAdmin - ? [ - { icon: , label: 'Mitarbeiter', view: 'mitarbeiter' }, - { icon: , label: 'Kunden', view: 'kunden' }, - { icon: , label: 'Admin', view: 'admin' }, - { icon: , label: 'Account', view: 'account' }, - ] - : [{ icon: , label: 'Account', view: 'account' }]; +export default function TopNav({ user, activeView, setActiveView, onLogout }) { + const isAdmin = user.rolle === 'admin'; + const items = mainItems; const [menuOpen, setMenuOpen] = useState(false); - const handleMenuClick = () => setMenuOpen(open => !open); - const handleMenuSelect = (view) => { - setActiveView(view); - setMenuOpen(false); - }; + const menuRef = useRef(null); + + useEffect(() => { + const handler = e => { if (menuRef.current && !menuRef.current.contains(e.target)) setMenuOpen(false); }; + document.addEventListener('mousedown', handler); + return () => document.removeEventListener('mousedown', handler); + }, []); + + const select = (v) => { setActiveView(v); setMenuOpen(false); }; return ( -
-
- Pfandsystem -
- +
+ ); } + +function NavBtn({ icon: Icon, label, active, onClick }) { + return ( + + ); +} diff --git a/src/components/Dashboard/VorgangsListe.jsx b/src/components/Dashboard/VorgangsListe.jsx index 091eff4..f0d9dff 100644 --- a/src/components/Dashboard/VorgangsListe.jsx +++ b/src/components/Dashboard/VorgangsListe.jsx @@ -1,110 +1,88 @@ import { useEffect, useState } from 'react'; import { api } from '../../api/client'; - import BildModal from './BildModal'; -const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:3001/api'; +const API_BASE = import.meta.env.VITE_API_URL || '/api'; -function FotoCell({ path }) { +function Foto({ path }) { const [open, setOpen] = useState(false); - if (!path) return <>-; - - // Foto-URL direkt vom Backend - const url = path.startsWith('http') ? path : `${API_BASE_URL.replace('/api', '')}${path}`; - + if (!path) return
; + const url = path.startsWith('http') ? path : `${API_BASE.replace('/api', '')}${path}`; return ( <> - Foto setOpen(true)} /> + Foto setOpen(true)} + className="h-16 w-16 object-cover rounded-lg ring-1 ring-slate-200 cursor-pointer hover:opacity-90" /> setOpen(false)} /> ); } export default function VorgangsListe({ refreshFlag, mode }) { - const [lieferungen, setLieferungen] = useState([]); - const [ruecknahmen, setRuecknahmen] = useState([]); - const [loading, setLoading] = useState(true); + const [list, setList] = useState([]); const [kunden, setKunden] = useState([]); const [artikel, setArtikel] = useState([]); const [filterKunde, setFilterKunde] = useState(''); const [filterArtikel, setFilterArtikel] = useState(''); const [filterVon, setFilterVon] = useState(''); const [filterBis, setFilterBis] = useState(''); + const [loading, setLoading] = useState(true); useEffect(() => { - async function loadData() { - setLoading(true); - try { - const [lieferungenData, ruecknahmenData, kundenData, artikelData] = await Promise.all([ - api.getLieferungen(), - api.getRuecknahmen(), - api.getKunden(), - api.getArtikel() - ]); - setLieferungen(lieferungenData || []); - setRuecknahmen(ruecknahmenData || []); - setKunden(kundenData || []); - setArtikel(artikelData || []); - } catch (error) { - console.error('Fehler beim Laden:', error); - } finally { - setLoading(false); - } - } - loadData(); - }, [refreshFlag]); + setLoading(true); + Promise.all([ + mode === 'lieferungen' ? api.getLieferungen() : api.getRuecknahmen(), + api.getKunden(), + api.getArtikel(), + ]).then(([d, k, a]) => { setList(d || []); setKunden(k || []); setArtikel(a || []); }) + .finally(() => setLoading(false)); + }, [refreshFlag, mode]); - if (loading) return
Lädt...
; - - let list = mode === 'lieferungen' ? lieferungen : mode === 'ruecknahmen' ? ruecknahmen : []; - - // Filter anwenden - if (filterKunde) list = list.filter(l => l.kunde_id === filterKunde); - if (filterArtikel) list = list.filter(l => l.artikel_id === filterArtikel); - if (filterVon) list = list.filter(l => new Date(l.erstellt_am) >= new Date(filterVon)); - if (filterBis) list = list.filter(l => new Date(l.erstellt_am) <= new Date(filterBis + 'T23:59:59')); + let view = list; + if (filterKunde) view = view.filter(l => l.kunde_id === filterKunde); + if (filterArtikel) view = view.filter(l => l.artikel_id === filterArtikel); + if (filterVon) view = view.filter(l => new Date(l.erstellt_am) >= new Date(filterVon)); + if (filterBis) view = view.filter(l => new Date(l.erstellt_am) <= new Date(filterBis + 'T23:59:59')); return ( - <> -
- setFilterKunde(e.target.value)}> + {kunden.map(k => )} - setFilterArtikel(e.target.value)}> + {artikel.map(a => )} - setFilterVon(e.target.value)} style={{ fontSize: 15, padding: '9px', borderRadius: 10, border: '1.2px solid #dbeafe', background: '#f6f7fb' }} placeholder='Von' /> - setFilterBis(e.target.value)} style={{ fontSize: 15, padding: '9px', borderRadius: 10, border: '1.2px solid #dbeafe', background: '#f6f7fb' }} placeholder='Bis' /> + setFilterVon(e.target.value)} /> + setFilterBis(e.target.value)} />
- {list.length === 0 ? ( -
Keine Daten vorhanden.
+ + {loading ? ( +
Lade…
+ ) : view.length === 0 ? ( +
Keine Eintraege.
) : ( -
- {list.map(item => ( -
-
- +
    + {view.map(item => ( +
  • + +
    +
    {item.kunde_name || 'Unbekannt'}
    +
    {item.artikel_bezeichnung}
    +
    + {new Date(item.erstellt_am).toLocaleString('de-DE', { dateStyle: 'short', timeStyle: 'short' })} + {item.mitarbeiter_name && <> · {item.mitarbeiter_name}} +
    -
    -
    {item.kunde_name || 'Unbekannt'}
    -
    {item.artikel_bezeichnung}
    -
    Menge: {item.anzahl}
    -
    {new Date(item.erstellt_am).toLocaleString()}
    +
    +
    {item.anzahl}
    +
    Stueck
    -
    +
  • ))} -
+ )} - +
); } diff --git a/src/components/Eingabe/._VorgangForm.jsx b/src/components/Eingabe/._VorgangForm.jsx new file mode 100644 index 0000000..988359c Binary files /dev/null and b/src/components/Eingabe/._VorgangForm.jsx differ diff --git a/src/components/Eingabe/VorgangForm.jsx b/src/components/Eingabe/VorgangForm.jsx index 3770ff2..7f1c49e 100644 --- a/src/components/Eingabe/VorgangForm.jsx +++ b/src/components/Eingabe/VorgangForm.jsx @@ -1,140 +1,141 @@ import { useEffect, useState } from 'react'; +import { FaCamera, FaInfoCircle } from 'react-icons/fa'; import { api } from '../../api/client'; -export default function VorgangForm({ user, onSubmit }) { +const API_BASE = import.meta.env.VITE_API_URL || '/api'; + +export default function VorgangForm({ onSubmit }) { const [kunden, setKunden] = useState([]); const [artikel, setArtikel] = useState([]); const [kundeId, setKundeId] = useState(''); const [artikelId, setArtikelId] = useState(''); const [anzahl, setAnzahl] = useState(1); const [foto, setFoto] = useState(null); - const [uploading, setUploading] = useState(false); - const [message, setMessage] = useState(''); + const [busy, setBusy] = useState(false); + const [msg, setMsg] = useState(''); + const [infoOpen, setInfoOpen] = useState(false); + const [kundenDetail, setKundenDetail] = useState(null); useEffect(() => { - async function loadData() { - try { - const [kundenData, artikelData] = await Promise.all([ - api.getKunden(), - api.getArtikel() - ]); - setKunden(kundenData || []); - setArtikel(artikelData || []); - } catch (error) { - console.error('Fehler beim Laden:', error); - } - } - loadData(); + Promise.all([api.getKunden(), api.getArtikel()]) + .then(([k, a]) => { setKunden(k || []); setArtikel(a || []); }); }, []); - const handleFotoChange = (e) => { - if (e.target.files && e.target.files[0]) { - setFoto(e.target.files[0]); - } + const openInfo = async () => { + if (!kundeId) return; + try { setKundenDetail(await api.getKunde(kundeId)); setInfoOpen(true); } + catch (e) { setMsg(e.message); } }; - const handleSubmit = async (e, typ) => { - e.preventDefault(); - setUploading(true); - setMessage(''); - + const submit = async (typ) => { + if (!kundeId || !artikelId) return setMsg('Bitte Kunde und Artikel waehlen'); + setBusy(true); setMsg(''); try { - const vorgangData = { - kunde_id: kundeId, - artikel_id: artikelId, - anzahl: parseInt(anzahl) - }; - - if (typ === 'lieferung') { - await api.createLieferung(vorgangData, foto); - } else { - await api.createRuecknahme(vorgangData, foto); - } - - setMessage('Vorgang gespeichert!'); - setKundeId(''); - setArtikelId(''); - setAnzahl(1); - setFoto(null); - if (onSubmit) onSubmit(); - } catch (error) { - setMessage('Fehler beim Speichern: ' + error.message); - } finally { - setUploading(false); - } + const data = { kunde_id: kundeId, artikel_id: artikelId, anzahl: parseInt(anzahl) }; + if (typ === 'lieferung') await api.createLieferung(data, foto); + else await api.createRuecknahme(data, foto); + setMsg('Gespeichert.'); setKundeId(''); setArtikelId(''); setAnzahl(1); setFoto(null); + onSubmit?.(); + } catch (e) { setMsg(e.message); } finally { setBusy(false); } }; return ( -
e.preventDefault()}> -

Neuer Vorgang

- - - setAnzahl(e.target.value)} required placeholder="Anzahl" style={{ fontSize: 18, padding: '14px', borderRadius: 12, border: '1.5px solid #dbeafe', background: '#f6f7fb', outline: 'none' }} /> - - -
- - +
+
+ +
+ + +
- {message &&
{message}
} - + +
+ + +
+ +
+ + setAnzahl(e.target.value)} /> +
+ +
+ setFoto(e.target.files?.[0] || null)} /> + +
+ + {msg &&
{msg}
} + +
+ + +
+ + {infoOpen && kundenDetail && ( + setInfoOpen(false)} /> + )} +
+ ); +} + +function KundeInfoModal({ kunde, onClose }) { + return ( +
+
e.stopPropagation()} className="bg-white rounded-xl max-w-lg w-full p-6 max-h-[85vh] overflow-y-auto shadow-soft"> +
+
+

{kunde.name}

+ {kunde.adresse &&

{kunde.adresse}

} +
+ +
+ {kunde.lieferzeit_bis && ( +
+ Anlieferung bis {kunde.lieferzeit_bis} Uhr +
+ )} + {kunde.anlieferungshinweis && ( +
+ {kunde.anlieferungshinweis} +
+ )} + {kunde.bilder?.length > 0 && ( +
+
Anlieferungs-Fotos
+
+ {kunde.bilder.map(b => { + const url = b.dateiname.startsWith('http') + ? b.dateiname + : `${(import.meta.env.VITE_API_URL || '/api').replace('/api', '')}${b.dateiname}`; + return ( +
+ + {b.beschreibung &&
{b.beschreibung}
} +
+ ); + })} +
+
+ )} + {kunde.adresse && ( + + Navigieren → + + )} +
+
); } diff --git a/src/components/Geraete.jsx b/src/components/Geraete.jsx new file mode 100644 index 0000000..211e84b --- /dev/null +++ b/src/components/Geraete.jsx @@ -0,0 +1,151 @@ +import { useEffect, useState } from 'react'; +import { FaPlus, FaEdit, FaTrash, FaTimes } from 'react-icons/fa'; +import { api } from '../api/client'; + +const STATUS = { + aktiv: 'badge-green', + in_reparatur: 'badge-amber', + lager: 'badge-slate', + ausgemustert: 'badge-rose', +}; + +const empty = { seriennummer: '', typ: '', bezeichnung: '', kunde_id: '', status: 'aktiv', notiz: '' }; + +export default function Geraete() { + const [list, setList] = useState([]); + const [kunden, setKunden] = useState([]); + const [form, setForm] = useState(empty); + const [editId, setEditId] = useState(null); + const [error, setError] = useState(''); + const [loading, setLoading] = useState(true); + const [filterTyp, setFilterTyp] = useState(''); + const [filterStatus, setFilterStatus] = useState(''); + + const load = async () => { + setLoading(true); + try { const [g, k] = await Promise.all([api.getGeraete(), api.getKunden()]); setList(g); setKunden(k); } + catch (e) { setError(e.message); } finally { setLoading(false); } + }; + useEffect(() => { load(); }, []); + + const startEdit = (g) => { + setEditId(g.id); + setForm({ + seriennummer: g.seriennummer, typ: g.typ, bezeichnung: g.bezeichnung || '', + kunde_id: g.kunde_id || '', status: g.status, notiz: g.notiz || '' + }); + }; + const cancel = () => { setEditId(null); setForm(empty); }; + const save = async () => { + if (!form.seriennummer || !form.typ) return setError('Seriennummer + Typ erforderlich'); + try { + if (editId) await api.updateGeraet(editId, form); else await api.createGeraet(form); + cancel(); setError(''); load(); + } catch (e) { setError(e.message); } + }; + const del = async (id) => { if (!confirm('Geraet loeschen?')) return; await api.deleteGeraet(id); load(); }; + + const typen = Array.from(new Set(list.map(g => g.typ))); + const view = list.filter(g => + (!filterTyp || g.typ === filterTyp) && (!filterStatus || g.status === filterStatus) + ); + + return ( +
+
+

Geraete

+
{list.length} Geraete
+
+ {error &&
{error}
} + +
+

{editId ? 'Geraet bearbeiten' : 'Neues Geraet'}

+
+ + setForm(f => ({ ...f, seriennummer: e.target.value }))} /> + + + setForm(f => ({ ...f, typ: e.target.value }))} /> + + + setForm(f => ({ ...f, bezeichnung: e.target.value }))} /> + + + + + + + + + setForm(f => ({ ...f, notiz: e.target.value }))} /> + +
+
+ + {editId && } +
+
+ +
+ + +
+ +
+ {loading ?
Lade…
: ( + + + + + + + + + + + + + {view.map(g => ( + + + + + + + + + ))} + {view.length === 0 && } + +
SNTypBezeichnungBei KundeStatus
{g.seriennummer}{g.typ}{g.bezeichnung || '—'}{g.kunde_name || }{g.status} + + +
Keine Geraete.
+ )} +
+
+ ); +} + +function Field({ label, children }) { + return
{children}
; +} diff --git a/src/components/KundenVerwaltung.jsx b/src/components/KundenVerwaltung.jsx index cd2be62..51ca48b 100644 --- a/src/components/KundenVerwaltung.jsx +++ b/src/components/KundenVerwaltung.jsx @@ -1,168 +1,220 @@ import { useEffect, useState } from 'react'; +import { FaEdit, FaTrash, FaCamera, FaTimes, FaPlus } from 'react-icons/fa'; import { api } from '../api/client'; +const API_BASE = import.meta.env.VITE_API_URL || '/api'; +const emptyForm = { + name: '', adresse: '', email: '', telefon: '', anrede: '', ansprechpartner: '', + notiz: '', anlieferungshinweis: '', lieferzeit_bis: '' +}; + export default function KundenVerwaltung() { const [kunden, setKunden] = useState([]); + const [editId, setEditId] = useState(null); + const [form, setForm] = useState(emptyForm); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); - const [editId, setEditId] = useState(null); - const [form, setForm] = useState({ name: '', adresse: '', email: '', telefon: '', anrede: '', ansprechpartner: '', notiz: '' }); + const [openBilder, setOpenBilder] = useState(null); // kunde-id mit bilder-modal - useEffect(() => { fetchKunden(); }, []); - - async function fetchKunden() { + const load = async () => { setLoading(true); - try { - const data = await api.getKunden(); - setKunden(data || []); - setError(''); - } catch (err) { - setError(err.message); - } finally { - setLoading(false); - } - } + try { setKunden(await api.getKunden()); setError(''); } + catch (e) { setError(e.message); } + finally { setLoading(false); } + }; + useEffect(() => { load(); }, []); - function handleEdit(k) { + const startEdit = (k) => { setEditId(k.id); setForm({ - name: k.name || '', - adresse: k.adresse || '', - email: k.email || '', - telefon: k.telefon || '', - anrede: k.anrede || '', - ansprechpartner: k.ansprechpartner || '', - notiz: k.notiz || '' + name: k.name || '', adresse: k.adresse || '', email: k.email || '', + telefon: k.telefon || '', anrede: k.anrede || '', + ansprechpartner: k.ansprechpartner || '', notiz: k.notiz || '', + anlieferungshinweis: k.anlieferungshinweis || '', + lieferzeit_bis: k.lieferzeit_bis || '' }); - } + }; + const cancel = () => { setEditId(null); setForm(emptyForm); }; - async function handleSave() { - if (!form.name) return setError('Name ist erforderlich'); + const save = async () => { + if (!form.name) return setError('Name erforderlich'); try { - if (editId) { - await api.updateKunde(editId, form); - } else { - await api.createKunde(form); - } - setEditId(null); - setForm({ name: '', adresse: '', email: '', telefon: '', anrede: '', ansprechpartner: '', notiz: '' }); - fetchKunden(); - } catch (err) { - setError(err.message); - } - } + if (editId) await api.updateKunde(editId, form); + else await api.createKunde(form); + cancel(); load(); + } catch (e) { setError(e.message); } + }; - function handleCancel() { - setEditId(null); - setForm({ name: '', adresse: '', email: '', telefon: '', anrede: '', ansprechpartner: '', notiz: '' }); - } - - async function handleDelete(id) { - if (!confirm('Kunde wirklich löschen?')) return; - try { - await api.deleteKunde(id); - fetchKunden(); - } catch (err) { - setError(err.message); - } - } - - function handleChange(e) { - setForm(f => ({ ...f, [e.target.name]: e.target.value })); - } + const del = async (id) => { + if (!confirm('Kunde wirklich loeschen?')) return; + try { await api.deleteKunde(id); load(); } catch (e) { setError(e.message); } + }; return ( -
-

Kundenverwaltung

- {loading &&
Lade...
} - {error &&
{error}
} -
- - - - - - - - {editId ? ( - <> - - - - ) : ( - - )} +
+
+

Kunden

+
{kunden.length} Kunden
- {/* Responsive Darstellung: Tabelle auf Desktop, Karten auf Mobil */} -
-
- - - - - - - - - - - + + {error &&
{error}
} + +
+

{editId ? 'Kunde bearbeiten' : 'Neuer Kunde'}

+
+ + setForm(f => ({ ...f, name: e.target.value }))} /> + + + setForm(f => ({ ...f, adresse: e.target.value }))} /> + + + setForm(f => ({ ...f, email: e.target.value }))} /> + + + setForm(f => ({ ...f, telefon: e.target.value }))} /> + + + + + + setForm(f => ({ ...f, ansprechpartner: e.target.value }))} /> + + + setForm(f => ({ ...f, lieferzeit_bis: e.target.value }))} /> + + + setForm(f => ({ ...f, notiz: e.target.value }))} /> + +
+ +
NameAdresseE-MailTelefonAnredeAnsprechpartnerNotizAktionen