Fast Alles drin außer echtes Abspeicher und Backtickern
This commit is contained in:
14
.env.example
Normal file
14
.env.example
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# Kopiere diese Datei zu .env und passe die Werte an
|
||||||
|
|
||||||
|
# Backend API Configuration
|
||||||
|
#VITE_API_URL=/api/intern/sofue/php/sofueDB.php
|
||||||
|
|
||||||
|
# Für Production könntest du auch direkte URLs verwenden:
|
||||||
|
VITE_API_URL=https://sternwarte-welzheim.de/intern/sofue/php/sofueDB.php
|
||||||
|
|
||||||
|
# HTTP Basic Authentication für geschütztes Backend
|
||||||
|
VITE_API_USERNAME=dein_username
|
||||||
|
VITE_API_PASSWORD=dein_passwort
|
||||||
|
|
||||||
|
# Debug-Modus (optional)
|
||||||
|
# VITE_DEBUG=true
|
||||||
5
.env.production
Normal file
5
.env.production
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Production Environment Variables
|
||||||
|
VITE_API_URL=https://dein-produktions-server.com/intern/sofue/php/sofueDB.php
|
||||||
|
|
||||||
|
# Optional: Debug-Modus für Production meist ausgeschaltet
|
||||||
|
# VITE_DEBUG=false
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -12,6 +12,9 @@ dist
|
|||||||
dist-ssr
|
dist-ssr
|
||||||
*.local
|
*.local
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
.env
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.vscode/*
|
.vscode/*
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
|
|||||||
123
DEPLOYMENT.md
Normal file
123
DEPLOYMENT.md
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
# Production Deployment Guide
|
||||||
|
|
||||||
|
## Schritt 1: Environment Variable setzen
|
||||||
|
|
||||||
|
1. Erstelle `.env.production` Datei:
|
||||||
|
```env
|
||||||
|
VITE_API_URL=https://dein-produktions-server.com/intern/sofue/php/sofueDB.php
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Oder setze die Environment Variable direkt beim Build:
|
||||||
|
```bash
|
||||||
|
VITE_API_URL=https://dein-server.com/api npm run build:prod
|
||||||
|
```
|
||||||
|
|
||||||
|
## Schritt 2: Production Build erstellen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Mit .env.production Datei
|
||||||
|
npm run build:prod
|
||||||
|
|
||||||
|
# Oder mit direkter Environment Variable
|
||||||
|
VITE_API_URL=https://dein-server.com/api npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Schritt 3: Build-Output deployen
|
||||||
|
|
||||||
|
Die generierten Dateien im `dist/` Ordner auf deinen Webserver kopieren:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Lokaler Build
|
||||||
|
npm run build:prod
|
||||||
|
|
||||||
|
# Upload auf Server (Beispiel mit rsync)
|
||||||
|
rsync -avz dist/ user@dein-server.com:/var/www/html/beoanswer/
|
||||||
|
|
||||||
|
# Oder mit scp
|
||||||
|
scp -r dist/* user@dein-server.com:/var/www/html/beoanswer/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Schritt 4: Webserver Konfiguration
|
||||||
|
|
||||||
|
### Apache (.htaccess)
|
||||||
|
```apache
|
||||||
|
RewriteEngine On
|
||||||
|
RewriteBase /beoanswer/
|
||||||
|
|
||||||
|
# Handle Angular and other front-end routes
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
RewriteRule . /beoanswer/index.html [L]
|
||||||
|
|
||||||
|
# CORS Headers (falls nötig)
|
||||||
|
Header set Access-Control-Allow-Origin "*"
|
||||||
|
Header set Access-Control-Allow-Methods "GET, POST, OPTIONS"
|
||||||
|
Header set Access-Control-Allow-Headers "Content-Type"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Nginx
|
||||||
|
```nginx
|
||||||
|
location /beoanswer/ {
|
||||||
|
try_files $uri $uri/ /beoanswer/index.html;
|
||||||
|
|
||||||
|
# CORS Headers (falls nötig)
|
||||||
|
add_header Access-Control-Allow-Origin *;
|
||||||
|
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
|
||||||
|
add_header Access-Control-Allow-Headers 'Content-Type';
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Schritt 5: PHP Backend CORS
|
||||||
|
|
||||||
|
Falls nötig, füge in `sofueDB.php` hinzu:
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
// CORS Headers für Production und Development
|
||||||
|
if (isset($_SERVER['HTTP_ORIGIN'])) {
|
||||||
|
$allowed_origins = [
|
||||||
|
'https://deine-frontend-domain.com', // Production
|
||||||
|
'http://localhost:5173', // Vite Development
|
||||||
|
'http://localhost:3000', // Alternative Port
|
||||||
|
'http://127.0.0.1:5173' // Localhost als IP
|
||||||
|
];
|
||||||
|
|
||||||
|
if (in_array($_SERVER['HTTP_ORIGIN'], $allowed_origins)) {
|
||||||
|
header("Access-Control-Allow-Origin: " . $_SERVER['HTTP_ORIGIN']);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback für direkte Server-zu-Server Anfragen
|
||||||
|
header("Access-Control-Allow-Origin: *");
|
||||||
|
}
|
||||||
|
|
||||||
|
header("Access-Control-Allow-Methods: POST, GET, OPTIONS");
|
||||||
|
header("Access-Control-Allow-Headers: Content-Type, Authorization");
|
||||||
|
header("Access-Control-Allow-Credentials: true");
|
||||||
|
|
||||||
|
// Handle preflight requests
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
|
||||||
|
http_response_code(200);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rest des PHP Codes...
|
||||||
|
?>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Schritt 6: Testen
|
||||||
|
|
||||||
|
1. Lokaler Test des Production Builds:
|
||||||
|
```bash
|
||||||
|
npm run preview:prod
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Live-Test mit echter URL:
|
||||||
|
```
|
||||||
|
https://dein-server.com/beoanswer/?id=123
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
- **CORS Fehler**: Backend CORS Headers prüfen
|
||||||
|
- **404 Fehler**: Webserver Routing konfigurieren
|
||||||
|
- **API Fehler**: Environment Variable und Backend-URL prüfen
|
||||||
|
- **Asset Loading**: Base URL in Vite Config setzen falls nötig
|
||||||
@@ -6,8 +6,10 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"lint": "eslint .",
|
"build:prod": "vite build --mode production",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview",
|
||||||
|
"preview:prod": "vite preview --mode production",
|
||||||
|
"lint": "eslint ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
|
|||||||
398
sofueDB.php
Executable file
398
sofueDB.php
Executable file
@@ -0,0 +1,398 @@
|
|||||||
|
<?php
|
||||||
|
# Hier werden die Anfragen vom Javascript verarbeitet und die
|
||||||
|
# Datenbank bedient
|
||||||
|
|
||||||
|
$db = null;
|
||||||
|
|
||||||
|
//include '../../dbaseconf.php';
|
||||||
|
include '../../../config_stern.php';
|
||||||
|
include '../../../phpmailer/dosendmail.php';
|
||||||
|
|
||||||
|
$table = 'SoFue2';
|
||||||
|
|
||||||
|
|
||||||
|
function getFromDbase($db, $query, $single) {
|
||||||
|
$result = mysqli_query($db,$query) or die (mysqli_error($db));
|
||||||
|
$erg = array();
|
||||||
|
if(mysqli_num_rows($result)) {
|
||||||
|
while($row = mysqli_fetch_assoc($result)) {
|
||||||
|
$erg[] = $row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if($single == true) {
|
||||||
|
return ($erg[0]);
|
||||||
|
} else {
|
||||||
|
return($erg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cudDbase($db, $query) {
|
||||||
|
return(mysqli_query($db,$query) or die (mysqli_error($db)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ein Record holen mit der ID $id holen nd kompleet übermitteln
|
||||||
|
function getOneRecord($db,$id) {
|
||||||
|
global $table;
|
||||||
|
|
||||||
|
$query = "select * from $table where id = $id";
|
||||||
|
return(getFromDbase($db,$query,true));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ein Record holen mit dem Wunschtermin als Auswahl
|
||||||
|
function getOneRecordTermin($db,$termin) {
|
||||||
|
global $table;
|
||||||
|
|
||||||
|
$query = "select * from $table where DATE(wtermin) = '$termin' and status = 2";
|
||||||
|
return(getFromDbase($db,$query,true));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alle Records mit übergebener WHERE-clause übergeben, sortiert in aufsteigender
|
||||||
|
// Zeitfolge (Führungstermine als Zeitfolge)
|
||||||
|
// Es werden maximal $cout Records übergeben
|
||||||
|
function getRecords($db, $st, $termin, $anz, $pagnbr) {
|
||||||
|
global $table;
|
||||||
|
|
||||||
|
$response = new stdClass();
|
||||||
|
$ergs = array();
|
||||||
|
|
||||||
|
$lastdate = new DateTime();
|
||||||
|
$lastdate = $lastdate->sub(new DateInterval('P9M'));
|
||||||
|
$lastdate = $lastdate->format('Y-m-d');
|
||||||
|
if($st == 4) {
|
||||||
|
$where ="where stattgefunden = 1 and deleted = 0 ";
|
||||||
|
} else {
|
||||||
|
$where = "where status = '$st' and deleted = 0";
|
||||||
|
if ($termin == 'neu') {
|
||||||
|
$where = $where . " and wtermin >= now()";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Anzahl der Records holen
|
||||||
|
$query = "select count(*) as count from $table $where ";
|
||||||
|
$row = getFromDbase($db,$query,true);
|
||||||
|
$count = $row['count'];
|
||||||
|
|
||||||
|
// Anzahl der Seiten ausrechnen
|
||||||
|
$totalpages = ceil($count/$anz);
|
||||||
|
// Falls die angeforderte Seit > als die ANzahl der Seiten ist, die letzte Seite übergeben
|
||||||
|
if($pagnbr > $totalpages) $pagnbr = $totalpages;
|
||||||
|
// Start-Record berechnen
|
||||||
|
$start = $anz * ($pagnbr-1);
|
||||||
|
if($start <0) {
|
||||||
|
$start = 0;
|
||||||
|
}
|
||||||
|
$where = $where . " and DATE(wtermin) >= '$lastdate'";
|
||||||
|
$query = "select * from $table $where order by wtermin desc limit $start,$anz";
|
||||||
|
$rows = getFromDbase($db, $query, false);
|
||||||
|
|
||||||
|
$response->page = $pagnbr;
|
||||||
|
$response->total = $totalpages; // Es wird immer 1 Page übergeben
|
||||||
|
$cnt = 0;
|
||||||
|
foreach($rows as $row) {
|
||||||
|
$response->rows[$cnt]['id'] = $row['id'];
|
||||||
|
$response->rows[$cnt]['cell'] = $row;
|
||||||
|
$cnt++;
|
||||||
|
}
|
||||||
|
$response->records = $count;
|
||||||
|
return ($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
# string substr ( string $string , int $start [, int $length ] )
|
||||||
|
# Beo-Daten holen
|
||||||
|
function getBeos($db, $what, $cond) {
|
||||||
|
$retur = array();
|
||||||
|
if($cond == "") {
|
||||||
|
$query = "select $what from beos order by $what";
|
||||||
|
} else {
|
||||||
|
$a = strpos($cond,'empty');
|
||||||
|
if ( $a !== false) {
|
||||||
|
$b = substr($cond,0,$a);
|
||||||
|
$query = "select $what from beos where $b '' order by $what";
|
||||||
|
} else {
|
||||||
|
$query = "select $what from beos where $cond order by $what";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# echo $query;
|
||||||
|
$rows = getFromDbase($db, $query, false);
|
||||||
|
foreach($rows as $row) {
|
||||||
|
$retur[] = $row[$what];
|
||||||
|
}
|
||||||
|
return ($retur);
|
||||||
|
}
|
||||||
|
|
||||||
|
# Statistikdaten für das laufende (oder ein altes) Jahr holen und übergeben
|
||||||
|
# Ausgaben: JSON:
|
||||||
|
# { year: 2018,
|
||||||
|
# data:[
|
||||||
|
# { month: 1, angefragt: 10, zugesagt: 7, abgesagt: 3, stattgefunden: 6 },
|
||||||
|
# { month: 2, angefragt: 8, zugesagt: 6, abgesagt: 2, stattgefunden: 5 },
|
||||||
|
# { month: 3, angefragt: 23, zugesagt: 20, abgesagt: 3, stattgefunden: 15 },
|
||||||
|
# ...
|
||||||
|
# { month: 12, angefragt: 34, zugesagt: 22, abgesagt: 12, stattgefunden: 10 },
|
||||||
|
function getStatistik($db, $year) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Daten eines BEO holen, mit Name als Suchkriterium
|
||||||
|
function getOneBEO($db, $name) {
|
||||||
|
$query = "select * from beos where name = '$name'";
|
||||||
|
return getFromDbase($db, $query, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateEntry($db, $post) {
|
||||||
|
global $table;
|
||||||
|
$oldinhalt = getOneRecord($db, $post['id']);
|
||||||
|
$data = "mitarbeiter='" . $post['mitarbeiter'] .
|
||||||
|
"', status='" . $post['status'] .
|
||||||
|
"', bemerkung='" . $post['bemerkung'] .
|
||||||
|
"', wtermin='" . $post['wtermin'] .
|
||||||
|
"', atermin='" . $post['atermin'] .
|
||||||
|
"', allwett=''" .
|
||||||
|
", erledigt_datum='" . $post['erledigt_datum'] . "'";
|
||||||
|
$id = $post['id'];
|
||||||
|
$query = "update $table set $data where id='$id'";
|
||||||
|
$ret = cudDbase($db, $query);
|
||||||
|
$newinhalt = getOneRecord($db, $post['id']);
|
||||||
|
$ma = $post['mitarbeiter'];
|
||||||
|
$oldTermin = $oldinhalt['wtermin'];
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateAfter($db,$post) {
|
||||||
|
global $table;
|
||||||
|
$oldinhalt = getOneRecord($db, $post['id']);
|
||||||
|
$data = "stattgefunden='" . $post['stattgefunden'] .
|
||||||
|
"', anzahl_echt='" . $post['besucher'] .
|
||||||
|
"', remarks='" . $post['remark'] .
|
||||||
|
"', bezahlt='" . $post['bezahlt'] . "'";
|
||||||
|
// if (!empty($post['wtermin'])) {
|
||||||
|
// $data .= ", wtermin='" . $post['wtermin'] . "'";
|
||||||
|
// $ma = $oldInhalt['mitarbeiter'];
|
||||||
|
// sendMailTo($ma, $oldinhalt, $post['wtermin'], "Wunsch");
|
||||||
|
// }
|
||||||
|
if (!empty($post['status'])) {
|
||||||
|
$data .= ", status='" . $post['status'] . "'";
|
||||||
|
}
|
||||||
|
$id = $post['id'];
|
||||||
|
$query = "update $table set $data where id='$id'";
|
||||||
|
$ret = cudDbase($db, $query);
|
||||||
|
return($ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteEntry($db, $id) {
|
||||||
|
global $table;
|
||||||
|
$query = "update $table set deleted=true where id='$id'";
|
||||||
|
return cudDbase($db, $query);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDBdata() {
|
||||||
|
global $host, $dbase, $user, $pass;
|
||||||
|
|
||||||
|
$erg = "HOST: >" . $host . "< Dbase: >" . $dbase . "< user/pass: >" . $user . "/" . $pass . "<";
|
||||||
|
return $erg;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function findBeoVorname($who) {
|
||||||
|
global $db;
|
||||||
|
$names = explode(",",$who);
|
||||||
|
$erg = getbeos($db,'vorname',"name='".$names[0]."'");
|
||||||
|
return ($erg[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findBeoEmail($who) {
|
||||||
|
global $db;
|
||||||
|
$names = explode(",",$who);
|
||||||
|
$erg = getbeos($db,'email_1',"name='".$names[0]."'");
|
||||||
|
return ($erg[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function wterminstr($t) {
|
||||||
|
$tage = array(
|
||||||
|
"So",
|
||||||
|
"Mo",
|
||||||
|
"Di",
|
||||||
|
"Mi",
|
||||||
|
"Do",
|
||||||
|
"Fr",
|
||||||
|
"Sa"
|
||||||
|
);
|
||||||
|
$dati = strtotime($t);
|
||||||
|
$dt = $tage[date("w",$dati)] . ", " . date('d.m.Y H:i',$dati);
|
||||||
|
return $dt;
|
||||||
|
}
|
||||||
|
function sendMail2Beo($ma, $termin) {
|
||||||
|
$dt = wterminstr($termin);
|
||||||
|
$body = "Hallo " . findBeoVorname($ma) .",
|
||||||
|
|
||||||
|
vielen Dank für die Bereitschaft, die Sonderführung am {$dt} zu übernehmen.
|
||||||
|
Bitte den Termin nicht vergessen und bitte ggf. auch das Teammitglied, das die
|
||||||
|
Führung mitmacht, informieren.
|
||||||
|
|
||||||
|
Der Termin wurde in den Sternwartenkalender eingetragen.
|
||||||
|
|
||||||
|
Die Kontaktdaten sind auf der Sonderführungsseite ( https://sternwarte-welzheim.de/intern/sofue/sofue.php ) zu finden.
|
||||||
|
|
||||||
|
Viele Grüße
|
||||||
|
Reinhard
|
||||||
|
|
||||||
|
Diese Meldung wurde automatisch erzeugt. Es kann nicht geantwortet werden.";
|
||||||
|
|
||||||
|
$betreff = "Vereinbarte Sonderführung am " .$dt;
|
||||||
|
$absender = "noreply@sternwarte-welzheim.de";
|
||||||
|
sendmail($betreff, $absender, $body, [], ['rexfue@gmail.com'], [findBeoEmail($ma)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendMailZusage($to, $mitarbeiter, $termin) {
|
||||||
|
$dt = wterminstr($termin);
|
||||||
|
$betreff = "ZUSAGE - Sternwartenführung am {$dt} Uhr";
|
||||||
|
$absender = "anmeldung@sternwarte-welzheim.de";
|
||||||
|
$ge1 = ($mitarbeiter['gender'] == 'm') ? "unser ehrenamtlicher Mitarbeiter, Herr" : "unsere ehrenamtliche Mitarbeiterin, Frau";
|
||||||
|
$ge2 = ($mitarbeiter['gender'] == 'm') ? "ihn" : "sie";
|
||||||
|
$ge3 = ($mitarbeiter['gender'] == 'm') ? "Herrn" : "Frau";
|
||||||
|
$body = "
|
||||||
|
Guten Tag,
|
||||||
|
|
||||||
|
für Ihren Wunschtermin, {$dt} Uhr, hat sich {$ge1} {$mitarbeiter['vorname']} {$mitarbeiter['name']} bereit erklärt,
|
||||||
|
die Sonderführung zu übernehmen. Sie erreichen {$ge2} über die e-mail-Adresse: {$mitarbeiter['email_1']}
|
||||||
|
|
||||||
|
Um nähere Besuchsmodalitäten zu klären, bitten wir Sie, mit {$ge3} {$mitarbeiter['name']} Kontakt aufzunehmen.
|
||||||
|
|
||||||
|
Wir bitten Sie, die Spende in Höhe von €50.00 auf unten aufgeführtes Konto zu überweisen oder in bar zur Führung mitzubringen.
|
||||||
|
|
||||||
|
Gesellschaft zur Förderung des Planetariums Stuttgart und der Sternwarte Welzheim e.V.
|
||||||
|
BANKVERBINDUNG: Deutsche Bank AG Stuttgart
|
||||||
|
IBAN DE18 6007 0070 0122 0383 00
|
||||||
|
BIC: DEUTDESSXXX
|
||||||
|
|
||||||
|
|
||||||
|
Mit sternfreundlichen Grüßen
|
||||||
|
Reinhard X. Fürst
|
||||||
|
Sternwarte Welzheim
|
||||||
|
";
|
||||||
|
sendmail($betreff, $absender, $body, [$mitarbeiter['email_1']], ['rexfue@gmail.com'], [$to]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendMail2Liste($to, $erg) {
|
||||||
|
$betreff = "Anfrage Sonderführung am {$erg['wtermin']}";
|
||||||
|
$absender = "sonderfuehrung@sternwarte-welzheim.de";
|
||||||
|
$body = "
|
||||||
|
Liebe BEOs,
|
||||||
|
|
||||||
|
wer kann folgende Sonderführung übernehmen?
|
||||||
|
|
||||||
|
Viele Grüße
|
||||||
|
Reinhard
|
||||||
|
|
||||||
|
---------------------------------------------------------------------------------------------------";
|
||||||
|
|
||||||
|
$body = $body . "
|
||||||
|
Name, Vorname: " . $erg['name'] . " " . $erg['vorname'] . "
|
||||||
|
Verein / Organisation : " . $erg['verein'] . "
|
||||||
|
Wunsch - Termin: " . $erg['wtermin'] . "
|
||||||
|
Teilnehmerzahl ca.: " . $erg['anzahl'] . "
|
||||||
|
|
||||||
|
Weitere Fragen oder Mitteilungen: " . $erg['mitteilung'] . "
|
||||||
|
Spendenbescheinigung: " . $erg['spende'] . "
|
||||||
|
---------------------------------------------------------------------------------------------------";
|
||||||
|
sendmail($betreff, $absender, $body, [], [], [$to]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sonderführung in den Kalender eintragen
|
||||||
|
function put2kalender($db, $data, $termin, $ma) {
|
||||||
|
$start = substr($termin,0,16);
|
||||||
|
$title = "WK, SF " . $data['name'] . ", " . $ma;
|
||||||
|
$sql = "INSERT into kalender (start, end, title, description) VALUES ('" . $start . "', DATE_ADD('" . $start . "',INTERVAL 2 HOUR), '" . $title . "', '')";
|
||||||
|
$erg = cudDbase($db, $sql);
|
||||||
|
$mist = 23;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Hier gehts dann los:
|
||||||
|
// Alle Paramater aus dem Ajax-Call auslesen
|
||||||
|
// Mögliche Aufrufe:
|
||||||
|
/*
|
||||||
|
* cmd=GET param=ID id=5 bringt das eine Record mit ID=5
|
||||||
|
* cmd=GET param=STATUS staus=offen bringt ALLE records mit stautus='offen' in zeitlich absteigender Reihenfoleg
|
||||||
|
*/
|
||||||
|
|
||||||
|
$erg = "";
|
||||||
|
$cmd = $_POST["cmd"];
|
||||||
|
|
||||||
|
/*
|
||||||
|
$x = "# ";
|
||||||
|
foreach ($_POST as $key => $value) {
|
||||||
|
$x = $x . $key . " " . $value . "\n";
|
||||||
|
}
|
||||||
|
$x = $x . '# ';
|
||||||
|
echo $x;
|
||||||
|
*/
|
||||||
|
|
||||||
|
switch ($cmd) {
|
||||||
|
case 'GET_ONE':
|
||||||
|
$erg = getOneRecord($db, $_POST["id"]);
|
||||||
|
break;
|
||||||
|
case 'GET_ONETERMIN':
|
||||||
|
$erg = getOneRecordTermin($db, $_POST["termin"]);
|
||||||
|
break;
|
||||||
|
case 'GET_MANY':
|
||||||
|
$st = $_POST['status'];
|
||||||
|
$anzahl = $_POST['rows'];
|
||||||
|
$page = $_POST['page'];
|
||||||
|
$termin = $_POST['termin'];
|
||||||
|
$erg = getRecords($db,$st, $termin ,$anzahl,$page);
|
||||||
|
break;
|
||||||
|
case 'GET_BEOS':
|
||||||
|
$erg = getBeos($db,$_POST['what'],$_POST['cond']);
|
||||||
|
// echo '#' . $erg ;
|
||||||
|
break;
|
||||||
|
case 'GET_ONEBEO':
|
||||||
|
$erg = getOneBEO($db,$_POST['name']);
|
||||||
|
// echo '#' . $erg ;
|
||||||
|
break;
|
||||||
|
# case GET_FUEH:
|
||||||
|
# $erg = getFuehrung_findet_statt($db);
|
||||||
|
# break;
|
||||||
|
case 'GET_STAT':
|
||||||
|
# $erg = getStatistik($db,$_POST['year']);
|
||||||
|
break;
|
||||||
|
case 'UPDATE':
|
||||||
|
$erg = updateEntry($db, $_POST);
|
||||||
|
break;
|
||||||
|
case 'UPDATEAFTER':
|
||||||
|
$erg = updateAfter($db, $_POST);
|
||||||
|
break;
|
||||||
|
case 'DELETE':
|
||||||
|
$erg = deleteEntry($db, $_POST['id']);
|
||||||
|
break;
|
||||||
|
case 'SENDMAILZUSAGE':
|
||||||
|
// function sendMailZusage($to, $mitarbeiter, $termin) {
|
||||||
|
$erg = getOneRecord($db, $_POST["id"]);
|
||||||
|
$names = explode(",",$_POST['mitarbeiter']);
|
||||||
|
$ma = getOneBEO($db, $names[0]);
|
||||||
|
sendMailZusage($erg['email'], $ma, $_POST['termin']);
|
||||||
|
break;
|
||||||
|
case 'SENDMAIL2BEO':
|
||||||
|
// function sendMail2Beo($ma, $termin) {
|
||||||
|
sendMail2beo($_POST['ma'], $_POST['termin']);
|
||||||
|
break;
|
||||||
|
case 'SENDMAIL2LISTE':
|
||||||
|
$erg = getOneRecord($db, $_POST["id"]);
|
||||||
|
sendMail2Liste($_POST['to'],$erg);
|
||||||
|
break;
|
||||||
|
case 'PUT2KALENDER':
|
||||||
|
$erg = getOneRecord($db, $_POST["id"]);
|
||||||
|
put2kalender($db, $erg, $_POST['termin'], $_POST['mitarbeiter']);
|
||||||
|
break;
|
||||||
|
case 'SHOWDB':
|
||||||
|
$erg = getDBdata();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
header("Content-type: text/json;charset=utf-8");
|
||||||
|
|
||||||
|
echo json_encode($erg);
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
140
src/App.jsx
140
src/App.jsx
@@ -1,4 +1,4 @@
|
|||||||
import { useState } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { FormProvider, useFormData } from './FormContext'
|
import { FormProvider, useFormData } from './FormContext'
|
||||||
import './App.css'
|
import './App.css'
|
||||||
import FandStattVer from './components/FandStattVer.jsx'
|
import FandStattVer from './components/FandStattVer.jsx'
|
||||||
@@ -11,11 +11,14 @@ import Verschoben from './components/Verschoben.jsx'
|
|||||||
|
|
||||||
|
|
||||||
function AppContent() {
|
function AppContent() {
|
||||||
const datum = "2025-10-23"
|
// States für Backend-Daten
|
||||||
const name = "Meiehofer"
|
const [datum, setDatum] = useState("")
|
||||||
|
const [name, setName] = useState("")
|
||||||
|
const [loading, setLoading] = useState(true)
|
||||||
|
const [error, setError] = useState(null)
|
||||||
|
|
||||||
const version = "1.0.0"
|
const version = "1.0.0"
|
||||||
const vdate = "2025-11-23"
|
const vdate = "2025-11-23"
|
||||||
//const isBar = true
|
|
||||||
|
|
||||||
// States
|
// States
|
||||||
const [schritt, setSchritt] = useState(0)
|
const [schritt, setSchritt] = useState(0)
|
||||||
@@ -24,6 +27,82 @@ function AppContent() {
|
|||||||
// Hole formData aus dem Context
|
// Hole formData aus dem Context
|
||||||
const { formData } = useFormData()
|
const { formData } = useFormData()
|
||||||
|
|
||||||
|
// URL-Parameter und Backend-Aufruf
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchData = async () => {
|
||||||
|
// API URL aus Environment Variable laden
|
||||||
|
const APIURL = import.meta.env.VITE_API_URL
|
||||||
|
|
||||||
|
if (!APIURL) {
|
||||||
|
throw new Error('API URL nicht konfiguriert. Bitte VITE_API_URL in .env Datei setzen.')
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// URL-Parameter auslesen
|
||||||
|
const urlParams = new URLSearchParams(window.location.search)
|
||||||
|
const id = urlParams.get('id')
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
throw new Error('Keine ID in der URL gefunden. Bitte rufen Sie die Seite mit ?id=123 auf.')
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Loading data for ID:', id)
|
||||||
|
|
||||||
|
// Backend-Aufruf mit Proxy
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('cmd', 'GET_ONE')
|
||||||
|
formData.append('id', id)
|
||||||
|
|
||||||
|
// HTTP Basic Authentication Header erstellen
|
||||||
|
const username = import.meta.env.VITE_API_USERNAME
|
||||||
|
const password = import.meta.env.VITE_API_PASSWORD
|
||||||
|
const headers = {}
|
||||||
|
|
||||||
|
if (username && password) {
|
||||||
|
const credentials = btoa(`${username}:${password}`)
|
||||||
|
headers['Authorization'] = `Basic ${credentials}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(APIURL, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: headers,
|
||||||
|
body: formData
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Daten konnten nicht geladen werden. Server-Fehler: ${response.status}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json()
|
||||||
|
|
||||||
|
console.log('Received data:', data) // Debug-Ausgabe
|
||||||
|
|
||||||
|
// Anpassung an die Datenbankfelder der SoFue2 Tabelle
|
||||||
|
if (!data.wtermin || !data.name) {
|
||||||
|
throw new Error('Unvollständige Daten vom Server erhalten.')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Daten aus Backend setzen
|
||||||
|
// wtermin ist vermutlich ein datetime, also nur das Datum extrahieren
|
||||||
|
const terminDate = new Date(data.wtermin)
|
||||||
|
const formatiertesDatum = terminDate.toLocaleDateString('de-DE')
|
||||||
|
|
||||||
|
setDatum(formatiertesDatum)
|
||||||
|
setName(data.name + (data.vorname ? ' ' + data.vorname : ''))
|
||||||
|
|
||||||
|
console.log('Data loaded:', data)
|
||||||
|
setLoading(false)
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error loading data:', err)
|
||||||
|
setError(err.message)
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchData()
|
||||||
|
}, []) // Leere Dependency-Array = nur beim ersten Laden ausführen
|
||||||
|
|
||||||
|
|
||||||
// Callbacks:
|
// Callbacks:
|
||||||
const handleFandStattVerNext = (auswahl) => {
|
const handleFandStattVerNext = (auswahl) => {
|
||||||
@@ -92,13 +171,13 @@ function AppContent() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Schritt 2: Ende wenn abgesagt bzw. neues Datum bei verschoben
|
// Schritt 2: Ende wenn abgesagt bzw. neues Datum bei verschoben
|
||||||
if (schritt >= 2 && formData.stattgefunden === 'verschoben') {
|
if (schritt >= 2 && formData.abgesagt === 'verschoben') {
|
||||||
components.push(<Verschoben key='verschoben' onNext={handleNext} isCompleted={schritt > 2} />
|
components.push(<Verschoben key='verschoben' onNext={handleNext} isCompleted={schritt > 2} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Schritt 4 (bei verschoben) oder Schritt 3 (bei absage): unterste Buttons
|
// Schritt 4 (bei verschoben) oder Schritt 3 (bei absage): unterste Buttons
|
||||||
const endeNeinSchritt = (formData.stattgefunden === 'verschoben') ? 3 : 2
|
const endeNeinSchritt = (formData.abgesagt === 'verschoben') ? 3 : 2
|
||||||
if (schritt >= endeNeinSchritt) {
|
if (schritt >= endeNeinSchritt) {
|
||||||
components.push(<LastButtons key='lastbutt' />
|
components.push(<LastButtons key='lastbutt' />
|
||||||
)
|
)
|
||||||
@@ -108,6 +187,55 @@ function AppContent() {
|
|||||||
return components
|
return components
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Loading und Error States
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="wrapper">
|
||||||
|
<div>
|
||||||
|
<h2 className="topline">Lade Daten...</h2>
|
||||||
|
<p>Bitte warten Sie, während die Führungsdaten geladen werden.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<div className="wrapper">
|
||||||
|
<div>
|
||||||
|
<h2 className="nachbearbeitung" style={{backgroundColor: '#ff6b6b', color: 'white'}}>
|
||||||
|
Fehler beim Laden der Daten
|
||||||
|
</h2>
|
||||||
|
<div style={{padding: '20px', textAlign: 'left'}}>
|
||||||
|
<h3>❌ Die Anwendung kann nicht gestartet werden</h3>
|
||||||
|
<p><strong>Grund:</strong> {error}</p>
|
||||||
|
<hr />
|
||||||
|
<h4>Mögliche Lösungen:</h4>
|
||||||
|
<ul>
|
||||||
|
<li>Überprüfen Sie die URL - sie sollte eine ID enthalten (z.B. ?id=123)</li>
|
||||||
|
<li>Stellen Sie sicher, dass das Backend erreichbar ist</li>
|
||||||
|
<li>Kontaktieren Sie den Administrator</li>
|
||||||
|
</ul>
|
||||||
|
<button
|
||||||
|
onClick={() => window.location.reload()}
|
||||||
|
style={{
|
||||||
|
marginTop: '20px',
|
||||||
|
padding: '10px 20px',
|
||||||
|
backgroundColor: '#007bff',
|
||||||
|
color: 'white',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '5px',
|
||||||
|
cursor: 'pointer'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Seite neu laden
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="wrapper">
|
<div className="wrapper">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -6,40 +6,42 @@ import { createContext, useContext, useState } from 'react'
|
|||||||
const FormContext = createContext()
|
const FormContext = createContext()
|
||||||
|
|
||||||
export function FormProvider({ children }) {
|
export function FormProvider({ children }) {
|
||||||
// console.log('🚀 FormProvider initialisiert')
|
console.log('🚀 FormProvider initialisiert')
|
||||||
|
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
stattgefunden: '',
|
stattgefunden: '',
|
||||||
besucherAnzahl: '',
|
besucher: '', // war: besucherAnzahl
|
||||||
spendenArt: '',
|
spendenArt: '',
|
||||||
barspende: '',
|
betrag: '', // war: barspende
|
||||||
bemerkungen: '',
|
bemerkungen: '',
|
||||||
neuertermin: '',
|
neuesDatum: '', // war: neuertermin
|
||||||
|
abgesagt: '', // für abgesagt/verschoben
|
||||||
// Weitere Felder können hier hinzugefügt werden
|
// Weitere Felder können hier hinzugefügt werden
|
||||||
})
|
})
|
||||||
|
|
||||||
const updateFormData = (field, value) => {
|
const updateFormData = (field, value) => {
|
||||||
//console.log('📝 FormContext UPDATE:', field, '=', value)
|
console.log('📝 FormContext UPDATE:', field, '=', value)
|
||||||
setFormData(prev => {
|
setFormData(prev => {
|
||||||
const newData = {
|
const newData = {
|
||||||
...prev,
|
...prev,
|
||||||
[field]: value
|
[field]: value
|
||||||
}
|
}
|
||||||
//console.log('📊 FormContext NEU:', newData)
|
console.log('📊 FormContext NEU:', newData)
|
||||||
return newData
|
return newData
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const resetFormData = () => {
|
const resetFormData = () => {
|
||||||
//console.log('🔄 FormContext RESET')
|
console.log('🔄 FormContext RESET')
|
||||||
|
|
||||||
setFormData({
|
setFormData({
|
||||||
stattgefunden: '',
|
stattgefunden: '',
|
||||||
besucherAnzahl: '',
|
besucher: '',
|
||||||
spendenArt: '',
|
spendenArt: '',
|
||||||
barspende: '',
|
betrag: '',
|
||||||
bemerkungen: '',
|
bemerkungen: '',
|
||||||
neuertermin: ''
|
neuesDatum: '',
|
||||||
|
abgesagt: ''
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,13 +12,32 @@ export default function Bemerkungen({ onNext, isCompleted }) {
|
|||||||
onNext()
|
onNext()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleKeyDown = (e) => {
|
||||||
|
// Ctrl+Enter oder Cmd+Enter zum Speichern
|
||||||
|
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
|
||||||
|
handleOK()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section id="bemerkungen">
|
<section id="bemerkungen">
|
||||||
<h3>Bemerkungen (optional):</h3>
|
<h3>Bemerkungen (optional):</h3>
|
||||||
<div className="bemerkdiv">
|
<div className="bemerkdiv">
|
||||||
<textarea className="beminfeld" />
|
<textarea
|
||||||
<button className="okbutton" onClick={handleOK}>OK</button>
|
className="beminfeld"
|
||||||
|
value={wert}
|
||||||
|
onChange={(e) => setWert(e.target.value)}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
placeholder="Hier können Sie optionale Bemerkungen zur Führung eingeben..."
|
||||||
|
disabled={isCompleted}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className="okbutton"
|
||||||
|
onClick={handleOK}
|
||||||
|
disabled={isCompleted}
|
||||||
|
>
|
||||||
|
OK
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ export default function BesucherBar({ title, euro, onNext, isCompleted }) {
|
|||||||
|
|
||||||
const { formData, updateFormData } = useFormData()
|
const { formData, updateFormData } = useFormData()
|
||||||
|
|
||||||
// Bestimme das Feld basierend auf dem Titel
|
// Bestimme Feldname basierend auf dem title
|
||||||
const fieldName = title.includes('Barspende') ? 'barspende' : 'besucherAnzahl'
|
const fieldName = title.includes('Barspende') ? 'betrag' : 'besucher'
|
||||||
|
|
||||||
const [wert, setWert] = useState(formData[fieldName] || '')
|
const [wert, setWert] = useState(formData[fieldName] || '')
|
||||||
const [showModal, setShowModal] = useState(false)
|
const [showModal, setShowModal] = useState(false)
|
||||||
|
|||||||
65
src/components/ConfirmModal.jsx
Normal file
65
src/components/ConfirmModal.jsx
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import './Modal.css'
|
||||||
|
|
||||||
|
export default function ConfirmModal({ isOpen = true, onClose, onConfirm, title, message, type = 'warning' }) {
|
||||||
|
if (!isOpen) return null
|
||||||
|
|
||||||
|
const handleOverlayClick = (e) => {
|
||||||
|
if (e.target === e.currentTarget) {
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleKeyDown = (e) => {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
onConfirm()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDefaultTitle = () => {
|
||||||
|
switch(type) {
|
||||||
|
case 'danger': return 'Achtung'
|
||||||
|
case 'warning': return 'Bestätigung erforderlich'
|
||||||
|
case 'info': return 'Information'
|
||||||
|
default: return 'Bestätigung'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getModalClass = () => {
|
||||||
|
return `modal-content modal-${type}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const displayTitle = title || getDefaultTitle()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="modal-overlay" onClick={handleOverlayClick} onKeyDown={handleKeyDown} tabIndex={0}>
|
||||||
|
<div className={getModalClass()}>
|
||||||
|
<div className="modal-header">
|
||||||
|
<h3 className="modal-title">{displayTitle}</h3>
|
||||||
|
<button className="modal-close" onClick={onClose}>×</button>
|
||||||
|
</div>
|
||||||
|
<div className="modal-body">
|
||||||
|
<p style={{whiteSpace: 'pre-line'}}>{message}</p>
|
||||||
|
</div>
|
||||||
|
<div className="modal-footer confirm-buttons">
|
||||||
|
<button
|
||||||
|
className="modal-button modal-button-secondary"
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
Nein
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="modal-button modal-button-danger"
|
||||||
|
onClick={onConfirm}
|
||||||
|
autoFocus
|
||||||
|
>
|
||||||
|
OK Abbrechen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -4,12 +4,15 @@ import Modal from './Modal'
|
|||||||
|
|
||||||
export default function FandStattVer({left, right, title, onNext, radioName = "fst"}) {
|
export default function FandStattVer({left, right, title, onNext, radioName = "fst"}) {
|
||||||
const { formData, updateFormData } = useFormData()
|
const { formData, updateFormData } = useFormData()
|
||||||
const [auswahl, setAuswahl] = useState(formData.stattgefunden || '')
|
|
||||||
|
// Bestimme das Feld basierend auf radioName
|
||||||
|
const fieldName = radioName === 'abgesagt' ? 'abgesagt' : 'stattgefunden'
|
||||||
|
const [auswahl, setAuswahl] = useState(formData[fieldName] || '')
|
||||||
|
|
||||||
const handleRadioChange = (e) => {
|
const handleRadioChange = (e) => {
|
||||||
const value = e.target.value
|
const value = e.target.value
|
||||||
setAuswahl(value)
|
setAuswahl(value)
|
||||||
updateFormData('stattgefunden', value)
|
updateFormData(fieldName, value)
|
||||||
onNext(value)
|
onNext(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,27 +1,222 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
import { useFormData } from '../FormContext'
|
import { useFormData } from '../FormContext'
|
||||||
|
import Modal from './Modal'
|
||||||
|
import ConfirmModal from './ConfirmModal'
|
||||||
|
|
||||||
export default function LastButtons() {
|
export default function LastButtons() {
|
||||||
|
|
||||||
const { formData } = useFormData()
|
const { formData } = useFormData()
|
||||||
|
const [isSending, setIsSending] = useState(false)
|
||||||
|
const [showModal, setShowModal] = useState(false)
|
||||||
|
const [modalMessage, setModalMessage] = useState('')
|
||||||
|
const [modalType, setModalType] = useState('error') // 'error' oder 'success'
|
||||||
|
const [showConfirmModal, setShowConfirmModal] = useState(false)
|
||||||
|
|
||||||
const handleSenden = () => {
|
const handleSenden = async () => {
|
||||||
console.log("Alle Formulardaten: ", formData)
|
console.log("Alle Formulardaten: ", formData)
|
||||||
|
|
||||||
|
setIsSending(true)
|
||||||
|
|
||||||
|
try {
|
||||||
|
// API URL und Auth-Daten aus Environment
|
||||||
|
const APIURL = import.meta.env.VITE_API_URL
|
||||||
|
const username = import.meta.env.VITE_API_USERNAME
|
||||||
|
const password = import.meta.env.VITE_API_PASSWORD
|
||||||
|
|
||||||
|
if (!APIURL) {
|
||||||
|
throw new Error('API URL nicht konfiguriert.')
|
||||||
|
}
|
||||||
|
|
||||||
|
// URL-Parameter für ID auslesen
|
||||||
|
const urlParams = new URLSearchParams(window.location.search)
|
||||||
|
const id = urlParams.get('id')
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
throw new Error('Keine ID in der URL gefunden.')
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormData für PHP Backend erstellen
|
||||||
|
const backendData = new FormData()
|
||||||
|
backendData.append('cmd', 'UPDATEAFTER')
|
||||||
|
backendData.append('id', id)
|
||||||
|
|
||||||
|
// Formulardaten zu Backend-Feldern mappen
|
||||||
|
// Basis-Status
|
||||||
|
if (formData.stattgefunden === 'ja') {
|
||||||
|
backendData.append('stattgefunden', '1')
|
||||||
|
|
||||||
|
// Spenden-Informationen
|
||||||
|
if (formData.spendenArt) {
|
||||||
|
switch(formData.spendenArt) {
|
||||||
|
case 'bar':
|
||||||
|
backendData.append('bezahlt', `Kasse ${formData.betrag}€)`)
|
||||||
|
break
|
||||||
|
case 'ueber':
|
||||||
|
backendData.append('bezahlt', 'Überweisung')
|
||||||
|
break
|
||||||
|
case 'kasse':
|
||||||
|
backendData.append('bezahlt', 'Spendenkässle')
|
||||||
|
break
|
||||||
|
case 'keine':
|
||||||
|
backendData.append('bezahlt', 'keine')
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (formData.stattgefunden === 'nein') {
|
||||||
|
backendData.append('stattgefunden', '0')
|
||||||
|
backendData.append('bezahlt', 'keine')
|
||||||
|
|
||||||
|
// Grund für Ausfall
|
||||||
|
if (formData.abgesagt === 'abgesagt') {
|
||||||
|
backendData.append('status', 3)
|
||||||
|
} else if (formData.abgesagt === 'verschoben') {
|
||||||
|
backendData.append('wtermin', formData.neuesDatum || '1900-01-01 00:00:00')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bemerkungen
|
||||||
|
backendData.append('remark', formData.bemerkungen || '')
|
||||||
|
// Besucher
|
||||||
|
backendData.append('besucher', formData.besucher || '0')
|
||||||
|
|
||||||
|
// // Bearbeitungsdatum setzen
|
||||||
|
// const now = new Date().toISOString().slice(0, 19).replace('T', ' ')
|
||||||
|
// backendData.append('bearbeitet_am', now)
|
||||||
|
|
||||||
|
// Debug: FormData kann nicht direkt geloggt werden, deshalb iterieren
|
||||||
|
console.log("=== FORM DATA DEBUG ===")
|
||||||
|
console.log("Original formData aus Context:", formData)
|
||||||
|
console.log("URL ID:", id)
|
||||||
|
console.log("Backend FormData Inhalt:")
|
||||||
|
for (let [key, value] of backendData.entries()) {
|
||||||
|
console.log(` ${key}: ${value}`)
|
||||||
|
}
|
||||||
|
console.log("========================")
|
||||||
|
/*
|
||||||
|
// HTTP Basic Authentication Header
|
||||||
|
const headers = {}
|
||||||
|
if (username && password) {
|
||||||
|
const credentials = btoa(`${username}:${password}`)
|
||||||
|
headers['Authorization'] = `Basic ${credentials}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backend-Aufruf
|
||||||
|
const response = await fetch(APIURL, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: headers,
|
||||||
|
body: backendData
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Server-Fehler: ${response.status}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json()
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
setModalType('success')
|
||||||
|
setModalMessage('✅ Daten erfolgreich gespeichert!')
|
||||||
|
setShowModal(true)
|
||||||
|
|
||||||
|
// Nach erfolgreicher Speicherung könnte man zur Übersicht zurück
|
||||||
|
setTimeout(() => {
|
||||||
|
// Optional: Weiterleitung oder Formular zurücksetzen
|
||||||
|
console.log('Erfolgreich gespeichert:', result)
|
||||||
|
}, 2000)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
throw new Error(result.message || 'Unbekannter Fehler beim Speichern')
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Fehler beim Speichern:', error)
|
||||||
|
setModalType('error')
|
||||||
|
setModalMessage(`❌ Fehler beim Speichern: ${error.message}`)
|
||||||
|
setShowModal(true)
|
||||||
|
} finally {
|
||||||
|
setIsSending(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleAbbruch = () => {
|
const handleAbbruch = () => {
|
||||||
console.log("Abbruch")
|
setShowConfirmModal(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmAbbruch = () => {
|
||||||
|
setShowConfirmModal(false)
|
||||||
|
// Zurück zur vorherigen Seite oder Startseite
|
||||||
|
window.history.back()
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancelAbbruch = () => {
|
||||||
|
setShowConfirmModal(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleAnleitung = () => {
|
const handleAnleitung = () => {
|
||||||
console.log("Zeige Anleitung")
|
setModalType('info')
|
||||||
|
setModalMessage(`
|
||||||
|
📋 Anleitung:
|
||||||
|
|
||||||
|
1. **Fand statt?** - Wählen Sie "ja" oder "nein"
|
||||||
|
|
||||||
|
2. **Bei "ja":**
|
||||||
|
- Anzahl Besucher eingeben
|
||||||
|
- Spenden-Art auswählen
|
||||||
|
- Bei Barspende: Betrag eingeben
|
||||||
|
- Optional: Bemerkungen
|
||||||
|
|
||||||
|
3. **Bei "nein":**
|
||||||
|
- "abgesagt" oder "verschoben" wählen
|
||||||
|
- Bei verschoben: neues Datum eingeben
|
||||||
|
|
||||||
|
4. **Senden** - Speichert alle Daten im System
|
||||||
|
`)
|
||||||
|
setShowModal(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeModal = () => {
|
||||||
|
setShowModal(false)
|
||||||
|
setModalMessage('')
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="lastbuttons">
|
<>
|
||||||
<button className="btnabbruch" onClick = {handleAbbruch}>Abbruch</button>
|
<div className="lastbuttons">
|
||||||
<button className="btnanleit" onClick = {handleAnleitung}>Anleitung</button>
|
<button className="btnabbruch" onClick={handleAbbruch}>
|
||||||
<button className="btnsend" onClick = {handleSenden}>Senden</button>
|
Abbruch
|
||||||
</div>
|
</button>
|
||||||
|
<button className="btnanleit" onClick={handleAnleitung}>
|
||||||
|
Anleitung
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="btnsend"
|
||||||
|
onClick={handleSenden}
|
||||||
|
disabled={isSending}
|
||||||
|
>
|
||||||
|
{isSending ? 'Speichert...' : 'Senden'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{showModal && (
|
||||||
|
<Modal
|
||||||
|
message={modalMessage}
|
||||||
|
onClose={closeModal}
|
||||||
|
type={modalType}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{showConfirmModal && (
|
||||||
|
<ConfirmModal
|
||||||
|
message={`Möchten Sie wirklich abbrechen?
|
||||||
|
|
||||||
|
Alle eingegebenen Daten gehen verloren und werden nicht gespeichert.`}
|
||||||
|
onClose={cancelAbbruch}
|
||||||
|
onConfirm={confirmAbbruch}
|
||||||
|
type="warning"
|
||||||
|
title="Vorgang abbrechen?"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -25,6 +25,61 @@
|
|||||||
animation: modalSlideIn 0.2s ease-out;
|
animation: modalSlideIn 0.2s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Modal Type Variants */
|
||||||
|
.modal-success {
|
||||||
|
border-left: 5px solid #28a745;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-error {
|
||||||
|
border-left: 5px solid #dc3545;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-warning {
|
||||||
|
border-left: 5px solid #ffc107;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-info {
|
||||||
|
border-left: 5px solid #17a2b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Type-specific header colors */
|
||||||
|
.modal-success .modal-header {
|
||||||
|
background: #d4edda;
|
||||||
|
border-bottom-color: #c3e6cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-error .modal-header {
|
||||||
|
background: #f8d7da;
|
||||||
|
border-bottom-color: #f5c6cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-warning .modal-header {
|
||||||
|
background: #fff3cd;
|
||||||
|
border-bottom-color: #ffeaa7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-info .modal-header {
|
||||||
|
background: #d1ecf1;
|
||||||
|
border-bottom-color: #bee5eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Type-specific title colors */
|
||||||
|
.modal-success .modal-title {
|
||||||
|
color: #155724;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-error .modal-title {
|
||||||
|
color: #721c24;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-warning .modal-title {
|
||||||
|
color: #856404;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-info .modal-title {
|
||||||
|
color: #0c5460;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes modalSlideIn {
|
@keyframes modalSlideIn {
|
||||||
from {
|
from {
|
||||||
transform: scale(0.9) translateY(-10px);
|
transform: scale(0.9) translateY(-10px);
|
||||||
@@ -88,6 +143,20 @@
|
|||||||
border-top: 1px solid #e9ecef;
|
border-top: 1px solid #e9ecef;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Confirm Modal - Multiple Buttons */
|
||||||
|
.modal-footer.confirm-buttons {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer.confirm-buttons .modal-button:first-child {
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer.confirm-buttons .modal-button {
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-button {
|
.modal-button {
|
||||||
@@ -112,6 +181,30 @@
|
|||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-button-secondary {
|
||||||
|
background: #6c757d;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-button-secondary:hover {
|
||||||
|
background: #545862;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-button-danger {
|
||||||
|
background: #dc3545;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-button-danger:hover {
|
||||||
|
background: #c82333;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Type-specific button for single button modals */
|
||||||
|
.modal-content:not(.modal-confirm) .modal-footer {
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Responsive Design */
|
/* Responsive Design */
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
.modal-content {
|
.modal-content {
|
||||||
|
|||||||
@@ -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, onClose, title, children }) {
|
export default function Modal({ isOpen = true, onClose, title, children, message, type = 'info' }) {
|
||||||
if (!isOpen) return null
|
if (!isOpen) return null
|
||||||
|
|
||||||
const handleOverlayClick = (e) => {
|
const handleOverlayClick = (e) => {
|
||||||
@@ -20,15 +20,34 @@ export default function Modal({ isOpen, onClose, title, children }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Automatischer Titel basierend auf type
|
||||||
|
const getDefaultTitle = () => {
|
||||||
|
switch(type) {
|
||||||
|
case 'success': return 'Erfolg'
|
||||||
|
case 'error': return 'Fehler'
|
||||||
|
case 'warning': return 'Warnung'
|
||||||
|
case 'info': return 'Information'
|
||||||
|
default: return 'Meldung'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CSS-Klasse basierend auf type
|
||||||
|
const getModalClass = () => {
|
||||||
|
return `modal-content modal-${type}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const displayTitle = title || getDefaultTitle()
|
||||||
|
const displayContent = message ? <p style={{whiteSpace: 'pre-line'}}>{message}</p> : children
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="modal-overlay" onClick={handleOverlayClick} onKeyDown={handleKeyDown} tabIndex={0}>
|
<div className="modal-overlay" onClick={handleOverlayClick} onKeyDown={handleKeyDown} tabIndex={0}>
|
||||||
<div className="modal-content">
|
<div className={getModalClass()}>
|
||||||
<div className="modal-header">
|
<div className="modal-header">
|
||||||
<h3 className="modal-title">{title}</h3>
|
<h3 className="modal-title">{displayTitle}</h3>
|
||||||
<button className="modal-close" onClick={onClose}>×</button>
|
<button className="modal-close" onClick={onClose}>×</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="modal-body">
|
<div className="modal-body">
|
||||||
{children}
|
{displayContent}
|
||||||
</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>
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ export default function Verschoben({onNext, isCompleted}) {
|
|||||||
const { formData, updateFormData } = useFormData()
|
const { formData, updateFormData } = useFormData()
|
||||||
|
|
||||||
// State für das selektierte Datum
|
// State für das selektierte Datum
|
||||||
const [selectedDate, setSelectedDate] = useState(formData.neuertermin || '')
|
const [selectedDate, setSelectedDate] = useState(formData.neuesDatum || '')
|
||||||
const [showModal, setShowModal] = useState(false)
|
const [showModal, setShowModal] = useState(false)
|
||||||
|
|
||||||
const handleOK = () => {
|
const handleOK = () => {
|
||||||
if (selectedDate) {
|
if (selectedDate) {
|
||||||
updateFormData('neuertermin', selectedDate)
|
updateFormData('neuesDatum', selectedDate)
|
||||||
onNext()
|
onNext()
|
||||||
} else {
|
} else {
|
||||||
setShowModal(true)
|
setShowModal(true)
|
||||||
@@ -40,7 +40,12 @@ export default function Verschoben({onNext, isCompleted}) {
|
|||||||
type="datetime-local"
|
type="datetime-local"
|
||||||
id="datetime"
|
id="datetime"
|
||||||
value={selectedDate}
|
value={selectedDate}
|
||||||
onChange={(e) => setSelectedDate(e.target.value)}
|
onChange={(e) => {
|
||||||
|
let selD = e.target.value
|
||||||
|
selD = selD + ':00'
|
||||||
|
selD = selD.replace('T',' ')
|
||||||
|
setSelectedDate(selD)
|
||||||
|
}}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
min={now.toISOString().slice(0,16)}
|
min={now.toISOString().slice(0,16)}
|
||||||
disabled={isCompleted}
|
disabled={isCompleted}
|
||||||
|
|||||||
@@ -2,6 +2,30 @@ import { defineConfig } from 'vite'
|
|||||||
import react from '@vitejs/plugin-react'
|
import react from '@vitejs/plugin-react'
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig(({ mode }) => {
|
||||||
plugins: [react()],
|
return {
|
||||||
|
plugins: [react()],
|
||||||
|
server: {
|
||||||
|
// Proxy nur für Development
|
||||||
|
proxy: mode === 'development' ? {
|
||||||
|
'/api': {
|
||||||
|
target: 'http://localhost:8080',
|
||||||
|
changeOrigin: true,
|
||||||
|
rewrite: (path) => path.replace(/^\/api/, '')
|
||||||
|
}
|
||||||
|
} : {}
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
// Production Build Optimierungen
|
||||||
|
sourcemap: false,
|
||||||
|
minify: 'terser',
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
manualChunks: {
|
||||||
|
vendor: ['react', 'react-dom']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user