Compare commits
3 Commits
4aa6ab3eb5
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 7527a189ce | |||
| e7b9d27314 | |||
| b53a5ae80a |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -15,6 +15,9 @@ dist-ssr
|
|||||||
# Environment variables
|
# Environment variables
|
||||||
.env
|
.env
|
||||||
|
|
||||||
|
# CORS-Proxy Konfiguration (enthält Credentials)
|
||||||
|
cors-config.php
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.vscode/*
|
.vscode/*
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
|
|||||||
13
ACHTUNG.md
Normal file
13
ACHTUNG.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# ACHTUNG
|
||||||
|
2025-10-29
|
||||||
|
|
||||||
|
Es gibt extreme Probleme mit CORS beim Zugriff auf die Sternwarte. Lokal auf localhost funtioniert die Anwendung jetzt richtig gut.
|
||||||
|
|
||||||
|
M.E. macht es keinen Sinn, so weiter zu machen. Sinnvoll ist es, einen neuen Server für die Sternwarte aufzusetzen, auf dem man dann problemlos das Alles laufen lassen kann (da ja dann Alles 'localhost' ist.)
|
||||||
|
|
||||||
|
Vorschlag: IONOS oder STRATO oder HETZNER oder ....
|
||||||
|
|
||||||
|
Vergleich in einer Numbers-Tabelle (in der iCloud unter Numbers/Vergleich_Server)
|
||||||
|
|
||||||
|
|
||||||
|
rxf
|
||||||
275
README.md
275
README.md
@@ -1,16 +1,273 @@
|
|||||||
# React + Vite
|
# BeoAnswer React App
|
||||||
|
|
||||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
Eine React-Anwendung zur Nachbearbeitung von Sonderführungen mit Backend-Integration.
|
||||||
|
|
||||||
Currently, two official plugins are available:
|
## 📋 Features
|
||||||
|
|
||||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
|
- **Interaktive Formulare** für Führungsnachbearbeitung
|
||||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
- **Backend-Integration** mit PHP über FormData
|
||||||
|
- **HTTP Basic Authentication** Support
|
||||||
|
- **Professionelle Modal-Dialoge** anstatt Browser-Alerts
|
||||||
|
- **Intelligente Navigation** mit Zurück-Button
|
||||||
|
- **Automatisches Fenster schließen** nach Aktionen
|
||||||
|
- **Environment-Variable Konfiguration**
|
||||||
|
- **Responsive Design**
|
||||||
|
|
||||||
## React Compiler
|
## 🚀 Quick Start
|
||||||
|
|
||||||
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
|
### Voraussetzungen
|
||||||
|
|
||||||
## Expanding the ESLint configuration
|
- Node.js (v16 oder höher)
|
||||||
|
- npm oder yarn
|
||||||
|
|
||||||
If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
|
### Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Repository klonen
|
||||||
|
git clone <repository-url>
|
||||||
|
cd beoanswer_react
|
||||||
|
|
||||||
|
# Dependencies installieren
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# Terser für Production Builds installieren
|
||||||
|
npm install --save-dev terser
|
||||||
|
|
||||||
|
# Environment-Datei erstellen
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
### Konfiguration
|
||||||
|
|
||||||
|
Erstelle eine `.env` Datei und passe die Werte an:
|
||||||
|
|
||||||
|
```env
|
||||||
|
# Backend API Configuration
|
||||||
|
VITE_API_URL=/api/intern/sofue/php/sofueDB.php
|
||||||
|
|
||||||
|
# HTTP Basic Authentication für geschütztes Backend
|
||||||
|
VITE_API_USERNAME=dein_username
|
||||||
|
VITE_API_PASSWORD=dein_passwort
|
||||||
|
```
|
||||||
|
|
||||||
|
### Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Development Server starten
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# App öffnet sich auf http://localhost:5173
|
||||||
|
# Mit URL-Parameter: http://localhost:5173/?id=123
|
||||||
|
```
|
||||||
|
|
||||||
|
### Production
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Production Build erstellen
|
||||||
|
npm run build:prod
|
||||||
|
|
||||||
|
# Production Preview
|
||||||
|
npm run preview:prod
|
||||||
|
|
||||||
|
# Build-Dateien befinden sich in ./dist/
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📦 Versionsverwaltung
|
||||||
|
|
||||||
|
Die App zeigt automatisch die Version aus der `package.json` und das aktuelle Build-Datum an.
|
||||||
|
|
||||||
|
### Version erhöhen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Patch-Version erhöhen (1.0.0 → 1.0.1)
|
||||||
|
npm version patch
|
||||||
|
|
||||||
|
# Minor-Version erhöhen (1.0.0 → 1.1.0)
|
||||||
|
npm version minor
|
||||||
|
|
||||||
|
# Major-Version erhöhen (1.0.0 → 2.0.0)
|
||||||
|
npm version major
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manuell in package.json
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"version": "1.2.3"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Wichtig:** Nach Versionänderungen den Development Server neu starten:
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🛠 Scripts
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev # Development Server
|
||||||
|
npm run build # Standard Build
|
||||||
|
npm run build:prod # Production Build
|
||||||
|
npm run preview # Preview des Builds
|
||||||
|
npm run preview:prod # Preview des Production Builds
|
||||||
|
npm run lint # Code Linting
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🌐 Deployment
|
||||||
|
|
||||||
|
### 1. Environment Variables setzen
|
||||||
|
|
||||||
|
Für Production eine `.env.production` erstellen:
|
||||||
|
|
||||||
|
```env
|
||||||
|
VITE_API_URL=https://dein-server.com/intern/sofue/php/sofueDB.php
|
||||||
|
VITE_API_USERNAME=production_user
|
||||||
|
VITE_API_PASSWORD=production_password
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Build erstellen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build:prod
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Dateien auf Server kopieren
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Beispiel mit rsync
|
||||||
|
rsync -avz dist/ user@server:/var/www/html/beoanswer/
|
||||||
|
|
||||||
|
# Oder mit scp
|
||||||
|
scp -r dist/* user@server:/var/www/html/beoanswer/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Webserver konfigurieren
|
||||||
|
|
||||||
|
Siehe [DEPLOYMENT.md](./DEPLOYMENT.md) für detaillierte Anweisungen.
|
||||||
|
|
||||||
|
## 📱 Verwendung
|
||||||
|
|
||||||
|
### URL-Parameter
|
||||||
|
|
||||||
|
Die App erwartet einen `id` URL-Parameter:
|
||||||
|
|
||||||
|
```
|
||||||
|
https://dein-server.com/beoanswer/?id=123
|
||||||
|
```
|
||||||
|
|
||||||
|
### Workflow
|
||||||
|
|
||||||
|
1. **Link aus E-Mail** öffnen
|
||||||
|
2. **Formular ausfüllen**:
|
||||||
|
- Ja/Nein ob Führung stattfand
|
||||||
|
- Bei "Ja": Besucherzahl, Spenden-Art, etc.
|
||||||
|
- Bei "Nein": Absage oder Verschiebung
|
||||||
|
3. **Daten speichern** - Fenster schließt automatisch
|
||||||
|
|
||||||
|
### Navigation
|
||||||
|
|
||||||
|
- **Zurück-Button**: Schrittweise Navigation rückwärts
|
||||||
|
- **Abbruch**: Bestätigung mit Modal, dann Fenster schließen
|
||||||
|
- **Anleitung**: Hilfe-Modal mit Workflow-Beschreibung
|
||||||
|
|
||||||
|
## 🔧 Entwicklung
|
||||||
|
|
||||||
|
### Projektstruktur
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── App.jsx # Hauptkomponente mit Routing-Logik
|
||||||
|
├── FormContext.jsx # Globaler State für Formulardaten
|
||||||
|
├── main.jsx # React App Entry Point
|
||||||
|
├── App.css # Haupt-Styling
|
||||||
|
├── components/
|
||||||
|
│ ├── BesucherBar.jsx # Eingabe für Besucher/Betrag
|
||||||
|
│ ├── Bemerkungen.jsx # Textarea für Bemerkungen
|
||||||
|
│ ├── FandStattVer.jsx # Radio-Button Komponente
|
||||||
|
│ ├── LastButtons.jsx # Abbruch/Anleitung/Senden Buttons
|
||||||
|
│ ├── LastLine.jsx # Version/Datum Anzeige
|
||||||
|
│ ├── Modal.jsx # Standard Modal Dialog
|
||||||
|
│ ├── ConfirmModal.jsx # Bestätigungs Modal
|
||||||
|
│ ├── Spende.jsx # Spenden-Art Auswahl
|
||||||
|
│ └── Verschoben.jsx # Datum-Eingabe für Verschiebung
|
||||||
|
```
|
||||||
|
|
||||||
|
### FormContext
|
||||||
|
|
||||||
|
Zentrale State-Verwaltung für alle Formulardaten:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const { formData, updateFormData, resetFormData } = useFormData()
|
||||||
|
|
||||||
|
// Daten setzen
|
||||||
|
updateFormData('besucher', '15')
|
||||||
|
|
||||||
|
// Daten lesen
|
||||||
|
console.log(formData.besucher)
|
||||||
|
|
||||||
|
// Formular zurücksetzen
|
||||||
|
resetFormData()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Backend Integration
|
||||||
|
|
||||||
|
Die App sendet Daten via FormData an das PHP Backend:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const backendData = new FormData()
|
||||||
|
backendData.append('cmd', 'UPDATEAFTER')
|
||||||
|
backendData.append('id', id)
|
||||||
|
backendData.append('besucher', formData.besucher)
|
||||||
|
// ...weitere Felder
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🐛 Troubleshooting
|
||||||
|
|
||||||
|
### Build-Fehler: "terser not found"
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install --save-dev terser
|
||||||
|
```
|
||||||
|
|
||||||
|
### CORS-Fehler im Development
|
||||||
|
|
||||||
|
Vite Proxy ist konfiguriert für `localhost:8080`. Anpassen in `vite.config.js`:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
proxy: {
|
||||||
|
'/api': 'http://localhost:8080'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Environment Variables werden nicht geladen
|
||||||
|
|
||||||
|
1. Development Server neu starten
|
||||||
|
2. Variablen müssen mit `VITE_` beginnen
|
||||||
|
3. `.env` Datei im Projektroot erstellen
|
||||||
|
|
||||||
|
### Modal-Buttons haben keinen Abstand
|
||||||
|
|
||||||
|
Browser-Cache leeren oder Hard-Refresh (`Ctrl+F5`).
|
||||||
|
|
||||||
|
## 📄 Weitere Dokumentation
|
||||||
|
|
||||||
|
- [DEPLOYMENT.md](./DEPLOYMENT.md) - Detaillierte Deployment-Anweisungen
|
||||||
|
- [.env.example](./.env.example) - Environment Variable Template
|
||||||
|
|
||||||
|
## 🤝 Beitragen
|
||||||
|
|
||||||
|
1. Feature Branch erstellen
|
||||||
|
2. Änderungen committen
|
||||||
|
3. Version mit `npm version` erhöhen
|
||||||
|
4. Build testen: `npm run build:prod`
|
||||||
|
5. Pull Request erstellen
|
||||||
|
|
||||||
|
## 📝 Lizenz
|
||||||
|
|
||||||
|
Private Projekt - Alle Rechte vorbehalten.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Version:** Automatisch aus package.json
|
||||||
|
**Build-Datum:** Automatisch generiert
|
||||||
|
**Letztes Update:** Oktober 2025
|
||||||
|
|||||||
114
cors-proxy.php
Normal file
114
cors-proxy.php
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* CORS Proxy für sofueDB.php
|
||||||
|
* Diese Datei muss in einem öffentlich zugänglichen Verzeichnis der Website liegen
|
||||||
|
*/
|
||||||
|
|
||||||
|
// CORS Headers für Frontend-Zugriff
|
||||||
|
$allowedOrigins = [
|
||||||
|
'http://localhost:5173',
|
||||||
|
'https://ihre-produktions-domain.de' // Ersetzen Sie durch Ihre echte Domain
|
||||||
|
];
|
||||||
|
|
||||||
|
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
|
||||||
|
if (in_array($origin, $allowedOrigins)) {
|
||||||
|
header("Access-Control-Allow-Origin: $origin");
|
||||||
|
} else {
|
||||||
|
// Für Development: localhost mit beliebigen Ports erlauben
|
||||||
|
if (preg_match('/^http:\/\/localhost:\d+$/', $origin)) {
|
||||||
|
header("Access-Control-Allow-Origin: $origin");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
header("Access-Control-Allow-Methods: POST, GET, OPTIONS");
|
||||||
|
header("Access-Control-Allow-Headers: Content-Type, Authorization");
|
||||||
|
header("Access-Control-Allow-Credentials: true");
|
||||||
|
|
||||||
|
// Preflight-Request abfangen
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||||
|
http_response_code(200);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nur POST-Requests erlauben
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
http_response_code(405);
|
||||||
|
echo 'Method Not Allowed';
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backend-URL und Credentials aus Environment oder Config
|
||||||
|
$backendUrl = 'https://sternwarte-welzheim.de/intern/sofue/php/sofueDB.php';
|
||||||
|
|
||||||
|
// Credentials sicher laden - verschiedene Optionen:
|
||||||
|
// Option 1: Aus Environment Variables (empfohlen)
|
||||||
|
$username = getenv('SOFUE_USERNAME') ?: $_ENV['SOFUE_USERNAME'] ?? null;
|
||||||
|
$password = getenv('SOFUE_PASSWORD') ?: $_ENV['SOFUE_PASSWORD'] ?? null;
|
||||||
|
|
||||||
|
// Option 2: Aus separater Config-Datei (Fallback)
|
||||||
|
if (!$username || !$password) {
|
||||||
|
$configFile = __DIR__ . '/cors-config.php';
|
||||||
|
if (file_exists($configFile)) {
|
||||||
|
include $configFile;
|
||||||
|
// cors-config.php sollte enthalten:
|
||||||
|
// <?php $username = 'beogruppe'; $password = 'ArktUhr'; ?>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Option 3: Letzter Fallback - aber sicherer als Klartext
|
||||||
|
if (!$username || !$password) {
|
||||||
|
// Base64-kodiert (minimal obfuskiert, aber nicht wirklich sicher)
|
||||||
|
$encoded = 'YmVvZ3J1cHBlOkFya3RVaHI='; // beogruppe:ArktUhr
|
||||||
|
$decoded = base64_decode($encoded);
|
||||||
|
list($username, $password) = explode(':', $decoded, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sicherheitscheck
|
||||||
|
if (!$username || !$password) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo 'Server configuration error';
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST-Daten aus dem Frontend übernehmen
|
||||||
|
$postData = $_POST;
|
||||||
|
|
||||||
|
// Debug-Log (optional, für Entwicklung)
|
||||||
|
error_log("CORS-Proxy: Weiterleitung an Backend mit " . count($postData) . " Parametern");
|
||||||
|
|
||||||
|
// cURL-Request an das geschützte Backend
|
||||||
|
$ch = curl_init();
|
||||||
|
curl_setopt($ch, CURLOPT_URL, $backendUrl);
|
||||||
|
curl_setopt($ch, CURLOPT_POST, true);
|
||||||
|
curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
|
||||||
|
curl_setopt($ch, CURLOPT_USERPWD, "$username:$password");
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
|
||||||
|
|
||||||
|
// Response vom Backend holen
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
$error = curl_error($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
// Fehlerbehandlung
|
||||||
|
if ($response === false) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo "Backend-Verbindungsfehler: " . $error;
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTP-Status vom Backend übernehmen
|
||||||
|
http_response_code($httpCode);
|
||||||
|
|
||||||
|
// Content-Type vom Backend übernehmen (falls JSON)
|
||||||
|
if (strpos($response, '{') === 0 || strpos($response, '[') === 0) {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
} else {
|
||||||
|
header('Content-Type: text/plain');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response vom Backend weiterleiten
|
||||||
|
echo $response;
|
||||||
|
?>
|
||||||
1562
package-lock.json
generated
1562
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "beoanswer_react",
|
"name": "beoanswer_react",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
@@ -24,6 +24,7 @@
|
|||||||
"eslint-plugin-react-hooks": "^5.2.0",
|
"eslint-plugin-react-hooks": "^5.2.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.22",
|
"eslint-plugin-react-refresh": "^0.4.22",
|
||||||
"globals": "^16.4.0",
|
"globals": "^16.4.0",
|
||||||
|
"terser": "^5.44.0",
|
||||||
"vite": "^7.1.7"
|
"vite": "^7.1.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
147
public/anleitung.html
Normal file
147
public/anleitung.html
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>BeoAnswer - Anleitung</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: #333;
|
||||||
|
border-bottom: 2px solid #007bff;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
color: #555;
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
.step {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-left: 4px solid #007bff;
|
||||||
|
padding: 15px;
|
||||||
|
margin: 15px 0;
|
||||||
|
border-radius: 0 8px 8px 0;
|
||||||
|
}
|
||||||
|
.step h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
color: #007bff;
|
||||||
|
}
|
||||||
|
.highlight {
|
||||||
|
background: #fff3cd;
|
||||||
|
border: 1px solid #ffeaa7;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
ul {
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
li {
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
|
.tip {
|
||||||
|
background: #d1ecf1;
|
||||||
|
border: 1px solid #bee5eb;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin: 15px 0;
|
||||||
|
}
|
||||||
|
.tip::before {
|
||||||
|
content: "💡 ";
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>📋 BeoAnswer - Anleitung</h1>
|
||||||
|
|
||||||
|
<div class="highlight">
|
||||||
|
<strong>Willkommen!</strong> Diese Anleitung hilft Ihnen bei der Nachbearbeitung von Sonderführungen.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>🚀 Schnellstart</h2>
|
||||||
|
<p>Die Anwendung führt Sie Schritt für Schritt durch die Nachbearbeitung. Folgen Sie einfach den Anweisungen auf dem Bildschirm.</p>
|
||||||
|
|
||||||
|
<h2>📝 Workflow</h2>
|
||||||
|
|
||||||
|
<div class="step">
|
||||||
|
<h3>1. Grundfrage beantworten</h3>
|
||||||
|
<p><strong>"Fand die Führung statt?"</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Ja:</strong> Weiter zu Schritt 2</li>
|
||||||
|
<li><strong>Nein:</strong> Weiter zu Schritt 5</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="step">
|
||||||
|
<h3>2. Besucherzahl eingeben (nur bei "Ja")</h3>
|
||||||
|
<p>Geben Sie die Anzahl der Teilnehmer ein und klicken Sie auf "OK".</p>
|
||||||
|
<div class="tip">Sie können auch die Enter-Taste drücken.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="step">
|
||||||
|
<h3>3. Spenden-Art auswählen (nur bei "Ja")</h3>
|
||||||
|
<p>Wählen Sie aus:</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Barspende:</strong> Weiter zu Schritt 4</li>
|
||||||
|
<li><strong>Wird überwiesen:</strong> Weiter zu Schritt 5</li>
|
||||||
|
<li><strong>Spendenkässle:</strong> Weiter zu Schritt 5</li>
|
||||||
|
<li><strong>Keine Spende:</strong> Weiter zu Schritt 5</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="step">
|
||||||
|
<h3>4. Spendenbetrag eingeben (nur bei Barspende)</h3>
|
||||||
|
<p>Geben Sie den Betrag der Barspende in Euro ein.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="step">
|
||||||
|
<h3>5. Bemerkungen hinzufügen (optional)</h3>
|
||||||
|
<p>Hier können Sie zusätzliche Informationen zur Führung eingeben:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Besonderheiten</li>
|
||||||
|
<li>Probleme</li>
|
||||||
|
<li>Feedback der Teilnehmer</li>
|
||||||
|
<li>Sonstige Anmerkungen</li>
|
||||||
|
</ul>
|
||||||
|
<div class="tip">Verwenden Sie Strg+Enter (oder Cmd+Enter) zum schnellen Speichern.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>❌ Bei abgesagten/verschobenen Führungen</h2>
|
||||||
|
|
||||||
|
<div class="step">
|
||||||
|
<h3>1. Grund auswählen</h3>
|
||||||
|
<p><strong>"Die Führung wurde"</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Abgesagt:</strong> Direkt zum Senden</li>
|
||||||
|
<li><strong>Verschoben:</strong> Weiter zu Schritt 2</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="step">
|
||||||
|
<h3>2. Neues Datum eingeben (nur bei "Verschoben")</h3>
|
||||||
|
<p>Wählen Sie das neue Datum und die Uhrzeit für die verschobene Führung.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>🔄 Navigation</h2>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Zurück-Button:</strong> Geht einen Schritt zurück und löscht die entsprechenden Eingaben</li>
|
||||||
|
<li><strong>Abbruch:</strong> Bricht den Vorgang ab (mit Sicherheitsabfrage)</li>
|
||||||
|
<li><strong>Anleitung:</strong> Zeigt diese Hilfe an</li>
|
||||||
|
<li><strong>Senden:</strong> Speichert alle Daten und schließt das Fenster</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>✅ Abschluss</h2>
|
||||||
|
<p>Nach dem Klick auf "Senden" werden die Daten gespeichert und das Fenster schließt sich automatisch. Sie kehren zur ursprünglichen Anwendung zurück.</p>
|
||||||
|
|
||||||
|
<div class="highlight">
|
||||||
|
<strong>Fragen oder Probleme?</strong> Wenden Sie sich an den Administrator.
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
10
src/App.jsx
10
src/App.jsx
@@ -51,7 +51,7 @@ function AppContent() {
|
|||||||
|
|
||||||
console.log('Loading data for ID:', id)
|
console.log('Loading data for ID:', id)
|
||||||
|
|
||||||
// Backend-Aufruf mit Proxy
|
// Backend-Aufruf mit HTTP Basic Auth
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
formData.append('cmd', 'GET_ONE')
|
formData.append('cmd', 'GET_ONE')
|
||||||
formData.append('id', id)
|
formData.append('id', id)
|
||||||
@@ -235,15 +235,15 @@ function AppContent() {
|
|||||||
// LastButtons IMMER anzeigen, aber Senden-Button nur wenn bereit
|
// LastButtons IMMER anzeigen, aber Senden-Button nur wenn bereit
|
||||||
const sendenBereit = () => {
|
const sendenBereit = () => {
|
||||||
if (pfad === 'ja') {
|
if (pfad === 'ja') {
|
||||||
// JA-Pfad: vollständig wenn Bemerkungen-Schritt erreicht
|
// JA-Pfad: vollständig wenn Bemerkungen-Schritt ABGESCHLOSSEN ist
|
||||||
const bemerkungsSchritt = (formData.spendenArt === 'bar') ? 4 : 3
|
const bemerkungsSchritt = (formData.spendenArt === 'bar') ? 4 : 3
|
||||||
return schritt >= bemerkungsSchritt
|
return schritt > bemerkungsSchritt // NACH dem Bemerkungsschritt, nicht beim Erreichen
|
||||||
} else if (pfad === 'nein') {
|
} else if (pfad === 'nein') {
|
||||||
// NEIN-Pfad: vollständig wenn abgesagt ODER verschoben mit Datum
|
// NEIN-Pfad: vollständig wenn abgesagt ODER verschoben mit Datum
|
||||||
if (formData.abgesagt === 'abgesagt') {
|
if (formData.abgesagt === 'abgesagt') {
|
||||||
return schritt >= 2
|
return schritt >= 2 // Beim Erreichen von Schritt 2 (nach Auswahl abgesagt)
|
||||||
} else if (formData.abgesagt === 'verschoben') {
|
} else if (formData.abgesagt === 'verschoben') {
|
||||||
return schritt >= 3 && formData.neuesDatum
|
return schritt >= 3 && formData.neuesDatum // Beim Erreichen von Schritt 3 mit Datum
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export default function LastButtons({ mitSend, mitBack, handleBack}) {
|
|||||||
const [showModal, setShowModal] = useState(false)
|
const [showModal, setShowModal] = useState(false)
|
||||||
const [modalMessage, setModalMessage] = useState('')
|
const [modalMessage, setModalMessage] = useState('')
|
||||||
const [modalType, setModalType] = useState('error') // 'error' oder 'success'
|
const [modalType, setModalType] = useState('error') // 'error' oder 'success'
|
||||||
|
const [isModalHtml, setIsModalHtml] = useState(false)
|
||||||
const [showConfirmModal, setShowConfirmModal] = useState(false)
|
const [showConfirmModal, setShowConfirmModal] = useState(false)
|
||||||
const [isSuccessModal, setIsSuccessModal] = useState(false)
|
const [isSuccessModal, setIsSuccessModal] = useState(false)
|
||||||
|
|
||||||
@@ -19,11 +20,11 @@ export default function LastButtons({ mitSend, mitBack, handleBack}) {
|
|||||||
setIsSending(true)
|
setIsSending(true)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// API URL und Auth-Daten aus Environment
|
// API URL aus Environment Variable
|
||||||
const APIURL = import.meta.env.VITE_API_URL
|
const APIURL = import.meta.env.VITE_API_URL
|
||||||
const username = import.meta.env.VITE_API_USERNAME
|
const username = import.meta.env.VITE_API_USERNAME
|
||||||
const password = import.meta.env.VITE_API_PASSWORD
|
const password = import.meta.env.VITE_API_PASSWORD
|
||||||
|
|
||||||
if (!APIURL) {
|
if (!APIURL) {
|
||||||
throw new Error('API URL nicht konfiguriert.')
|
throw new Error('API URL nicht konfiguriert.')
|
||||||
}
|
}
|
||||||
@@ -122,7 +123,7 @@ export default function LastButtons({ mitSend, mitBack, handleBack}) {
|
|||||||
// Versuche JSON zu parsen
|
// Versuche JSON zu parsen
|
||||||
result = JSON.parse(responseText)
|
result = JSON.parse(responseText)
|
||||||
console.log('Backend Response (parsed):', result)
|
console.log('Backend Response (parsed):', result)
|
||||||
} catch (e) {
|
} catch {
|
||||||
// Falls kein JSON, behandle als einfachen Text
|
// Falls kein JSON, behandle als einfachen Text
|
||||||
console.log('Backend Response ist kein JSON, behandle als Text')
|
console.log('Backend Response ist kein JSON, behandle als Text')
|
||||||
result = { success: responseText.trim() === 'true', raw: responseText }
|
result = { success: responseText.trim() === 'true', raw: responseText }
|
||||||
@@ -178,8 +179,23 @@ export default function LastButtons({ mitSend, mitBack, handleBack}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleAnleitung = () => {
|
const handleAnleitung = () => {
|
||||||
setModalType('info')
|
// Öffne die HTML-Anleitung in einem neuen Fenster/Tab
|
||||||
setModalMessage(`
|
const anleitungUrl = '/anleitung.html'
|
||||||
|
const windowFeatures = 'width=800,height=600,scrollbars=yes,resizable=yes,toolbar=no,menubar=no,location=no'
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Versuche ein Popup-Fenster zu öffnen
|
||||||
|
const anleitungWindow = window.open(anleitungUrl, 'anleitung', windowFeatures)
|
||||||
|
|
||||||
|
// Fallback: Wenn Popup blockiert wird, öffne in neuem Tab
|
||||||
|
if (!anleitungWindow) {
|
||||||
|
window.open(anleitungUrl, '_blank')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Letzter Fallback: Als Modal anzeigen
|
||||||
|
console.warn('Anleitung konnte nicht in neuem Fenster geöffnet werden:', error)
|
||||||
|
setModalType('info')
|
||||||
|
setModalMessage(`
|
||||||
📋 Anleitung:
|
📋 Anleitung:
|
||||||
|
|
||||||
1. **Fand statt?** - Wählen Sie "ja" oder "nein"
|
1. **Fand statt?** - Wählen Sie "ja" oder "nein"
|
||||||
@@ -195,8 +211,10 @@ export default function LastButtons({ mitSend, mitBack, handleBack}) {
|
|||||||
- Bei verschoben: neues Datum eingeben
|
- Bei verschoben: neues Datum eingeben
|
||||||
|
|
||||||
4. **Senden** - Speichert alle Daten im System
|
4. **Senden** - Speichert alle Daten im System
|
||||||
`)
|
`)
|
||||||
setShowModal(true)
|
setIsModalHtml(false)
|
||||||
|
setShowModal(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
@@ -224,6 +242,7 @@ export default function LastButtons({ mitSend, mitBack, handleBack}) {
|
|||||||
// Normales Modal schließen
|
// Normales Modal schließen
|
||||||
setShowModal(false)
|
setShowModal(false)
|
||||||
setModalMessage('')
|
setModalMessage('')
|
||||||
|
setIsModalHtml(false)
|
||||||
setIsSuccessModal(false)
|
setIsSuccessModal(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -262,6 +281,7 @@ export default function LastButtons({ mitSend, mitBack, handleBack}) {
|
|||||||
message={modalMessage}
|
message={modalMessage}
|
||||||
onClose={closeModal}
|
onClose={closeModal}
|
||||||
type={modalType}
|
type={modalType}
|
||||||
|
isHtml={isModalHtml}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React from 'react'
|
|||||||
// Import des CSS direkt hier
|
// Import des CSS direkt hier
|
||||||
import './Modal.css'
|
import './Modal.css'
|
||||||
|
|
||||||
export default function Modal({ isOpen = true, onClose, title, children, message, type = 'info' }) {
|
export default function Modal({ isOpen = true, onClose, title, children, message, type = 'info', isHtml = false }) {
|
||||||
if (!isOpen) return null
|
if (!isOpen) return null
|
||||||
|
|
||||||
const handleOverlayClick = (e) => {
|
const handleOverlayClick = (e) => {
|
||||||
@@ -37,7 +37,23 @@ export default function Modal({ isOpen = true, onClose, title, children, message
|
|||||||
}
|
}
|
||||||
|
|
||||||
const displayTitle = title || getDefaultTitle()
|
const displayTitle = title || getDefaultTitle()
|
||||||
const displayContent = message ? <p style={{whiteSpace: 'pre-line'}}>{message}</p> : children
|
|
||||||
|
// Content-Behandlung: HTML oder normaler Text
|
||||||
|
const getDisplayContent = () => {
|
||||||
|
if (children) return children
|
||||||
|
|
||||||
|
if (message) {
|
||||||
|
if (isHtml) {
|
||||||
|
// HTML-Inhalt sicher anzeigen
|
||||||
|
return <div dangerouslySetInnerHTML={{ __html: message }} />
|
||||||
|
} else {
|
||||||
|
// Normaler Text mit Zeilenumbrüchen
|
||||||
|
return <p style={{whiteSpace: 'pre-line'}}>{message}</p>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="modal-overlay" onClick={handleOverlayClick} onKeyDown={handleKeyDown} tabIndex={0}>
|
<div className="modal-overlay" onClick={handleOverlayClick} onKeyDown={handleKeyDown} tabIndex={0}>
|
||||||
@@ -47,7 +63,7 @@ export default function Modal({ isOpen = true, onClose, title, children, message
|
|||||||
<button className="modal-close" onClick={onClose}>×</button>
|
<button className="modal-close" onClick={onClose}>×</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="modal-body">
|
<div className="modal-body">
|
||||||
{displayContent}
|
{getDisplayContent()}
|
||||||
</div>
|
</div>
|
||||||
<div className="modal-footer">
|
<div className="modal-footer">
|
||||||
<button className="modal-button" onClick={onClose} autoFocus>OK</button>
|
<button className="modal-button" onClick={onClose} autoFocus>OK</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user