Compare commits
26 Commits
45b76cf1b2
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| f6e9f0fef6 | |||
| b77cb63758 | |||
| eb46fd0d6d | |||
| 681cf70341 | |||
| 01a51a8ed1 | |||
| 0e0263abbb | |||
| a8967c707a | |||
| 77dc29fa9c | |||
| 2e8e010ceb | |||
| 61ace47270 | |||
| 66953de7f4 | |||
| 4297a98511 | |||
| ba026bda31 | |||
| 8fe9086205 | |||
| 45eeb5a9c9 | |||
| ca5bc00187 | |||
| 07f0711ea0 | |||
| e009a16972 | |||
| b78831266d | |||
| 3e8b1f9691 | |||
| ad0f7b2912 | |||
| e3bb5d36b9 | |||
| 9071f96f0d | |||
| 75a6988248 | |||
| e42d9c9b32 | |||
| 53decbc96b |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,3 +11,4 @@ download
|
||||
*.log
|
||||
webseiten
|
||||
|
||||
sternwarte/beoanswer/.env.production
|
||||
|
||||
26
docs/Server-Ideen.md
Normal file
26
docs/Server-Ideen.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Neuer Server f. Sternwarte
|
||||
|
||||
## Mailserver
|
||||
* Start0
|
||||
* IONOS -> billigster ist 1.50/m incl Domain
|
||||
|
||||
## Domain
|
||||
Am einfachsten die **sternwarte-welzheim.de** zu dem Mailserver umziehen.
|
||||
Die ist da **kostenlos**! Und damit sehen die Mails dann wieder gut aus (z.B. info@sternwarte-welzheim.de).
|
||||
|
||||
## Server
|
||||
* keiner Linux-Server bei z.B.:
|
||||
* Starto
|
||||
* IONOS
|
||||
|
||||
|
||||
## Mailingliste
|
||||
Auf dem Linux-Server mit Mailman selber aufsetzen
|
||||
|
||||
### Version
|
||||
|
||||
Version | Datum | Beschreibung
|
||||
--------|-------|-------------
|
||||
1.0.0 | 2025-11-02 | Beginn der Ideen
|
||||
|
||||
|
||||
BIN
docs/Server-Ideen.pdf
Normal file
BIN
docs/Server-Ideen.pdf
Normal file
Binary file not shown.
30
sternwarte/.gitignore
vendored
Normal file
30
sternwarte/.gitignore
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Environment variables
|
||||
sternwarte/beoanswer/.env*
|
||||
|
||||
# CORS-Proxy Konfiguration (enthält Credentials)
|
||||
cors-config.php
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
8
sternwarte/.vscode/launch.json
vendored
8
sternwarte/.vscode/launch.json
vendored
@@ -4,14 +4,13 @@
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
|
||||
{
|
||||
"name": "Listen for Xdebug",
|
||||
"type": "php",
|
||||
"request": "launch",
|
||||
"port": 9003,
|
||||
"pathMappings": {
|
||||
"/var/www/html": "${workspaceRoot}"
|
||||
"/var/www/html": "${workspaceFolder}"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -81,11 +80,6 @@
|
||||
},
|
||||
"profile": true,
|
||||
"openProfile": true
|
||||
},
|
||||
{
|
||||
"name": "Listen for Xdebug",
|
||||
"type": "php",
|
||||
"request": "launch"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
Um Zugriff auf das Netzwerk der Sternwarte zu erlangen, wurde dort auf der Fritzbox die Software **Wireguard** aktiviert. Diese erlaubt es, über einen sicheren Tunnel von dem Heim-PC (oder auch vom Tablet oder Smartphone) auf das Netzwerk zuzugreifen. Dazu muss auf dem lokalen Gerät (also PC etc). ebenfalls die Software **Wireguard** installiert und aktiviert werden.
|
||||
|
||||
###Installation von Wireguard
|
||||
### Installation von Wireguard
|
||||
|
||||
Unabh. vom Betriebssystem muss zuerst die Konfigurationsdatei (*Sternwarte.conf*) auf das Gerät geladen werden. Diese befindet sich auf dem Sternwartenserver im internen Bereich unter *Anleitungen*.
|
||||
|
||||
@@ -56,12 +56,12 @@ Dazu auf der Webseite <https://www.wireguard.com/install/> das Programm für das
|
||||
* den Schalter bei *Sternwarte* einschalten
|
||||
* Verbindung wird aufgebaut
|
||||
|
||||
###Zugriff auf Sternwarten-Netz
|
||||
Nun kann über die IP-Adresse oder den Namen im Sternwarten-Netz auf die diversen Geräte zugegriffen werden. z.B. kann zum Testen
|
||||
über **192.168.1.95** der Zugriff auf die Testseite des Wetterserver-Rechners erfolgen.
|
||||
### Zugriff auf Sternwarten-Netz
|
||||
Nun kann über die IP-Adresse oder den Namen im Sternwarten-Netz auf die diversen Geräte zugegriffen werden. z.B. kann zum Testen über **192.168.1.26** (oder **http://wetterserver**) der Zugriff auf die Testseite des Wetterserver-Rechners erfolgen. Hier darf **kein** http**s** verwendet werden.
|
||||
|
||||
###Zugriff auf das NAS-Laufwerk
|
||||
Die Web-Oberfläche des NAS kann über die Adresse **192.168.1.250** direkt im Browser aufgerufen werden. Der Username und das Passwort sind: 'Sternwarte', 'Welzheim92'. Natürlich geht die Verbindun nur dann, wenn Wireguard eingeschaltet ist !!
|
||||
### Zugriff auf das NAS-Laufwerk
|
||||
Die Web-Oberfläche des NAS kann über die Adresse **https://192.168.1.250** (oder über **https://Goldgrube**) direkt im Browser aufgerufen werden. **ACHTUNG**: Bitte unbedingt **https://** verwenden. Zwar mault dann der Browser, aber es geht leider nicht anders. Je nach Browser sieht die Warnmeldung unterschiedlich aus, aber prinzipiell muss man auf *Erweitert* oder so gehen, dann sagen dass man das Risiko akzeptiert und trotzdem die Webseite besuchen will. Da wir ja über VPN verbunden sind, ist das keinerlei Risiko. In der Regel merkt sich der Browser das und bein nächsten mal gibt es keine Warnung mehr.
|
||||
Der Username und das Passwort sind: 'Sternwarte', 'Welzheim92'. Natürlich geht die Verbindung nur dann, wenn Wireguard eingeschaltet ist !!
|
||||
|
||||
|
||||
Außerdem kann das Laufwerk direkt eingebunden werden:
|
||||
@@ -86,14 +86,15 @@ Ab sofort braucht nur Wireguard eingeschaltet sein (oder werden) und der Zugriff
|
||||
|
||||
Hier läuft dann der Zugriff direkt über *Goldgrube*.
|
||||
|
||||
###VPN (Wireguard) verlassen
|
||||
### VPN (Wireguard) verlassen
|
||||
**\*\*\* NICHT vergessen das VPN wieder ausschalten \*\*\***
|
||||
Denn sonst läuft **jeder** Internetverkehr über die Fritzbox der Sternwarte !
|
||||
|
||||
Je nach Betriebssystem die APP **Wireguard** wieder aufrufen und den entsprechenden Schalter wieder ausschalten (bzw. auf *Deaktivieren* klicken.
|
||||
|
||||
####Versionen
|
||||
#### Versionen
|
||||
Datum | Version | Author | Bemerkung
|
||||
------|---------|--------|--------
|
||||
2024-05-15 | 0.1 | rxf | erster Entwurf
|
||||
2024-06-17 | 1.0 | rxf | vollständige Verison
|
||||
2025-11-07 | 1.1.0 | rxf | https Notwendigkeit
|
||||
2024-06-17 | 1.0.0 | rxf | vollständige Verison
|
||||
2024-05-15 | 0.1.0 | rxf | erster Entwurf
|
||||
|
||||
BIN
sternwarte/Anleitungen/Anleitung für Wireguard (VPN).pdf
Normal file
BIN
sternwarte/Anleitungen/Anleitung für Wireguard (VPN).pdf
Normal file
Binary file not shown.
@@ -61,7 +61,11 @@ function getOneBEO($kurz, $what)
|
||||
{
|
||||
global $db;
|
||||
|
||||
if ($what == 'true') {
|
||||
$sql_stmt = "SELECT * FROM beos where name='$kurz'";
|
||||
} else {
|
||||
$sql_stmt = "SELECT $what FROM beos where kürzel='$kurz'";
|
||||
}
|
||||
$erg = array();
|
||||
$result = mysqli_query($db, $sql_stmt) or die(mysqli_error($db));
|
||||
$data = mysqli_fetch_assoc($result);
|
||||
@@ -418,6 +422,23 @@ function getOneRecordTermin($termin) {
|
||||
}
|
||||
|
||||
|
||||
// Ein Record von den Sonderführungen mit der ID $id holen und kompleet übermitteln
|
||||
function getOneSonderTeilnehmer($id) {
|
||||
global $db;
|
||||
$query = "select * from SoFue2 where id = $id";
|
||||
$result = mysqli_query($db, $query) or die(mysqli_error($db));
|
||||
while ($row = mysqli_fetch_assoc($result)) {
|
||||
foreach ($row as $key => $value) {
|
||||
$entry[$key] = $value;
|
||||
}
|
||||
$erg[] = $entry;
|
||||
}
|
||||
return $erg;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
$_POST = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
$erg = "";
|
||||
@@ -543,6 +564,10 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||
case 'GET_ONETERMIN':
|
||||
$erg = getOneRecordTermin($_POST["termin"]);
|
||||
break;
|
||||
case 'GET_ONESONDERTEILNEHMER':
|
||||
$erg = getOneSonderTeilnehmer($_POST["id"]);
|
||||
break;
|
||||
|
||||
default:
|
||||
$erg = ['error' => 'Unknown POST-Command', 'cmd' => $cmd, 'params' => $x];
|
||||
}
|
||||
|
||||
1440
sternwarte/DB4js_all.php
Normal file
1440
sternwarte/DB4js_all.php
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -1,669 +0,0 @@
|
||||
/*! SWFObject v2.0 <http://code.google.com/p/swfobject/>
|
||||
Copyright (c) 2007 Geoff Stearns, Michael Williams, and Bobby van der Sluis
|
||||
This software is released under the MIT License <http://www.opensource.org/licenses/mit-license.php>
|
||||
*/
|
||||
|
||||
var swfobject = function() {
|
||||
|
||||
var UNDEF = "undefined",
|
||||
OBJECT = "object",
|
||||
SHOCKWAVE_FLASH = "Shockwave Flash",
|
||||
SHOCKWAVE_FLASH_AX = "ShockwaveFlash.ShockwaveFlash",
|
||||
FLASH_MIME_TYPE = "application/x-shockwave-flash",
|
||||
EXPRESS_INSTALL_ID = "SWFObjectExprInst",
|
||||
|
||||
win = window,
|
||||
doc = document,
|
||||
nav = navigator,
|
||||
|
||||
domLoadFnArr = [],
|
||||
regObjArr = [],
|
||||
timer = null,
|
||||
storedAltContent = null,
|
||||
storedAltContentId = null,
|
||||
isDomLoaded = false,
|
||||
isExpressInstallActive = false;
|
||||
|
||||
/* Centralized function for browser feature detection
|
||||
- Proprietary feature detection (conditional compiling) is used to detect Internet Explorer's features
|
||||
- User agent string detection is only used when no alternative is possible
|
||||
- Is executed directly for optimal performance
|
||||
*/
|
||||
var ua = function() {
|
||||
var w3cdom = typeof doc.getElementById != UNDEF && typeof doc.getElementsByTagName != UNDEF && typeof doc.createElement != UNDEF && typeof doc.appendChild != UNDEF && typeof doc.replaceChild != UNDEF && typeof doc.removeChild != UNDEF && typeof doc.cloneNode != UNDEF,
|
||||
playerVersion = [0,0,0],
|
||||
d = null;
|
||||
if (typeof nav.plugins != UNDEF && typeof nav.plugins[SHOCKWAVE_FLASH] == OBJECT) {
|
||||
d = nav.plugins[SHOCKWAVE_FLASH].description;
|
||||
if (d) {
|
||||
d = d.replace(/^.*\s+(\S+\s+\S+$)/, "$1");
|
||||
playerVersion[0] = parseInt(d.replace(/^(.*)\..*$/, "$1"), 10);
|
||||
playerVersion[1] = parseInt(d.replace(/^.*\.(.*)\s.*$/, "$1"), 10);
|
||||
playerVersion[2] = /r/.test(d) ? parseInt(d.replace(/^.*r(.*)$/, "$1"), 10) : 0;
|
||||
}
|
||||
}
|
||||
else if (typeof win.ActiveXObject != UNDEF) {
|
||||
var a = null, fp6Crash = false;
|
||||
try {
|
||||
a = new ActiveXObject(SHOCKWAVE_FLASH_AX + ".7");
|
||||
}
|
||||
catch(e) {
|
||||
try {
|
||||
a = new ActiveXObject(SHOCKWAVE_FLASH_AX + ".6");
|
||||
playerVersion = [6,0,21];
|
||||
a.AllowScriptAccess = "always"; // Introduced in fp6.0.47
|
||||
}
|
||||
catch(e) {
|
||||
if (playerVersion[0] == 6) {
|
||||
fp6Crash = true;
|
||||
}
|
||||
}
|
||||
if (!fp6Crash) {
|
||||
try {
|
||||
a = new ActiveXObject(SHOCKWAVE_FLASH_AX);
|
||||
}
|
||||
catch(e) {}
|
||||
}
|
||||
}
|
||||
if (!fp6Crash && a) { // a will return null when ActiveX is disabled
|
||||
try {
|
||||
d = a.GetVariable("$version"); // Will crash fp6.0.21/23/29
|
||||
if (d) {
|
||||
d = d.split(" ")[1].split(",");
|
||||
playerVersion = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)];
|
||||
}
|
||||
}
|
||||
catch(e) {}
|
||||
}
|
||||
}
|
||||
var u = nav.userAgent.toLowerCase(),
|
||||
p = nav.platform.toLowerCase(),
|
||||
webkit = /webkit/.test(u) ? parseFloat(u.replace(/^.*webkit\/(\d+(\.\d+)?).*$/, "$1")) : false, // returns either the webkit version or false if not webkit
|
||||
ie = false,
|
||||
windows = p ? /win/.test(p) : /win/.test(u),
|
||||
mac = p ? /mac/.test(p) : /mac/.test(u);
|
||||
/*@cc_on
|
||||
ie = true;
|
||||
@if (@_win32)
|
||||
windows = true;
|
||||
@elif (@_mac)
|
||||
mac = true;
|
||||
@end
|
||||
@*/
|
||||
return { w3cdom:w3cdom, pv:playerVersion, webkit:webkit, ie:ie, win:windows, mac:mac };
|
||||
}();
|
||||
|
||||
/* Cross-browser onDomLoad
|
||||
- Based on Dean Edwards' solution: http://dean.edwards.name/weblog/2006/06/again/
|
||||
- Will fire an event as soon as the DOM of a page is loaded (supported by Gecko based browsers - like Firefox -, IE, Opera9+, Safari)
|
||||
*/
|
||||
var onDomLoad = function() {
|
||||
if (!ua.w3cdom) {
|
||||
return;
|
||||
}
|
||||
addDomLoadEvent(main);
|
||||
if (ua.ie && ua.win) {
|
||||
try { // Avoid a possible Operation Aborted error
|
||||
doc.write("<scr" + "ipt id=__ie_ondomload defer=true src=//:></scr" + "ipt>"); // String is split into pieces to avoid Norton AV to add code that can cause errors
|
||||
var s = getElementById("__ie_ondomload");
|
||||
if (s) {
|
||||
s.onreadystatechange = function() {
|
||||
if (this.readyState == "complete") {
|
||||
this.parentNode.removeChild(this);
|
||||
callDomLoadFunctions();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
catch(e) {}
|
||||
}
|
||||
if (ua.webkit && typeof doc.readyState != UNDEF) {
|
||||
timer = setInterval(function() { if (/loaded|complete/.test(doc.readyState)) { callDomLoadFunctions(); }}, 10);
|
||||
}
|
||||
if (typeof doc.addEventListener != UNDEF) {
|
||||
doc.addEventListener("DOMContentLoaded", callDomLoadFunctions, null);
|
||||
}
|
||||
addLoadEvent(callDomLoadFunctions);
|
||||
}();
|
||||
|
||||
function callDomLoadFunctions() {
|
||||
if (isDomLoaded) {
|
||||
return;
|
||||
}
|
||||
if (ua.ie && ua.win) { // Test if we can really add elements to the DOM; we don't want to fire it too early
|
||||
var s = createElement("span");
|
||||
try { // Avoid a possible Operation Aborted error
|
||||
var t = doc.getElementsByTagName("body")[0].appendChild(s);
|
||||
t.parentNode.removeChild(t);
|
||||
}
|
||||
catch (e) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
isDomLoaded = true;
|
||||
if (timer) {
|
||||
clearInterval(timer);
|
||||
timer = null;
|
||||
}
|
||||
var dl = domLoadFnArr.length;
|
||||
for (var i = 0; i < dl; i++) {
|
||||
domLoadFnArr[i]();
|
||||
}
|
||||
}
|
||||
|
||||
function addDomLoadEvent(fn) {
|
||||
if (isDomLoaded) {
|
||||
fn();
|
||||
}
|
||||
else {
|
||||
domLoadFnArr[domLoadFnArr.length] = fn; // Array.push() is only available in IE5.5+
|
||||
}
|
||||
}
|
||||
|
||||
/* Cross-browser onload
|
||||
- Based on James Edwards' solution: http://brothercake.com/site/resources/scripts/onload/
|
||||
- Will fire an event as soon as a web page including all of its assets are loaded
|
||||
*/
|
||||
function addLoadEvent(fn) {
|
||||
if (typeof win.addEventListener != UNDEF) {
|
||||
win.addEventListener("load", fn, false);
|
||||
}
|
||||
else if (typeof doc.addEventListener != UNDEF) {
|
||||
doc.addEventListener("load", fn, false);
|
||||
}
|
||||
else if (typeof win.attachEvent != UNDEF) {
|
||||
win.attachEvent("onload", fn);
|
||||
}
|
||||
else if (typeof win.onload == "function") {
|
||||
var fnOld = win.onload;
|
||||
win.onload = function() {
|
||||
fnOld();
|
||||
fn();
|
||||
};
|
||||
}
|
||||
else {
|
||||
win.onload = fn;
|
||||
}
|
||||
}
|
||||
|
||||
/* Main function
|
||||
- Will preferably execute onDomLoad, otherwise onload (as a fallback)
|
||||
*/
|
||||
function main() { // Static publishing only
|
||||
var rl = regObjArr.length;
|
||||
for (var i = 0; i < rl; i++) { // For each registered object element
|
||||
var id = regObjArr[i].id;
|
||||
if (ua.pv[0] > 0) {
|
||||
var obj = getElementById(id);
|
||||
if (obj) {
|
||||
regObjArr[i].width = obj.getAttribute("width") ? obj.getAttribute("width") : "0";
|
||||
regObjArr[i].height = obj.getAttribute("height") ? obj.getAttribute("height") : "0";
|
||||
if (hasPlayerVersion(regObjArr[i].swfVersion)) { // Flash plug-in version >= Flash content version: Houston, we have a match!
|
||||
if (ua.webkit && ua.webkit < 312) { // Older webkit engines ignore the object element's nested param elements
|
||||
fixParams(obj);
|
||||
}
|
||||
setVisibility(id, true);
|
||||
}
|
||||
else if (regObjArr[i].expressInstall && !isExpressInstallActive && hasPlayerVersion("6.0.65") && (ua.win || ua.mac)) { // Show the Adobe Express Install dialog if set by the web page author and if supported (fp6.0.65+ on Win/Mac OS only)
|
||||
showExpressInstall(regObjArr[i]);
|
||||
}
|
||||
else { // Flash plug-in and Flash content version mismatch: display alternative content instead of Flash content
|
||||
displayAltContent(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
else { // If no fp is installed, we let the object element do its job (show alternative content)
|
||||
setVisibility(id, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Fix nested param elements, which are ignored by older webkit engines
|
||||
- This includes Safari up to and including version 1.2.2 on Mac OS 10.3
|
||||
- Fall back to the proprietary embed element
|
||||
*/
|
||||
function fixParams(obj) {
|
||||
var nestedObj = obj.getElementsByTagName(OBJECT)[0];
|
||||
if (nestedObj) {
|
||||
var e = createElement("embed"), a = nestedObj.attributes;
|
||||
if (a) {
|
||||
var al = a.length;
|
||||
for (var i = 0; i < al; i++) {
|
||||
if (a[i].nodeName.toLowerCase() == "data") {
|
||||
e.setAttribute("src", a[i].nodeValue);
|
||||
}
|
||||
else {
|
||||
e.setAttribute(a[i].nodeName, a[i].nodeValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
var c = nestedObj.childNodes;
|
||||
if (c) {
|
||||
var cl = c.length;
|
||||
for (var j = 0; j < cl; j++) {
|
||||
if (c[j].nodeType == 1 && c[j].nodeName.toLowerCase() == "param") {
|
||||
e.setAttribute(c[j].getAttribute("name"), c[j].getAttribute("value"));
|
||||
}
|
||||
}
|
||||
}
|
||||
obj.parentNode.replaceChild(e, obj);
|
||||
}
|
||||
}
|
||||
|
||||
/* Fix hanging audio/video threads and force open sockets and NetConnections to disconnect
|
||||
- Occurs when unloading a web page in IE using fp8+ and innerHTML/outerHTML
|
||||
- Dynamic publishing only
|
||||
*/
|
||||
function fixObjectLeaks(id) {
|
||||
if (ua.ie && ua.win && hasPlayerVersion("8.0.0")) {
|
||||
win.attachEvent("onunload", function () {
|
||||
var obj = getElementById(id);
|
||||
if (obj) {
|
||||
for (var i in obj) {
|
||||
if (typeof obj[i] == "function") {
|
||||
obj[i] = function() {};
|
||||
}
|
||||
}
|
||||
obj.parentNode.removeChild(obj);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/* Show the Adobe Express Install dialog
|
||||
- Reference: http://www.adobe.com/cfusion/knowledgebase/index.cfm?id=6a253b75
|
||||
*/
|
||||
function showExpressInstall(regObj) {
|
||||
isExpressInstallActive = true;
|
||||
var obj = getElementById(regObj.id);
|
||||
if (obj) {
|
||||
if (regObj.altContentId) {
|
||||
var ac = getElementById(regObj.altContentId);
|
||||
if (ac) {
|
||||
storedAltContent = ac;
|
||||
storedAltContentId = regObj.altContentId;
|
||||
}
|
||||
}
|
||||
else {
|
||||
storedAltContent = abstractAltContent(obj);
|
||||
}
|
||||
if (!(/%$/.test(regObj.width)) && parseInt(regObj.width, 10) < 310) {
|
||||
regObj.width = "310";
|
||||
}
|
||||
if (!(/%$/.test(regObj.height)) && parseInt(regObj.height, 10) < 137) {
|
||||
regObj.height = "137";
|
||||
}
|
||||
doc.title = doc.title.slice(0, 47) + " - Flash Player Installation";
|
||||
var pt = ua.ie && ua.win ? "ActiveX" : "PlugIn",
|
||||
dt = doc.title,
|
||||
fv = "MMredirectURL=" + win.location + "&MMplayerType=" + pt + "&MMdoctitle=" + dt,
|
||||
replaceId = regObj.id;
|
||||
// For IE when a SWF is loading (AND: not available in cache) wait for the onload event to fire to remove the original object element
|
||||
// In IE you cannot properly cancel a loading SWF file without breaking browser load references, also obj.onreadystatechange doesn't work
|
||||
if (ua.ie && ua.win && obj.readyState != 4) {
|
||||
var newObj = createElement("div");
|
||||
replaceId += "SWFObjectNew";
|
||||
newObj.setAttribute("id", replaceId);
|
||||
obj.parentNode.insertBefore(newObj, obj); // Insert placeholder div that will be replaced by the object element that loads expressinstall.swf
|
||||
obj.style.display = "none";
|
||||
win.attachEvent("onload", function() { obj.parentNode.removeChild(obj); });
|
||||
}
|
||||
createSWF({ data:regObj.expressInstall, id:EXPRESS_INSTALL_ID, width:regObj.width, height:regObj.height }, { flashvars:fv }, replaceId);
|
||||
}
|
||||
}
|
||||
|
||||
/* Functions to abstract and display alternative content
|
||||
*/
|
||||
function displayAltContent(obj) {
|
||||
if (ua.ie && ua.win && obj.readyState != 4) {
|
||||
// For IE when a SWF is loading (AND: not available in cache) wait for the onload event to fire to remove the original object element
|
||||
// In IE you cannot properly cancel a loading SWF file without breaking browser load references, also obj.onreadystatechange doesn't work
|
||||
var el = createElement("div");
|
||||
obj.parentNode.insertBefore(el, obj); // Insert placeholder div that will be replaced by the alternative content
|
||||
el.parentNode.replaceChild(abstractAltContent(obj), el);
|
||||
obj.style.display = "none";
|
||||
win.attachEvent("onload", function() { obj.parentNode.removeChild(obj); });
|
||||
}
|
||||
else {
|
||||
obj.parentNode.replaceChild(abstractAltContent(obj), obj);
|
||||
}
|
||||
}
|
||||
|
||||
function abstractAltContent(obj) {
|
||||
var ac = createElement("div");
|
||||
if (ua.win && ua.ie) {
|
||||
ac.innerHTML = obj.innerHTML;
|
||||
}
|
||||
else {
|
||||
var nestedObj = obj.getElementsByTagName(OBJECT)[0];
|
||||
if (nestedObj) {
|
||||
var c = nestedObj.childNodes;
|
||||
if (c) {
|
||||
var cl = c.length;
|
||||
for (var i = 0; i < cl; i++) {
|
||||
if (!(c[i].nodeType == 1 && c[i].nodeName.toLowerCase() == "param") && !(c[i].nodeType == 8)) {
|
||||
ac.appendChild(c[i].cloneNode(true));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ac;
|
||||
}
|
||||
|
||||
/* Cross-browser dynamic SWF creation
|
||||
*/
|
||||
function createSWF(attObj, parObj, id) {
|
||||
var r, el = getElementById(id);
|
||||
if (typeof attObj.id == UNDEF) { // if no 'id' is defined for the object element, it will inherit the 'id' from the alternative content
|
||||
attObj.id = id;
|
||||
}
|
||||
if (ua.ie && ua.win) { // IE, the object element and W3C DOM methods do not combine: fall back to outerHTML
|
||||
var att = "";
|
||||
for (var i in attObj) {
|
||||
if (attObj[i] != Object.prototype[i]) { // Filter out prototype additions from other potential libraries, like Object.prototype.toJSONString = function() {}
|
||||
if (i == "data") {
|
||||
parObj.movie = attObj[i];
|
||||
}
|
||||
else if (i.toLowerCase() == "styleclass") { // 'class' is an ECMA4 reserved keyword
|
||||
att += ' class="' + attObj[i] + '"';
|
||||
}
|
||||
else if (i != "classid") {
|
||||
att += ' ' + i + '="' + attObj[i] + '"';
|
||||
}
|
||||
}
|
||||
}
|
||||
var par = "";
|
||||
for (var j in parObj) {
|
||||
if (parObj[j] != Object.prototype[j]) { // Filter out prototype additions from other potential libraries
|
||||
par += '<param name="' + j + '" value="' + parObj[j] + '" />';
|
||||
}
|
||||
}
|
||||
el.outerHTML = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"' + att + '>' + par + '</object>';
|
||||
fixObjectLeaks(attObj.id); // This bug affects dynamic publishing only
|
||||
r = getElementById(attObj.id);
|
||||
}
|
||||
else if (ua.webkit && ua.webkit < 312) { // Older webkit engines ignore the object element's nested param elements: fall back to the proprietary embed element
|
||||
var e = createElement("embed");
|
||||
e.setAttribute("type", FLASH_MIME_TYPE);
|
||||
for (var k in attObj) {
|
||||
if (attObj[k] != Object.prototype[k]) { // Filter out prototype additions from other potential libraries
|
||||
if (k == "data") {
|
||||
e.setAttribute("src", attObj[k]);
|
||||
}
|
||||
else if (k.toLowerCase() == "styleclass") { // 'class' is an ECMA4 reserved keyword
|
||||
e.setAttribute("class", attObj[k]);
|
||||
}
|
||||
else if (k != "classid") { // Filter out IE specific attribute
|
||||
e.setAttribute(k, attObj[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (var l in parObj) {
|
||||
if (parObj[l] != Object.prototype[l]) { // Filter out prototype additions from other potential libraries
|
||||
if (l != "movie") { // Filter out IE specific param element
|
||||
e.setAttribute(l, parObj[l]);
|
||||
}
|
||||
}
|
||||
}
|
||||
el.parentNode.replaceChild(e, el);
|
||||
r = e;
|
||||
}
|
||||
else { // Well-behaving browsers
|
||||
var o = createElement(OBJECT);
|
||||
o.setAttribute("type", FLASH_MIME_TYPE);
|
||||
for (var m in attObj) {
|
||||
if (attObj[m] != Object.prototype[m]) { // Filter out prototype additions from other potential libraries
|
||||
if (m.toLowerCase() == "styleclass") { // 'class' is an ECMA4 reserved keyword
|
||||
o.setAttribute("class", attObj[m]);
|
||||
}
|
||||
else if (m != "classid") { // Filter out IE specific attribute
|
||||
o.setAttribute(m, attObj[m]);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (var n in parObj) {
|
||||
if (parObj[n] != Object.prototype[n] && n != "movie") { // Filter out prototype additions from other potential libraries and IE specific param element
|
||||
createObjParam(o, n, parObj[n]);
|
||||
}
|
||||
}
|
||||
el.parentNode.replaceChild(o, el);
|
||||
r = o;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
function createObjParam(el, pName, pValue) {
|
||||
var p = createElement("param");
|
||||
p.setAttribute("name", pName);
|
||||
p.setAttribute("value", pValue);
|
||||
el.appendChild(p);
|
||||
}
|
||||
|
||||
function getElementById(id) {
|
||||
return doc.getElementById(id);
|
||||
}
|
||||
|
||||
function createElement(el) {
|
||||
return doc.createElement(el);
|
||||
}
|
||||
|
||||
function hasPlayerVersion(rv) {
|
||||
var pv = ua.pv, v = rv.split(".");
|
||||
v[0] = parseInt(v[0], 10);
|
||||
v[1] = parseInt(v[1], 10);
|
||||
v[2] = parseInt(v[2], 10);
|
||||
return (pv[0] > v[0] || (pv[0] == v[0] && pv[1] > v[1]) || (pv[0] == v[0] && pv[1] == v[1] && pv[2] >= v[2])) ? true : false;
|
||||
}
|
||||
|
||||
/* Cross-browser dynamic CSS creation
|
||||
- Based on Bobby van der Sluis' solution: http://www.bobbyvandersluis.com/articles/dynamicCSS.php
|
||||
*/
|
||||
function createCSS(sel, decl) {
|
||||
if (ua.ie && ua.mac) {
|
||||
return;
|
||||
}
|
||||
var h = doc.getElementsByTagName("head")[0], s = createElement("style");
|
||||
s.setAttribute("type", "text/css");
|
||||
s.setAttribute("media", "screen");
|
||||
if (!(ua.ie && ua.win) && typeof doc.createTextNode != UNDEF) {
|
||||
s.appendChild(doc.createTextNode(sel + " {" + decl + "}"));
|
||||
}
|
||||
h.appendChild(s);
|
||||
if (ua.ie && ua.win && typeof doc.styleSheets != UNDEF && doc.styleSheets.length > 0) {
|
||||
var ls = doc.styleSheets[doc.styleSheets.length - 1];
|
||||
if (typeof ls.addRule == OBJECT) {
|
||||
ls.addRule(sel, decl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setVisibility(id, isVisible) {
|
||||
var v = isVisible ? "visible" : "hidden";
|
||||
if (isDomLoaded) {
|
||||
getElementById(id).style.visibility = v;
|
||||
}
|
||||
else {
|
||||
createCSS("#" + id, "visibility:" + v);
|
||||
}
|
||||
}
|
||||
|
||||
function getTargetVersion(obj) {
|
||||
if (!obj)
|
||||
return 0;
|
||||
var c = obj.childNodes;
|
||||
var cl = c.length;
|
||||
for (var i = 0; i < cl; i++) {
|
||||
if (c[i].nodeType == 1 && c[i].nodeName.toLowerCase() == "object") {
|
||||
c = c[i].childNodes;
|
||||
cl = c.length;
|
||||
i = 0;
|
||||
}
|
||||
if (c[i].nodeType == 1 && c[i].nodeName.toLowerCase() == "param" && c[i].getAttribute("name") == "swfversion") {
|
||||
return c[i].getAttribute("value");
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function getExpressInstall(obj) {
|
||||
if (!obj)
|
||||
return "";
|
||||
var c = obj.childNodes;
|
||||
var cl = c.length;
|
||||
for (var i = 0; i < cl; i++) {
|
||||
if (c[i].nodeType == 1 && c[i].nodeName.toLowerCase() == "object") {
|
||||
c = c[i].childNodes;
|
||||
cl = c.length;
|
||||
i = 0;
|
||||
}
|
||||
if (c[i].nodeType == 1 && c[i].nodeName.toLowerCase() == "param" && c[i].getAttribute("name") == "expressinstall") {
|
||||
return c[i].getAttribute("value");
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
return {
|
||||
/* Public API
|
||||
- Reference: http://code.google.com/p/swfobject/wiki/SWFObject_2_0_documentation
|
||||
*/
|
||||
registerObject: function(objectIdStr, swfVersionStr, xiSwfUrlStr) {
|
||||
if (!ua.w3cdom || !objectIdStr) {
|
||||
return;
|
||||
}
|
||||
var obj = document.getElementById(objectIdStr);
|
||||
var xi = getExpressInstall(obj);
|
||||
var regObj = {};
|
||||
regObj.id = objectIdStr;
|
||||
regObj.swfVersion = swfVersionStr ? swfVersionStr : getTargetVersion(obj);
|
||||
regObj.expressInstall = xiSwfUrlStr ? xiSwfUrlStr : ((xi != "") ? xi : false);
|
||||
regObjArr[regObjArr.length] = regObj;
|
||||
setVisibility(objectIdStr, false);
|
||||
},
|
||||
|
||||
getObjectById: function(objectIdStr) {
|
||||
var r = null;
|
||||
if (ua.w3cdom && isDomLoaded) {
|
||||
var o = getElementById(objectIdStr);
|
||||
if (o) {
|
||||
var n = o.getElementsByTagName(OBJECT)[0];
|
||||
if (!n || (n && typeof o.SetVariable != UNDEF)) {
|
||||
r = o;
|
||||
}
|
||||
else if (typeof n.SetVariable != UNDEF) {
|
||||
r = n;
|
||||
}
|
||||
}
|
||||
}
|
||||
return r;
|
||||
},
|
||||
|
||||
embedSWF: function(swfUrlStr, replaceElemIdStr, widthStr, heightStr, swfVersionStr, xiSwfUrlStr, flashvarsObj, parObj, attObj) {
|
||||
if (!ua.w3cdom || !swfUrlStr || !replaceElemIdStr || !widthStr || !heightStr || !swfVersionStr) {
|
||||
return;
|
||||
}
|
||||
widthStr += ""; // Auto-convert to string to make it idiot proof
|
||||
heightStr += "";
|
||||
if (hasPlayerVersion(swfVersionStr)) {
|
||||
setVisibility(replaceElemIdStr, false);
|
||||
var att = (typeof attObj == OBJECT) ? attObj : {};
|
||||
att.data = swfUrlStr;
|
||||
att.width = widthStr;
|
||||
att.height = heightStr;
|
||||
var par = (typeof parObj == OBJECT) ? parObj : {};
|
||||
if (typeof flashvarsObj == OBJECT) {
|
||||
for (var i in flashvarsObj) {
|
||||
if (flashvarsObj[i] != Object.prototype[i]) { // Filter out prototype additions from other potential libraries
|
||||
if (typeof par.flashvars != UNDEF) {
|
||||
par.flashvars += "&" + i + "=" + flashvarsObj[i];
|
||||
}
|
||||
else {
|
||||
par.flashvars = i + "=" + flashvarsObj[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
addDomLoadEvent(function() {
|
||||
createSWF(att, par, replaceElemIdStr);
|
||||
if (att.id == replaceElemIdStr) {
|
||||
setVisibility(replaceElemIdStr, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (xiSwfUrlStr && !isExpressInstallActive && hasPlayerVersion("6.0.65") && (ua.win || ua.mac)) {
|
||||
setVisibility(replaceElemIdStr, false);
|
||||
addDomLoadEvent(function() {
|
||||
var regObj = {};
|
||||
regObj.id = regObj.altContentId = replaceElemIdStr;
|
||||
regObj.width = widthStr;
|
||||
regObj.height = heightStr;
|
||||
regObj.expressInstall = xiSwfUrlStr;
|
||||
showExpressInstall(regObj);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
getFlashPlayerVersion: function() {
|
||||
return { major:ua.pv[0], minor:ua.pv[1], release:ua.pv[2] };
|
||||
},
|
||||
|
||||
hasFlashPlayerVersion:hasPlayerVersion,
|
||||
|
||||
createSWF: function(attObj, parObj, replaceElemIdStr) {
|
||||
if (ua.w3cdom && isDomLoaded) {
|
||||
return createSWF(attObj, parObj, replaceElemIdStr);
|
||||
}
|
||||
else {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
|
||||
createCSS: function(sel, decl) {
|
||||
if (ua.w3cdom) {
|
||||
createCSS(sel, decl);
|
||||
}
|
||||
},
|
||||
|
||||
addDomLoadEvent:addDomLoadEvent,
|
||||
|
||||
addLoadEvent:addLoadEvent,
|
||||
|
||||
getQueryParamValue: function(param) {
|
||||
var q = doc.location.search || doc.location.hash;
|
||||
if (param == null) {
|
||||
return q;
|
||||
}
|
||||
if(q) {
|
||||
var pairs = q.substring(1).split("&");
|
||||
for (var i = 0; i < pairs.length; i++) {
|
||||
if (pairs[i].substring(0, pairs[i].indexOf("=")) == param) {
|
||||
return pairs[i].substring((pairs[i].indexOf("=") + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
},
|
||||
|
||||
// For internal usage only
|
||||
expressInstallCallback: function() {
|
||||
if (isExpressInstallActive && storedAltContent) {
|
||||
var obj = getElementById(EXPRESS_INSTALL_ID);
|
||||
if (obj) {
|
||||
obj.parentNode.replaceChild(storedAltContent, obj);
|
||||
if (storedAltContentId) {
|
||||
setVisibility(storedAltContentId, true);
|
||||
if (ua.ie && ua.win) {
|
||||
storedAltContent.style.display = "block";
|
||||
}
|
||||
}
|
||||
storedAltContent = null;
|
||||
storedAltContentId = null;
|
||||
isExpressInstallActive = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}();
|
||||
@@ -93,6 +93,7 @@
|
||||
$enddatum = new DateTime('now');
|
||||
$enddatum->modify($monthstoadd);
|
||||
$enddatum = $enddatum->format('Ymd');
|
||||
$datum_heute = 20251114; //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
|
||||
$sql_sel = "SELECT * FROM fdatum1 where datum >='$datum_heute' && datum <= '$enddatum' order by datum ASC";
|
||||
$result = mysqli_query($db, $sql_sel) or die(mysqli_error($db));
|
||||
while ($row = mysqli_fetch_assoc($result)) {
|
||||
@@ -291,7 +292,6 @@
|
||||
"$stern_vorname $stern_name am " . preg_replace("/(\d+) Uhr/","um $0",$stern_datum) . " für $stern_teil $person " .
|
||||
// $stern_teil == 1 ? "Person" : "Personen" .
|
||||
".\r\n\r\n" .
|
||||
"Bitte bringen Sie diese Bestätigung als Ausdruck oder digital zur Führung mit. \r\n\r\n".
|
||||
"Die Führung findet NUR bei sternklarem Himmel statt. Falls der Himmel bedeckt ist \r\n" .
|
||||
"und die Führung ausfällt, erhalten Sie bis spätestens eine Stunde vor Führungsbeginn \r\n" .
|
||||
"eine Email. Sie können sich dann gerne zu einem neuen Termin anmelden.\r\n\r\n" .
|
||||
@@ -378,15 +378,19 @@
|
||||
</p>
|
||||
<p>
|
||||
Wenn Sie alle Felder ausgefüllt und abgeschickt haben (mit dem "Anmeldung senden"-Knopf),
|
||||
erhalten Sie eine Anmeldebestätigung per e-mail. Diese bitte unbedingt zur Führung
|
||||
ausgedruckt oder in digitaler Form mitbringen!
|
||||
erhalten Sie eine Anmeldebestätigung per e-mail.
|
||||
|
||||
<div style="text-align:center;"><strong>Ohne die mitgebrachte Anmeldebestätigung erfolgt
|
||||
<!-- <div style="text-align:center;"><strong>Ohne die mitgebrachte Anmeldebestätigung erfolgt
|
||||
k e i n Einlass.</strong></div>
|
||||
-->
|
||||
</p>
|
||||
<p>
|
||||
<?PHP echo $stern_error_msg ?>
|
||||
</p>
|
||||
<p class="sondermeldung">
|
||||
Unsere Sternwarte bleibt bis voraussichtlich 14. November 2025 wegen neuer technischer Einrichtung
|
||||
geschlossen. <br />Bis dahin können keine Führungen durchgeführt werden.
|
||||
</p>
|
||||
<strong>Anmeldung:</strong>
|
||||
<span class="textklein">(Alle Felder mit '*' müssen ausgefüllt werden)</span><br />
|
||||
<br />
|
||||
@@ -478,7 +482,7 @@
|
||||
<p>
|
||||
Hinweis zum Datenschutz: <a href="" id="dschu">Datenschutzerklärung</a>
|
||||
</p>
|
||||
<p class="lastchange">Letzte Änderungen: 2024-05-22 rxf</p>
|
||||
<p class="lastchange">Letzte Änderungen: 2025-12-01 rxf</p>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -290,8 +290,6 @@
|
||||
"$stern_vorname $stern_name am " . preg_replace("/(\d+) Uhr/","um $0",$stern_datum) . " für $stern_teil $person " .
|
||||
// $stern_teil == 1 ? "Person" : "Personen" .
|
||||
".\r\n\r\n" .
|
||||
"Bitte bringen Sie diese Bestätigung als Ausdruck oder digital zur Führung mit. \r\n".
|
||||
"Ohne diese Bestätigung und 2G-Zertifikat erfolgt ausnahmslos k e i n Einlass.\r\n\r\n" .
|
||||
"Die Führung findet NUR bei sternklarem Himmel statt. Falls der Himmel bedeckt ist \r\n" .
|
||||
"und die Führung ausfällt, bitten wir Sie um eine neue Anmeldung.\r\n\r\n" .
|
||||
"Die Hygienevorschriften sind zu beachten: die Teilnehmer müssen eine medizinische Maske,\r\n" .
|
||||
@@ -367,8 +365,7 @@
|
||||
</p>
|
||||
<p>
|
||||
Wenn Sie alle Felder ausgefüllt und abgeschickt haben (mit dem "Anmeldung senden"-Knopf),
|
||||
erhalten Sie eine Anmeldebestätigung per e-mail. Diese bitte unbedingt zur Führung
|
||||
ausgedruckt oder in digitaler Form mitbringen!
|
||||
erhalten Sie eine Anmeldebestätigung per e-mail.
|
||||
|
||||
<div style="text-align:center;"><strong>Ohne die mitgebrachte Anmeldebestätigung und dem Impfzertifikat erfolgt ausnahmslos
|
||||
k e i n Einlass.</strong></div>
|
||||
|
||||
@@ -257,8 +257,6 @@
|
||||
"$stern_vorname $stern_name am " . preg_replace("/(\d+) Uhr/","um $0",$stern_datum) . " für $stern_teil $person " .
|
||||
// $stern_teil == 1 ? "Person" : "Personen" .
|
||||
".\r\n\r\n" .
|
||||
"Bitte bringen Sie diese Bestätigung als Ausdruck oder digital zur Führung mit. \r\n".
|
||||
"Ohne diese Bestätigung erfolgt ausnahmslos k e i n Einlass.\r\n\r\n" .
|
||||
"Die Führung findet NUR bei sternklarem Himmel statt. Falls der Himmel bedeckt ist \r\n" .
|
||||
"und die Führung ausfällt, bitten wir Sie um eine neue Anmeldung.\r\n\r\n" .
|
||||
"Die Hygienevorschriften sind zu beachten: Die Teilnehmer müssen Gesichtsmasken \r\n" .
|
||||
@@ -340,8 +338,7 @@
|
||||
</p>
|
||||
<p>
|
||||
Wenn Sie alle Felder ausgefüllt und abgeschickt haben (mit den "Anmeldung senden"-Knopf),
|
||||
erhalten Sie eine Anmeldebestätigung per e-mail. Diese bitte unbedingt zur Führung
|
||||
ausgedruckt oder in digitaler Form mitbringen!
|
||||
erhalten Sie eine Anmeldebestätigung per e-mail.
|
||||
|
||||
<div style="text-align:center;"><strong>Ohne die mitgebrachte Anmeldebestätigung erfolgt ausnahmslos
|
||||
k e i n Einlass.</strong></div>
|
||||
|
||||
14
sternwarte/beoanswer/.env.example
Normal file
14
sternwarte/beoanswer/.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
|
||||
10
sternwarte/beoanswer/.env.production
Normal file
10
sternwarte/beoanswer/.env.production
Normal file
@@ -0,0 +1,10 @@
|
||||
# Production Environment Variables
|
||||
# VITE_API_URL=/intern/sofue/php/sofueDB.php
|
||||
VITE_API_URL=/DB4js_all.php # Lokales Development Backend (über Proxy)
|
||||
|
||||
# HTTP Basic Authentication
|
||||
VITE_API_USERNAME=beogruppe
|
||||
VITE_API_PASSWORD=ArktUhr
|
||||
|
||||
# Optional: Debug-Modus für Production meist ausgeschaltet
|
||||
# VITE_DEBUG=false
|
||||
30
sternwarte/beoanswer/.gitignore
vendored
Normal file
30
sternwarte/beoanswer/.gitignore
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Environment variables
|
||||
.env*
|
||||
|
||||
# CORS-Proxy Konfiguration (enthält Credentials)
|
||||
cors-config.php
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
13
sternwarte/beoanswer/ACHTUNG.md
Normal file
13
sternwarte/beoanswer/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
|
||||
123
sternwarte/beoanswer/DEPLOYMENT.md
Normal file
123
sternwarte/beoanswer/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
|
||||
12
sternwarte/beoanswer/Dockerfile
Normal file
12
sternwarte/beoanswer/Dockerfile
Normal file
@@ -0,0 +1,12 @@
|
||||
FROM node:25-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
|
||||
COPY . .
|
||||
|
||||
EXPOSE 5173
|
||||
|
||||
CMD ["npm", "run", "dev", "--", "--host"]
|
||||
273
sternwarte/beoanswer/README.md
Normal file
273
sternwarte/beoanswer/README.md
Normal file
@@ -0,0 +1,273 @@
|
||||
# BeoAnswer React App
|
||||
|
||||
Eine React-Anwendung zur Nachbearbeitung von Sonderführungen mit Backend-Integration.
|
||||
|
||||
## 📋 Features
|
||||
|
||||
- **Interaktive Formulare** für Führungsnachbearbeitung
|
||||
- **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**
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### Voraussetzungen
|
||||
|
||||
- Node.js (v16 oder höher)
|
||||
- npm oder yarn
|
||||
|
||||
### 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
|
||||
@@ -1,377 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
|
||||
<title>beoanswer</title>
|
||||
|
||||
|
||||
<style type="text/css">
|
||||
body {
|
||||
font-family: Helvetica, arial, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
background-color: white;
|
||||
padding: 30px; }
|
||||
|
||||
body > *:first-child {
|
||||
margin-top: 0 !important; }
|
||||
body > *:last-child {
|
||||
margin-bottom: 0 !important; }
|
||||
|
||||
a {
|
||||
color: #4183C4; }
|
||||
a.absent {
|
||||
color: #cc0000; }
|
||||
a.anchor {
|
||||
display: block;
|
||||
padding-left: 30px;
|
||||
margin-left: -30px;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0; }
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin: 20px 0 10px;
|
||||
padding: 0;
|
||||
font-weight: bold;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
cursor: text;
|
||||
position: relative; }
|
||||
|
||||
h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor, h5:hover a.anchor, h6:hover a.anchor {
|
||||
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA09pVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoMTMuMCAyMDEyMDMwNS5tLjQxNSAyMDEyLzAzLzA1OjIxOjAwOjAwKSAgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OUM2NjlDQjI4ODBGMTFFMTg1ODlEODNERDJBRjUwQTQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OUM2NjlDQjM4ODBGMTFFMTg1ODlEODNERDJBRjUwQTQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo5QzY2OUNCMDg4MEYxMUUxODU4OUQ4M0REMkFGNTBBNCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo5QzY2OUNCMTg4MEYxMUUxODU4OUQ4M0REMkFGNTBBNCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PsQhXeAAAABfSURBVHjaYvz//z8DJYCRUgMYQAbAMBQIAvEqkBQWXI6sHqwHiwG70TTBxGaiWwjCTGgOUgJiF1J8wMRAIUA34B4Q76HUBelAfJYSA0CuMIEaRP8wGIkGMA54bgQIMACAmkXJi0hKJQAAAABJRU5ErkJggg==) no-repeat 10px center;
|
||||
text-decoration: none; }
|
||||
|
||||
h1 tt, h1 code {
|
||||
font-size: inherit; }
|
||||
|
||||
h2 tt, h2 code {
|
||||
font-size: inherit; }
|
||||
|
||||
h3 tt, h3 code {
|
||||
font-size: inherit; }
|
||||
|
||||
h4 tt, h4 code {
|
||||
font-size: inherit; }
|
||||
|
||||
h5 tt, h5 code {
|
||||
font-size: inherit; }
|
||||
|
||||
h6 tt, h6 code {
|
||||
font-size: inherit; }
|
||||
|
||||
h1 {
|
||||
font-size: 28px;
|
||||
color: black; }
|
||||
|
||||
h2 {
|
||||
font-size: 24px;
|
||||
border-bottom: 1px solid #cccccc;
|
||||
color: black; }
|
||||
|
||||
h3 {
|
||||
font-size: 18px; }
|
||||
|
||||
h4 {
|
||||
font-size: 16px; }
|
||||
|
||||
h5 {
|
||||
font-size: 14px; }
|
||||
|
||||
h6 {
|
||||
color: #777777;
|
||||
font-size: 14px; }
|
||||
|
||||
p, blockquote, ul, ol, dl, li, table, pre {
|
||||
margin: 15px 0; }
|
||||
|
||||
hr {
|
||||
background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAECAYAAACtBE5DAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OENDRjNBN0E2NTZBMTFFMEI3QjRBODM4NzJDMjlGNDgiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OENDRjNBN0I2NTZBMTFFMEI3QjRBODM4NzJDMjlGNDgiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4Q0NGM0E3ODY1NkExMUUwQjdCNEE4Mzg3MkMyOUY0OCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4Q0NGM0E3OTY1NkExMUUwQjdCNEE4Mzg3MkMyOUY0OCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PqqezsUAAAAfSURBVHjaYmRABcYwBiM2QSA4y4hNEKYDQxAEAAIMAHNGAzhkPOlYAAAAAElFTkSuQmCC) repeat-x 0 0;
|
||||
border: 0 none;
|
||||
color: #cccccc;
|
||||
height: 4px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body > h2:first-child {
|
||||
margin-top: 0;
|
||||
padding-top: 0; }
|
||||
body > h1:first-child {
|
||||
margin-top: 0;
|
||||
padding-top: 0; }
|
||||
body > h1:first-child + h2 {
|
||||
margin-top: 0;
|
||||
padding-top: 0; }
|
||||
body > h3:first-child, body > h4:first-child, body > h5:first-child, body > h6:first-child {
|
||||
margin-top: 0;
|
||||
padding-top: 0; }
|
||||
|
||||
a:first-child h1, a:first-child h2, a:first-child h3, a:first-child h4, a:first-child h5, a:first-child h6 {
|
||||
margin-top: 0;
|
||||
padding-top: 0; }
|
||||
|
||||
h1 p, h2 p, h3 p, h4 p, h5 p, h6 p {
|
||||
margin-top: 0; }
|
||||
|
||||
li p.first {
|
||||
display: inline-block; }
|
||||
li {
|
||||
margin: 0; }
|
||||
ul, ol {
|
||||
padding-left: 30px; }
|
||||
|
||||
ul :first-child, ol :first-child {
|
||||
margin-top: 0; }
|
||||
|
||||
dl {
|
||||
padding: 0; }
|
||||
dl dt {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
padding: 0;
|
||||
margin: 15px 0 5px; }
|
||||
dl dt:first-child {
|
||||
padding: 0; }
|
||||
dl dt > :first-child {
|
||||
margin-top: 0; }
|
||||
dl dt > :last-child {
|
||||
margin-bottom: 0; }
|
||||
dl dd {
|
||||
margin: 0 0 15px;
|
||||
padding: 0 15px; }
|
||||
dl dd > :first-child {
|
||||
margin-top: 0; }
|
||||
dl dd > :last-child {
|
||||
margin-bottom: 0; }
|
||||
|
||||
blockquote {
|
||||
border-left: 4px solid #dddddd;
|
||||
padding: 0 15px;
|
||||
color: #777777; }
|
||||
blockquote > :first-child {
|
||||
margin-top: 0; }
|
||||
blockquote > :last-child {
|
||||
margin-bottom: 0; }
|
||||
|
||||
table {
|
||||
padding: 0;border-collapse: collapse; }
|
||||
table tr {
|
||||
border-top: 1px solid #cccccc;
|
||||
background-color: white;
|
||||
margin: 0;
|
||||
padding: 0; }
|
||||
table tr:nth-child(2n) {
|
||||
background-color: #f8f8f8; }
|
||||
table tr th {
|
||||
font-weight: bold;
|
||||
border: 1px solid #cccccc;
|
||||
margin: 0;
|
||||
padding: 6px 13px; }
|
||||
table tr td {
|
||||
border: 1px solid #cccccc;
|
||||
margin: 0;
|
||||
padding: 6px 13px; }
|
||||
table tr th :first-child, table tr td :first-child {
|
||||
margin-top: 0; }
|
||||
table tr th :last-child, table tr td :last-child {
|
||||
margin-bottom: 0; }
|
||||
|
||||
img {
|
||||
max-width: 100%; }
|
||||
|
||||
span.frame {
|
||||
display: block;
|
||||
overflow: hidden; }
|
||||
span.frame > span {
|
||||
border: 1px solid #dddddd;
|
||||
display: block;
|
||||
float: left;
|
||||
overflow: hidden;
|
||||
margin: 13px 0 0;
|
||||
padding: 7px;
|
||||
width: auto; }
|
||||
span.frame span img {
|
||||
display: block;
|
||||
float: left; }
|
||||
span.frame span span {
|
||||
clear: both;
|
||||
color: #333333;
|
||||
display: block;
|
||||
padding: 5px 0 0; }
|
||||
span.align-center {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
clear: both; }
|
||||
span.align-center > span {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
margin: 13px auto 0;
|
||||
text-align: center; }
|
||||
span.align-center span img {
|
||||
margin: 0 auto;
|
||||
text-align: center; }
|
||||
span.align-right {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
clear: both; }
|
||||
span.align-right > span {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
margin: 13px 0 0;
|
||||
text-align: right; }
|
||||
span.align-right span img {
|
||||
margin: 0;
|
||||
text-align: right; }
|
||||
span.float-left {
|
||||
display: block;
|
||||
margin-right: 13px;
|
||||
overflow: hidden;
|
||||
float: left; }
|
||||
span.float-left span {
|
||||
margin: 13px 0 0; }
|
||||
span.float-right {
|
||||
display: block;
|
||||
margin-left: 13px;
|
||||
overflow: hidden;
|
||||
float: right; }
|
||||
span.float-right > span {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
margin: 13px auto 0;
|
||||
text-align: right; }
|
||||
|
||||
code, tt {
|
||||
margin: 0 2px;
|
||||
padding: 0 5px;
|
||||
white-space: nowrap;
|
||||
border: 1px solid #eaeaea;
|
||||
background-color: #f8f8f8;
|
||||
border-radius: 3px; }
|
||||
|
||||
pre code {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
white-space: pre;
|
||||
border: none;
|
||||
background: transparent; }
|
||||
|
||||
.highlight pre {
|
||||
background-color: #f8f8f8;
|
||||
border: 1px solid #cccccc;
|
||||
font-size: 13px;
|
||||
line-height: 19px;
|
||||
overflow: auto;
|
||||
padding: 6px 10px;
|
||||
border-radius: 3px; }
|
||||
|
||||
pre {
|
||||
background-color: #f8f8f8;
|
||||
border: 1px solid #cccccc;
|
||||
font-size: 13px;
|
||||
line-height: 19px;
|
||||
overflow: auto;
|
||||
padding: 6px 10px;
|
||||
border-radius: 3px; }
|
||||
pre code, pre tt {
|
||||
background-color: transparent;
|
||||
border: none; }
|
||||
|
||||
sup {
|
||||
font-size: 0.83em;
|
||||
vertical-align: super;
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
kbd {
|
||||
display: inline-block;
|
||||
padding: 3px 5px;
|
||||
font-size: 11px;
|
||||
line-height: 10px;
|
||||
color: #555;
|
||||
vertical-align: middle;
|
||||
background-color: #fcfcfc;
|
||||
border: solid 1px #ccc;
|
||||
border-bottom-color: #bbb;
|
||||
border-radius: 3px;
|
||||
box-shadow: inset 0 -1px 0 #bbb
|
||||
}
|
||||
|
||||
* {
|
||||
-webkit-print-color-adjust: exact;
|
||||
}
|
||||
@media screen and (min-width: 914px) {
|
||||
body {
|
||||
width: 854px;
|
||||
margin:0 auto;
|
||||
}
|
||||
}
|
||||
@media print {
|
||||
table, pre {
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
pre {
|
||||
word-wrap: break-word;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<h1 id="toc_0">Anleitung</h1>
|
||||
|
||||
<p>Die Webseite hilft bei der Nachbearbeitung von Sonderführungen auf der Sternwarte. Es werden der Reihe nach die relevanten Fragen gestellt, die dann zu beantworten sind. </p>
|
||||
|
||||
<ul>
|
||||
<li><strong>Fand die Führung statt?</strong><br>
|
||||
Wird benatwortet durch Klick auf <strong>ja</strong> oder <strong>nein</strong>.<br></li>
|
||||
</ul>
|
||||
|
||||
<p>Falls die Führung statt fand, gehts weiter mit:</p>
|
||||
|
||||
<ul>
|
||||
<li><p><strong>Besucher-Anzahl</strong><br>
|
||||
Hier bitte die tatsächliche Anzahl der Besucher eintragen. Bestätigen mit <em>Return</em> oder durch Klick auf OK. Danach folgt:</p></li>
|
||||
<li><p><strong>Eine Spende</strong><br>
|
||||
Es kann gewählt werden, ob eine Barspende eingegangen ist, ob eine Spende überwiesen wird, ob eine Spende in die Spendenkasse
|
||||
geworfen wurde oder ob keine Spende ausgemacht wurde (z.B. private Führungen). Falls "<em>ist in bar eingegangen</em>" gewäht wurde, kommt:</p></li>
|
||||
<li><p><strong>Höhe der Barspende</strong><br>
|
||||
Hier bitte den Betrag in EUR eingeben und wieder mit <em>Return</em> oder Klich aif OK abschließen. </p>
|
||||
|
||||
<p>Als Letztes kann noch eine Bemerkung zu der Führung eingegeben werden: </p></li>
|
||||
<li><p><strong>Bemerkungen (optional)</strong><br>
|
||||
Diese kann auch leer bleiben. Allerdings <strong>muss</strong> unbeding mit Klick auf OK abgeschlossen werden, sonst gehts nicht weiter.<br>
|
||||
Danach werden durch Klick auf den <strong>Senden</strong>-Button die Daten an die Datenbank gesendet.</p></li>
|
||||
</ul>
|
||||
|
||||
<p>Falls die Führung <strong>nicht</strong> stattgefunden hat, gehts weiter mit </p>
|
||||
|
||||
<ul>
|
||||
<li><p><strong>Die Führung wurde</strong><br>
|
||||
<strong>abgestagt</strong> oder <strong>verschoben</strong><br>
|
||||
Wird <strong>abgesagt</strong> gewählt, erscheint gleich der <strong>Senden</strong>-Button. Die Führung wird dann in der Datenbank als <em>abgesagt</em> markiert.<br>
|
||||
Wird <strong>verwschoben</strong> gewählt, so erscheint </p></li>
|
||||
<li><p><strong>Verschoben auf:</strong><br>
|
||||
und es kann ein neues Datum (mit Uhrzeit) über die aufpoppende Datums-Auswahl gewählt werden. Nach Abschluss mit OK erscheint nun wieder der <strong>Sende</strong>-Button. Durch Klick darauf wird die Führung mit dem neuen Datum als <em>zugesagt</em> in die Datenbank übernommen. Der verantwortliche BEO erhält eine Erinnerungs-Mail.</p>
|
||||
|
||||
<p>Während der gesamten Eingabe-Prozedur kann über den <strong>Abbruch</strong>-Button jederzeit der Vorgang abgebrochen und neu begonnen werden.</p></li>
|
||||
</ul>
|
||||
|
||||
<p>rxf 2019-02-04 </p>
|
||||
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,41 +0,0 @@
|
||||
#Anleitung
|
||||
|
||||
Die Webseite hilft bei der Nachbearbeitung von Sonderführungen auf der Sternwarte. Es werden der Reihe nach die relevanten Fragen gestellt, die dann zu beantworten sind.
|
||||
|
||||
* **Fand die Führung statt?**
|
||||
Wird benatwortet durch Klick auf **ja** oder **nein**.
|
||||
|
||||
Falls die Führung statt fand, gehts weiter mit:
|
||||
|
||||
* **Besucher-Anzahl**
|
||||
Hier bitte die tatsächliche Anzahl der Besucher eintragen. Bestätigen mit *Return* oder durch Klick auf OK. Danach folgt:
|
||||
|
||||
* **Eine Spende**
|
||||
Es kann gewählt werden, ob eine Barspende eingegangen ist, ob eine Spende überwiesen wird oder ob keine Spende ausgemacht wurde (z.B. private Führungen). Falss "*ist in bar eingegangen*" gewäht wurde, kommt:
|
||||
|
||||
* **Höhe der Barspende**
|
||||
Hier bitte den Betrag in EUR eingeben und wieder mit *Return* oder Klich aif OK abschließen.
|
||||
|
||||
Als Letztes kann noch eine Bemerkung zu der Führung eingegeben werden:
|
||||
|
||||
* **Bemerkungen (optional)**
|
||||
Diese kann auch leer bleiben. Allerdings **muss** unbeding mit Klick auf OK abgeschlossen werden, sonst gehts nicht weiter.
|
||||
Danach werden durch Klick auf den **Senden**-Button die Daten an die Datenbank gesendet.
|
||||
|
||||
Falls die Führung **nicht** stattgefunden hat, gehts weiter mit
|
||||
|
||||
* **Die Führung wurde**
|
||||
**abgestagt** oder **verschoben**
|
||||
Wird **abgesagt** gewählt, erscheint gleich der **Senden**-Button. Die Führung wird dann in der Datenbank als *abgesagt* markiert.
|
||||
Wird **verwschoben** gewählt, so erscheint
|
||||
|
||||
* **Verschoben auf:**
|
||||
und es kann ein neues Datum (mit Uhrzeit) über die aufpoppende Datums-Auswahl gewählt werden. Nach Abschluss mit OK erscheint nun wieder der **Sende**-Button. Durch Klick darauf wird die Führung mit dem neuen Datum als *zugesagt* in die Datenbank übernommen. Der verantwortliche BEO erhält eine Erinnerungs-Mail.
|
||||
|
||||
Während der gesamten Eingabe-Prozedur kann über den **Abbruch**-Button jederzeit der Vorgang abgebrochen und neu begonnen werden.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
rxf 2018-10-17
|
||||
@@ -1,176 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Nachbearbeitung</title>
|
||||
<link rel="stylesheet" type="text/css" media="screen" href="../intern/sofue/css/jquery-ui.min.css" />
|
||||
<link rel="stylesheet" type="text/css" media="screen" href="../intern/sofue/css/ui.jqgrid.css" />
|
||||
<link rel="stylesheet" type="text/css" media="screen" href="../intern/sofue/css/jquery-ui-timepicker-addon.css" />
|
||||
<link href="css/basscss-custom.css" rel="stylesheet">
|
||||
<link rel="stylesheet" type="text/css" media="screen" href="css/mystyle.css" />
|
||||
|
||||
<script type="text/javascript" src="../intern/sofue/js/jquery-1.11.0.min.js"></script>
|
||||
<script type="text/javascript" src="../intern/sofue/js/i18n/grid.locale-de.js"></script>
|
||||
<script type="text/javascript" src="../intern/sofue/js/jquery.jqGrid.min.js"></script>
|
||||
<script type="text/javascript" src="../intern/sofue/js/jquery-ui-1.10.0.custom.min.js"></script>
|
||||
<script type="text/javascript" src="../intern/sofue/js/jquery-ui-timepicker-addon.js"></script>
|
||||
<script type="text/javascript" src="../intern/sofue/js/moment.js"></script>
|
||||
<script type="text/javascript" src="../intern/sofue/js/de.js"></script>
|
||||
<script type="text/javascript" src="../intern/sofue/js/ajax.js"></script>
|
||||
<script type="text/javascript" src="js/jquery-ui-slider-access-addon.js"></script>
|
||||
|
||||
<script type="text/javascript" src="js/version.js"></script>
|
||||
<script type="text/javascript" src="js/beoanswer.js"></script>
|
||||
|
||||
</head>
|
||||
|
||||
<?php
|
||||
$id = $_GET['id']
|
||||
?>
|
||||
<script>
|
||||
let url_id = <?php echo json_encode($id); ?>;
|
||||
</script>
|
||||
|
||||
<body>
|
||||
<div id="master" class="container h3 border border-blue m1 bg-darken-1">
|
||||
<section class="px1">
|
||||
|
||||
<h1 id="sofueHead" class="h1 mb0 center">Sonderführung vom <br class="sm-hide"></h1>
|
||||
<h3 class="h3 mb2 center" id="sofueName">für </h3>
|
||||
<h2 class="h2 mb3 center bg-yellow">Nachbearbeitung</h2>
|
||||
|
||||
<div class="clearfix" >
|
||||
<div>
|
||||
<div id="inputstatt" class="mb2 border">
|
||||
<div class="bold" >Fand die Führung statt?</div>
|
||||
<div>
|
||||
<label class="inline">
|
||||
<input type="radio" id="statt_ja" name="statt" value="ja">
|
||||
ja
|
||||
</label>
|
||||
<label class="inline">
|
||||
<input type="radio" id="statt_nein" name="statt" value="nein">
|
||||
nein
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="inputabs" class="mb2 border hide">
|
||||
<div class="bold">Die Führung wurde </div>
|
||||
<div>
|
||||
<label class="inline mb2 ">
|
||||
<input type="radio" id="abges" name="nostatt" value="abgesagt">
|
||||
abgesagt.
|
||||
</label>
|
||||
<label class="inline mb2">
|
||||
<input type="radio" id="verscho" name="nostatt" value="verschoben">
|
||||
verschoben.
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="verschbn" class="mb2 border hide">
|
||||
<div class="bold">
|
||||
<label for="newtermin" class="bold mr2">Verschoben auf:</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="text" id="newtermin" placeholder="neues Datum wählen">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="besucher" class="mb2 border hide">
|
||||
<div class="bold">
|
||||
<label for="beszahl" class="bold mr2">Besucher-Anzahl:</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="text" id="beszahl" placeholder="Anzahl eingeben">
|
||||
<button class="btn btn-primary mb1 mr2 ml3 mt1" id="btnOK0">OK</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
<div id="diespende" class="mb2 border hide">
|
||||
<div class=" bold">Eine Spende</div>
|
||||
<div>
|
||||
<div id="inputspend">
|
||||
<label class="block"> <input type="radio" id="spendbar"
|
||||
name="spend" value="bar"> ist in bar eingegangen.
|
||||
</label>
|
||||
<label class="block"> <input type="radio"
|
||||
id="spendueber" name="spend" value="ueberweis"> wird überwiesen.
|
||||
</label>
|
||||
<label class="block"> <input type="radio"
|
||||
id="spendkasse" name="spend" value="kasse"> ist in der Spendenkasse.
|
||||
</label>
|
||||
<label class="block mb1"> <input type="radio"
|
||||
id="spendno" name="spend" value="nospend"> ist nicht vorgesehen.
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="barspend" class="mb2 border hide">
|
||||
<div class="bold">
|
||||
<label for="barsp" class="bold mr2">Höhe der Barspende:</label>
|
||||
</div>
|
||||
<div>
|
||||
<input class="mr1" type="text" id="barsp" placeholder="Betrag in EURO">€
|
||||
<button class="btn btn-primary mb1 mr2 ml3 mt1" id="btnOK1">OK</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
<div id="remarks" class="mb2 border hide">
|
||||
<div class="bold">
|
||||
<label for="remark" class="bold mr2">Bemerkungen (optional):</label>
|
||||
</div>
|
||||
<div>
|
||||
<textarea class="mr1" id="remark" placeholder="Bemerkung"></textarea>
|
||||
<button class="btn btn-primary mb1 mr2 ml3 mt1" id="btnOK2">OK</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="fertig" class="mb2 border hide ">
|
||||
<div class="bold center">
|
||||
Fertig
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="beendet" class="m2 border hide">
|
||||
<div class="bold center">
|
||||
Daten in die Datenbank übernommen. <br />
|
||||
Vielen Dank
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="center mt2" id="tosend">
|
||||
<button class="btn btn-primary mb1 mr2" id="btncancel">Abbruch</button>
|
||||
<button class="btn btn-primary mb1 mr2" id="btnMan">Anleitung</button>
|
||||
<button class="btn btn-primary mb1 hide" id="btnsend">Senden</button>
|
||||
</div>
|
||||
|
||||
<!-- Ende von "main" -->
|
||||
|
||||
<!-- div für PopUp -->
|
||||
<div id="maint"></div>
|
||||
|
||||
<!-- Info unter Tabelle -->
|
||||
<div id="author" class="h5 mt2 mb1 py1">
|
||||
<div id="mailadr" class="left px1">
|
||||
<a href="mailto:rexfue@gmail.com">mailto:rexfue@gmail.com</a>
|
||||
</div>
|
||||
<div id="versn" class="right px1">
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div id='anleitung'></div>
|
||||
</div>
|
||||
<!-- container -->
|
||||
|
||||
</body>
|
||||
</html>
|
||||
114
sternwarte/beoanswer/cors-proxy.php
Normal file
114
sternwarte/beoanswer/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;
|
||||
?>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,53 +0,0 @@
|
||||
:root {
|
||||
--height-w0:45px;
|
||||
--height-w1:60px;
|
||||
--height-w2:100px;
|
||||
--container-width: 30em;
|
||||
--button-color: black;
|
||||
--button-background-color: aqua;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
color: #000;
|
||||
background-color: skyblue;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
#master {
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
#author {
|
||||
border-top: 1px blue solid;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 30em;
|
||||
}
|
||||
|
||||
|
||||
#remark {
|
||||
width: 200px;
|
||||
/* border: solid 1px blue; */
|
||||
}
|
||||
|
||||
textarea {
|
||||
line-height: 1.1;
|
||||
padding: .5rem .5rem;
|
||||
}
|
||||
|
||||
#remarks, #beszahl, #barsp {
|
||||
/* border: solid 1px green; */
|
||||
/* float: left; */
|
||||
|
||||
}
|
||||
|
||||
#btnOK2, #btnOK1, #btnOK0 {
|
||||
/* border: solid 1px red; */
|
||||
/* float: left; */
|
||||
}
|
||||
|
||||
|
||||
#diespende {
|
||||
clear: both;
|
||||
}
|
||||
12
sternwarte/beoanswer/docker-compose.yml
Normal file
12
sternwarte/beoanswer/docker-compose.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
services:
|
||||
beoanswer_rect:
|
||||
build: .
|
||||
ports:
|
||||
- "5173:5173"
|
||||
volumes:
|
||||
- .:/app # Source-Code in Container mounten
|
||||
- /app/node_modules # node_modules im Container behalten
|
||||
environment:
|
||||
- NODE_ENV=development
|
||||
command: ["npm", "run", "dev", "--", "--host"]
|
||||
|
||||
29
sternwarte/beoanswer/eslint.config.js
Normal file
29
sternwarte/beoanswer/eslint.config.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import js from '@eslint/js'
|
||||
import globals from 'globals'
|
||||
import reactHooks from 'eslint-plugin-react-hooks'
|
||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||
import { defineConfig, globalIgnores } from 'eslint/config'
|
||||
|
||||
export default defineConfig([
|
||||
globalIgnores(['dist']),
|
||||
{
|
||||
files: ['**/*.{js,jsx}'],
|
||||
extends: [
|
||||
js.configs.recommended,
|
||||
reactHooks.configs['recommended-latest'],
|
||||
reactRefresh.configs.vite,
|
||||
],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
ecmaFeatures: { jsx: true },
|
||||
sourceType: 'module',
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
|
||||
},
|
||||
},
|
||||
])
|
||||
13
sternwarte/beoanswer/index.html
Normal file
13
sternwarte/beoanswer/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="./src/assets/react.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>beoanswer</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,318 +0,0 @@
|
||||
/*
|
||||
*
|
||||
*******************
|
||||
2025-06-16:
|
||||
Da leider kein CRON auf dem Webserver läuft, kann 'checkfuehrung' bis auf
|
||||
Weiteres nicht weiter gepflegt bzw. ausgeführt werden. 'checkfuehrung' ist
|
||||
für dieses Programm )beoanswer) hier notwendig.
|
||||
*******************
|
||||
|
||||
*/
|
||||
$(document).ready(function() {
|
||||
|
||||
let sendobject = {
|
||||
stattgefunden: false,
|
||||
anzahl: 0,
|
||||
spende:0,
|
||||
betrag:0,
|
||||
rem:'',
|
||||
newtermin: '1900-01-01',
|
||||
};
|
||||
let ajaxURL = '../../intern/sofue/php/sofueDB.php'
|
||||
$('#versn').html("Version: " + VERSION + ' vom ' + VDATE);
|
||||
|
||||
console.log("von php:", url_id);
|
||||
|
||||
// Daten der Führung holen
|
||||
if(url_id != null) {
|
||||
doAjaxCall_arr(ajaxURL,{cmd: 'GET_ONE', id: url_id}, enterajaxerg);
|
||||
} else {
|
||||
alert("Keine ID übergeben");
|
||||
return false
|
||||
// throw new Error("Keine ID übergeben");
|
||||
}
|
||||
|
||||
function enterajaxerg(erg) {
|
||||
console.log(erg)
|
||||
let t = $('#sofueHead').html();
|
||||
$('#sofueHead').html(t+(erg.wtermin).substr(0,10));
|
||||
$('#sofueName').html('für ' + erg.name);
|
||||
|
||||
}
|
||||
|
||||
// Change bei 'stattgefunden'
|
||||
$('#inputstatt').change(function() {
|
||||
let s = $('input[name="statt"]:checked').val();
|
||||
console.log(s);
|
||||
if(s == "nein") {
|
||||
showAbsage();
|
||||
sendobject.stattgefunden=false;
|
||||
} else if (s=='ja') {
|
||||
showBesucher();
|
||||
sendobject.stattgefunden=true;
|
||||
}
|
||||
});
|
||||
|
||||
// der JA-Zweig
|
||||
function showBesucher() {
|
||||
$('#besucher').removeClass("hide");
|
||||
}
|
||||
|
||||
$('#beszahl').keydown(e=>{
|
||||
let keycode = e.keyCode || e.which;
|
||||
if(keycode == 13) {
|
||||
besucher = $('#beszahl').val();
|
||||
if($.isNumeric(besucher)) {
|
||||
console.log(besucher);
|
||||
sendobject.anzahl = besucher;
|
||||
showSpende();
|
||||
} else {
|
||||
alert("Nur Ziffern eingeben");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$('#btnOK0').click(()=>{
|
||||
besucher = $('#beszahl').val();
|
||||
if($.isNumeric(besucher)) {
|
||||
console.log(besucher);
|
||||
sendobject.anzahl = besucher;
|
||||
showSpende();
|
||||
} else {
|
||||
alert("Nur Ziffern eingeben");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function showSpende() {
|
||||
$('#diespende').removeClass("hide");
|
||||
}
|
||||
|
||||
$('#inputspend').change(function() {
|
||||
let spende=0;
|
||||
let s = $('input[name="spend"]:checked').val();
|
||||
console.log(s);
|
||||
if(s == "bar") {
|
||||
spende=1;
|
||||
showBarspende();
|
||||
} else {
|
||||
showRemark();
|
||||
if (s=='ueberweis') {
|
||||
spende=2;
|
||||
} else if (s=='kasse') {
|
||||
spende=3;
|
||||
}
|
||||
}
|
||||
sendobject.spende = spende;
|
||||
});
|
||||
|
||||
function showBarspende() {
|
||||
$('#barspend').removeClass("hide");
|
||||
}
|
||||
|
||||
$('#barsp').keydown(e=>{
|
||||
let keycode = e.keyCode || e.which;
|
||||
if(keycode == 13) {
|
||||
let barsp = $('#barsp').val();
|
||||
if($.isNumeric(barsp)) {
|
||||
console.log(barsp);
|
||||
sendobject.betrag = barsp;
|
||||
showRemark();
|
||||
} else {
|
||||
alert("Nur Ziffern eingeben");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$('#btnOK1').click(()=>{
|
||||
let barsp = $('#barsp').val();
|
||||
if($.isNumeric(barsp)) {
|
||||
console.log(barsp);
|
||||
sendobject.betrag = barsp;
|
||||
showRemark();
|
||||
} else {
|
||||
alert("Nur Ziffern eingeben");
|
||||
}
|
||||
});
|
||||
|
||||
$('#btnOK2').click(()=>{
|
||||
sendobject.rem = $('#remark').val();
|
||||
showFertig();
|
||||
});
|
||||
|
||||
function showRemark() {
|
||||
let p = $('#btnOK2').offset();
|
||||
$('#btnOK2').offset({top: p.top-10, left: p.left});
|
||||
$('#remarks').removeClass('hide');
|
||||
}
|
||||
|
||||
function showFertig() {
|
||||
$('#fertig').removeClass("hide");
|
||||
$('#btnsend').removeClass("hide");
|
||||
}
|
||||
|
||||
// der NEIN-Zweig
|
||||
function showAbsage() {
|
||||
$('input[name="nostatt"]').prop('checked',"");
|
||||
$('#inputabs').removeClass("hide");
|
||||
}
|
||||
|
||||
// Change bei 'nicht stattt'
|
||||
$('#inputabs').change(function() {
|
||||
let s = $('input[name="nostatt"]:checked').val();
|
||||
console.log(s);
|
||||
if(s == "abgesagt") {
|
||||
showFertig();
|
||||
} else if (s == "verschoben") {
|
||||
showVerschoben();
|
||||
}
|
||||
});
|
||||
|
||||
function showVerschoben() {
|
||||
$('#verschbn').removeClass("hide");
|
||||
|
||||
|
||||
// Picker für den neuen Termin
|
||||
$('#newtermin').datetimepicker( // Initialisierung des datetimepickers
|
||||
{
|
||||
addSliderAccess: true,
|
||||
sliderAccessArgs: { touchonly: false },
|
||||
showOn: 'focus' , // onFocus wird der Picker angezeigt
|
||||
timeFormat: 'HH:mm',
|
||||
showMinute: true,
|
||||
stepMinute: 15,
|
||||
// showSecond: false,
|
||||
// showMillisec: false,
|
||||
// showMicrosec: false,
|
||||
// showTimezone: false,
|
||||
hourMin: 13,
|
||||
dateFormat: "yy-mm-dd", // angezeigt wird in diesem Format
|
||||
minDate: "+1d", // min Datum: morgen
|
||||
closeText: "OK", // erst wenn OK geklickt wird, dann gehts weiter
|
||||
timeText: "Uhrzeit", // Texte dazu
|
||||
hourText: "Stunde",
|
||||
onClose: function (dateText, inst) { // was passiert, wenn OK gedrückt wird:
|
||||
if(dateText != "") {
|
||||
var dt = moment(dateText).format('YYYY-MM-DD HH:mm');
|
||||
$("#newtermin").html(dt);
|
||||
sendobject.newtermin=dt;
|
||||
showFertig();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Datepicker auf Deutsch einstellen
|
||||
$.datepicker.regional['de'] = {
|
||||
closeText: '',
|
||||
prevText: '<',
|
||||
nextText: '>',
|
||||
currentText: '',
|
||||
monthNames: ['Januar','Februar','März','April','Mai','Juni',
|
||||
'Juli','August','September','Oktober','November','Dezember'],
|
||||
monthNamesShort: ['Jan','Feb','Mar','Apr','Mai','Jun',
|
||||
'Jul','Aug','Sep','Okt','Nov','Dez'],
|
||||
dayNames: ['Sonntag','Montag','Dienstag','Mittwoch','Donnerstag','Freitag','Samstag'],
|
||||
dayNamesShort: ['SON','MON','DIE','MIT','DON','FRE','SAM'],
|
||||
dayNamesMin: ['So','Mo','Di','Mi','Do','Fr','Sa'],
|
||||
weekHeader: 'KW',
|
||||
dateFormat: 'yyyy-mm-dd',
|
||||
firstDay: 1,
|
||||
isRTL: false,
|
||||
showMonthAfterYear: false,
|
||||
yearSuffix: ''
|
||||
};
|
||||
$.datepicker.setDefaults($.datepicker.regional['de']); // diese Einstellung nun übernehmen
|
||||
|
||||
// Buttons
|
||||
$('#btncancel').click(()=>location.reload());
|
||||
|
||||
$('#btnsend').click(()=>{
|
||||
console.log("Sende an Datenbank:");
|
||||
console.log(sendobject);
|
||||
clearAll(0);
|
||||
let cmd = {
|
||||
stattgefunden: sendobject.stattgefunden ? 1 : 0,
|
||||
id: url_id,
|
||||
cmd: 'UPDATEAFTER'
|
||||
};
|
||||
if(sendobject.stattgefunden) { // der Termin hat stattgefunden
|
||||
cmd.besucher = sendobject.anzahl;
|
||||
cmd.remark = sendobject.rem;
|
||||
cmd.bezahlt = (function() {
|
||||
console.log("sendobjc:", sendobject);
|
||||
if( sendobject.spende == 1 ) {
|
||||
return `bar Kasse (€${sendobject.betrag})`;
|
||||
} else if (sendobject.spende == 2) {
|
||||
return 'Überweisung';
|
||||
} else if (sendobject.spende == 3) {
|
||||
return 'Spendenkasse';
|
||||
} else return 'Keine';
|
||||
})()
|
||||
} else { // der Termin hat NICHT stattgefunden
|
||||
cmd.besucher = sendobject.anzahl;
|
||||
cmd.remark = '';
|
||||
cmd.bezahlt = 'Keine';
|
||||
if(sendobject.newtermin != '1900-01-01') {
|
||||
cmd.wtermin = sendobject.newtermin; // er wurde verlegt, also gibts einen neuen Wunschtermin
|
||||
} else {
|
||||
cmd.status = 3; // nicht stattgefunden -> abgesagt
|
||||
}
|
||||
}
|
||||
console.log(cmd);
|
||||
if(url_id != null) {
|
||||
doAjaxCall_arr(ajaxURL,cmd,showajaxerg);
|
||||
}
|
||||
});
|
||||
|
||||
function clearAll(was) {
|
||||
$('#tosend').addClass('hide');
|
||||
$('#inputstatt').addClass('hide');
|
||||
$('#fertig').addClass('hide');
|
||||
$('#verschbn').addClass('hide');
|
||||
$('#inputabs').addClass('hide');
|
||||
$('#besucher').addClass('hide');
|
||||
$('#diespende').addClass('hide');
|
||||
$('#barspend').addClass('hide');
|
||||
$('#remarks').addClass('hide');
|
||||
}
|
||||
|
||||
|
||||
function showajaxerg(erg) {
|
||||
console.log("AjaxErg: ", erg);
|
||||
$('#beendet').removeClass('hide');
|
||||
}
|
||||
|
||||
// Klick auf den Anleitungs-Button
|
||||
$('#btnMan').click(function() {
|
||||
$("#anleitung").dialog('open');
|
||||
});
|
||||
|
||||
|
||||
// 3. Dialog für die Anleitung
|
||||
// Dieser hat KEINEN Button (wird über das Schließkreuz beendet) und
|
||||
// eine etwas kleinere Schrift
|
||||
$("#anleitung").dialog({
|
||||
autoOpen: false,
|
||||
width: 400,
|
||||
modal: true,
|
||||
position: {my: 'top', at: 'top', of: window },
|
||||
title: 'Anleitung',
|
||||
open:
|
||||
function() {
|
||||
$(this).load('beoanswer.html');
|
||||
},
|
||||
buttons: [
|
||||
{
|
||||
text: "Abbrechen",
|
||||
click : function() {
|
||||
$(this).dialog("close");
|
||||
},
|
||||
width: 150,
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
|
||||
});
|
||||
@@ -1,103 +0,0 @@
|
||||
/*
|
||||
* jQuery UI Slider Access
|
||||
* By: Trent Richardson [http://trentrichardson.com]
|
||||
* Version 0.2
|
||||
* Last Modified: 12/12/2011
|
||||
*
|
||||
* Copyright 2011 Trent Richardson
|
||||
* Dual licensed under the MIT and GPL licenses.
|
||||
* http://trentrichardson.com/Impromptu/GPL-LICENSE.txt
|
||||
* http://trentrichardson.com/Impromptu/MIT-LICENSE.txt
|
||||
*
|
||||
*/
|
||||
(function ($) {
|
||||
|
||||
$.fn.extend({
|
||||
sliderAccess: function (options) {
|
||||
options = options || {};
|
||||
options.touchonly = options.touchonly !== undefined ? options.touchonly : true; // by default only show it if touch device
|
||||
|
||||
if (options.touchonly === true && !("ontouchend" in document))
|
||||
return $(this);
|
||||
|
||||
return $(this).each(function (i, obj) {
|
||||
var $t = $(this),
|
||||
o = $.extend({}, {
|
||||
where: 'after',
|
||||
step: $t.slider('option', 'step'),
|
||||
upIcon: 'ui-icon-plus',
|
||||
downIcon: 'ui-icon-minus',
|
||||
text: false,
|
||||
upText: '+',
|
||||
downText: '-',
|
||||
buttonset: true,
|
||||
buttonsetTag: 'span',
|
||||
speed: 150
|
||||
}, options),
|
||||
$buttons = $('<' + o.buttonsetTag + ' class="ui-slider-access">' +
|
||||
'<button data-icon="' + o.downIcon + '" data-step="-' + o.step + '">' + o.downText + '</button>' +
|
||||
'<button data-icon="' + o.upIcon + '" data-step="' + o.step + '">' + o.upText + '</button>' +
|
||||
'</' + o.buttonsetTag + '>');
|
||||
|
||||
$buttons.children('button').each(function (j, jobj) {
|
||||
var $jt = $(this),
|
||||
timeout = null,
|
||||
increment = function($jt, $t, e) {
|
||||
var step = $jt.data('step'),
|
||||
curr = $t.slider('value'),
|
||||
newval = curr += step * 1,
|
||||
minval = $t.slider('option', 'min'),
|
||||
maxval = $t.slider('option', 'max');
|
||||
e.preventDefault();
|
||||
if (newval < minval || newval > maxval)
|
||||
return;
|
||||
$t.slider('value', newval);
|
||||
$t.slider("option", "slide").call($t, null, { value: newval });
|
||||
};
|
||||
|
||||
$jt.button({
|
||||
text: o.text,
|
||||
icons: { primary: $jt.data('icon') }
|
||||
})
|
||||
.bind('touchstart mousedown', function (e) {
|
||||
increment($jt, $t, e);
|
||||
timeout = setInterval(function () {
|
||||
increment($jt, $t, e);
|
||||
}, o.speed);
|
||||
});
|
||||
|
||||
$(document).bind('touchend mouseup', function (e) {
|
||||
clearInterval(timeout);
|
||||
return e.type == 'touchend';
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// before or after
|
||||
$t[o.where]($buttons);
|
||||
|
||||
if (o.buttonset) {
|
||||
$buttons.removeClass('ui-corner-right').removeClass('ui-corner-left').buttonset();
|
||||
$buttons.eq(0).addClass('ui-corner-left');
|
||||
$buttons.eq(1).addClass('ui-corner-right');
|
||||
}
|
||||
|
||||
// adjust the width so we don't break the original layout
|
||||
var bOuterWidth = $buttons.css({
|
||||
marginLeft: (o.where == 'after' ? 10 : 0),
|
||||
marginRight: (o.where == 'before' ? 10 : 0)
|
||||
}).outerWidth(true) + 5;
|
||||
var tOuterWidth = $t.outerWidth(true);
|
||||
|
||||
// support "always" hide the slider
|
||||
if (o.hideSlider == 'always' || (o.hideSlider == 'touch' && ("ontouchend" in document))) {
|
||||
$t.css('display', 'none');
|
||||
}
|
||||
else {
|
||||
$t.css('display', 'inline-block').width(tOuterWidth - bOuterWidth);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
})(jQuery);
|
||||
@@ -1,17 +0,0 @@
|
||||
// VersiosNummern und -Geschichte
|
||||
|
||||
var VERSION="1.2";
|
||||
var VDATE="2024-11-01";
|
||||
|
||||
/* History
|
||||
|
||||
Rev. Datum Entwickler
|
||||
|
||||
1.2 2024-11-01 rxf
|
||||
- Nur noch ID zur identifizierung der Führung
|
||||
- Name des Besuchers mit anzeigen
|
||||
1.1 2018-12-11 rxf
|
||||
- kleinere Anpassungen
|
||||
1.0 2018-09-27 rxf
|
||||
- Los gehts
|
||||
*/
|
||||
2900
sternwarte/beoanswer/package-lock.json
generated
Normal file
2900
sternwarte/beoanswer/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
30
sternwarte/beoanswer/package.json
Normal file
30
sternwarte/beoanswer/package.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "beoanswer_react",
|
||||
"private": true,
|
||||
"version": "1.0.3",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"build:prod": "vite build --mode production",
|
||||
"preview": "vite preview",
|
||||
"preview:prod": "vite preview --mode production",
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.36.0",
|
||||
"@types/react": "^19.1.16",
|
||||
"@types/react-dom": "^19.1.9",
|
||||
"@vitejs/plugin-react": "^5.0.4",
|
||||
"eslint": "^9.36.0",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.22",
|
||||
"globals": "^16.4.0",
|
||||
"terser": "^5.44.0",
|
||||
"vite": "^7.1.7"
|
||||
}
|
||||
}
|
||||
147
sternwarte/beoanswer/public/anleitung.html
Normal file
147
sternwarte/beoanswer/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>
|
||||
1
sternwarte/beoanswer/public/vite.svg
Normal file
1
sternwarte/beoanswer/public/vite.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
398
sternwarte/beoanswer/sofueDB.php
Executable file
398
sternwarte/beoanswer/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);
|
||||
|
||||
?>
|
||||
|
||||
122
sternwarte/beoanswer/src/App.css
Normal file
122
sternwarte/beoanswer/src/App.css
Normal file
@@ -0,0 +1,122 @@
|
||||
#root {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
max-width: 600px;
|
||||
margin: auto;
|
||||
border: 1px solid blue;
|
||||
background: lightgray;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: lightskyblue;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
.nachbearbeitung {
|
||||
background-color: yellow;
|
||||
height: 50px;
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
section {
|
||||
border-bottom : 1px solid rgb(187, 185, 185);
|
||||
text-align: left;
|
||||
margin: 0 auto 20px auto;
|
||||
padding: 0 0em 1em 2em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.infeldsm {
|
||||
width: 12em;
|
||||
}
|
||||
|
||||
.fstdiv {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.fsLabel {
|
||||
margin-right: 20px;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.okbutton {
|
||||
margin-left: 2em;
|
||||
}
|
||||
|
||||
.radiogroup {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.selspende {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.spendeok {
|
||||
margin-left: 2em;
|
||||
}
|
||||
.bemerkdiv {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.beminfeld {
|
||||
width: 14em;
|
||||
}
|
||||
|
||||
.lastline {
|
||||
border-top: 1px solid blue;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: auto;
|
||||
padding: 15px;
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
.lastbuttons {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-content: space-around;
|
||||
flex-wrap: wrap;
|
||||
width: 80%;
|
||||
margin: auto;
|
||||
margin-bottom: 20px;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.umbruch {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.btnsend {
|
||||
background-color: blue;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btnsend :hover {
|
||||
border-color: #646cff;
|
||||
}
|
||||
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.umbruch {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.lastline {
|
||||
font-size: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
input[type="radio"] {
|
||||
margin-right: 10px;
|
||||
}
|
||||
332
sternwarte/beoanswer/src/App.jsx
Normal file
332
sternwarte/beoanswer/src/App.jsx
Normal file
@@ -0,0 +1,332 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { FormProvider, useFormData } from './FormContext'
|
||||
import packageJson from '../package.json'
|
||||
import './App.css'
|
||||
import FandStattVer from './components/FandStattVer.jsx'
|
||||
import BesucherBar from './components/BesucherBar.jsx'
|
||||
import Spende from './components/Spende.jsx'
|
||||
import LastLine from './components/LastLine.jsx'
|
||||
import Bemerkungen from './components/Bemerkungen.jsx'
|
||||
import LastButtons from './components/LastButtons.jsx'
|
||||
import Verschoben from './components/Verschoben.jsx'
|
||||
|
||||
|
||||
function AppContent() {
|
||||
// States für Backend-Daten
|
||||
const [datum, setDatum] = useState("")
|
||||
const [name, setName] = useState("")
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState(null)
|
||||
//const [mitsend, setMitsend] = useState(false)
|
||||
//const [mitback, setMitback] = useState(false)
|
||||
|
||||
const version = packageJson.version
|
||||
const vdate = new Date().toLocaleDateString('de-DE')
|
||||
|
||||
// States
|
||||
const [schritt, setSchritt] = useState(0)
|
||||
const [pfad, setPfad] = useState('')
|
||||
|
||||
// Hole formData aus dem Context
|
||||
const { formData, updateFormData } = 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 HTTP Basic Auth
|
||||
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}`
|
||||
}
|
||||
|
||||
console.log(formData)
|
||||
|
||||
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:
|
||||
const handleFandStattVerNext = (auswahl) => {
|
||||
auswahl && setPfad(auswahl)
|
||||
handleNext()
|
||||
}
|
||||
|
||||
const handleNext = () => {
|
||||
setSchritt((schritt) => schritt + 1)
|
||||
}
|
||||
|
||||
const handleBack = () => {
|
||||
if (schritt > 0) {
|
||||
const neuerSchritt = schritt - 1
|
||||
setSchritt(neuerSchritt)
|
||||
|
||||
// Entsprechende FormData-Felder zurücksetzen je nach Schritt und Pfad
|
||||
if (pfad === 'ja') {
|
||||
// JA-Pfad rückwärts
|
||||
if (schritt === 1) {
|
||||
// Von Besucher zurück zu ja/nein → Pfad löschen
|
||||
setPfad('')
|
||||
updateFormData('stattgefunden', '')
|
||||
} else if (schritt === 2) {
|
||||
// Von Spende zurück zu Besucher → Besucher löschen
|
||||
updateFormData('besucher', '')
|
||||
} else if (schritt === 3) {
|
||||
// Von Betrag/Bemerkungen zurück zu Spende → Spende löschen
|
||||
updateFormData('spendenArt', '')
|
||||
} else if (schritt === 4) {
|
||||
// Von Bemerkungen zurück → Betrag löschen (bei Bar-Spende)
|
||||
updateFormData('betrag', '')
|
||||
} else if (schritt === 5) {
|
||||
// Von Senden zurück → Bemerkungen löschen
|
||||
updateFormData('bemerkungen', '')
|
||||
}
|
||||
} else if (pfad === 'nein') {
|
||||
// NEIN-Pfad rückwärts
|
||||
if (schritt === 1) {
|
||||
// Von abgesagt/verschoben zurück zu ja/nein → Pfad löschen
|
||||
setPfad('')
|
||||
updateFormData('stattgefunden', '')
|
||||
} else if (schritt === 2) {
|
||||
// Von Datum/Senden zurück zu abgesagt/verschoben → abgesagt löschen
|
||||
updateFormData('abgesagt', '')
|
||||
} else if (schritt === 3) {
|
||||
// Von Senden zurück → neues Datum löschen (bei verschoben)
|
||||
updateFormData('neuesDatum', '')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const setBackButton = () => {
|
||||
// setMitback(true)
|
||||
}
|
||||
|
||||
// Welche Komponeneten werden angezeigt:
|
||||
const renderCoponents = () => {
|
||||
const components = []
|
||||
|
||||
// Schritt 0: ja/nein - Auswahl
|
||||
components.push(
|
||||
<FandStattVer key="fandstatt" left='ja' right='nein' title='Fand die Führung statt?'
|
||||
onNext={handleFandStattVerNext}
|
||||
setbackButton={setBackButton}
|
||||
iscompleted={schritt > 1} />
|
||||
)
|
||||
if (schritt === 0) {
|
||||
// Bei ja/nein Auswahl: Kein Zurück-Button, kein Senden-Button
|
||||
components.push(<LastButtons key='lastbutt' mitSend={false} mitBack={false} handleBack={handleBack}/>)
|
||||
return components
|
||||
}
|
||||
|
||||
// JA-Pfad:
|
||||
if (pfad === 'ja') {
|
||||
// Schritt 1: Besucher-Anzahl
|
||||
if (schritt >= 1) {
|
||||
components.push(<BesucherBar key='besucher' title='Besucher-Anzahl' euro='' onNext={handleNext} isCompleted={schritt > 1} />
|
||||
)
|
||||
}
|
||||
|
||||
// Schritt 2: Spende
|
||||
if (schritt >= 2) {
|
||||
components.push(<Spende key='spende' onNext={handleNext} isComplete={schritt > 2} />
|
||||
)
|
||||
}
|
||||
|
||||
// Schritt 3: Betrag der Spende (nur bei Bar-Spende)
|
||||
if ((schritt >= 3) && (formData.spendenArt === 'bar')) {
|
||||
components.push(<BesucherBar key='betrag' title='Höhe der Barspende' euro='€' onNext={handleNext} isCompleted={schritt > 3} />
|
||||
)
|
||||
}
|
||||
|
||||
// Schritt 4 (bei Bar-Spende) oder Schritt 3 (bei anderen Spenden): Bemerkungen
|
||||
const bemerkungsSchritt = (formData.spendenArt === 'bar') ? 4 : 3
|
||||
if (schritt >= bemerkungsSchritt) {
|
||||
components.push(<Bemerkungen key='bemerkungen' onNext={handleNext} isCompleted={schritt > bemerkungsSchritt} />
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
// NEIN - Pfad
|
||||
if (pfad === 'nein') {
|
||||
|
||||
// Schritt 1: abgesagt / verschoben
|
||||
if (schritt >= 1) {
|
||||
components.push(
|
||||
<FandStattVer key="abgesagt" left='abgesagt' right='verschoben' title='Die Führung wurde' radioName='abgesagt'
|
||||
onNext={handleNext}
|
||||
setbackButton={setBackButton}
|
||||
iscompleted={schritt > 1} />
|
||||
)
|
||||
}
|
||||
|
||||
// Schritt 2: Ende wenn abgesagt bzw. neues Datum bei verschoben
|
||||
if (schritt >= 2 && formData.abgesagt === 'verschoben') {
|
||||
components.push(<Verschoben key='verschoben' onNext={handleNext} isCompleted={schritt > 2} />
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Zurück-Button nur anzeigen wenn nicht bei ja/nein Auswahl
|
||||
const backVerfuegbar = schritt > 0
|
||||
|
||||
// LastButtons IMMER anzeigen, aber Senden-Button nur wenn bereit
|
||||
const sendenBereit = () => {
|
||||
if (pfad === 'ja') {
|
||||
// JA-Pfad: vollständig wenn Bemerkungen-Schritt ABGESCHLOSSEN ist
|
||||
const bemerkungsSchritt = (formData.spendenArt === 'bar') ? 4 : 3
|
||||
return schritt > bemerkungsSchritt // NACH dem Bemerkungsschritt, nicht beim Erreichen
|
||||
} else if (pfad === 'nein') {
|
||||
// NEIN-Pfad: vollständig wenn abgesagt ODER verschoben mit Datum
|
||||
if (formData.abgesagt === 'abgesagt') {
|
||||
return schritt >= 2 // Beim Erreichen von Schritt 2 (nach Auswahl abgesagt)
|
||||
} else if (formData.abgesagt === 'verschoben') {
|
||||
return schritt >= 3 && formData.neuesDatum // Beim Erreichen von Schritt 3 mit Datum
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// LastButtons immer anzeigen
|
||||
components.push(<LastButtons key='lastbutt' mitSend={sendenBereit()} mitBack={backVerfuegbar} handleBack={handleBack} />)
|
||||
|
||||
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 (
|
||||
<div className="wrapper">
|
||||
<div>
|
||||
<h2 className="topline">
|
||||
Sonderführung vom <br className="umbruch" />{datum}
|
||||
</h2>
|
||||
<h4>für {name}</h4>
|
||||
<h2 className="nachbearbeitung">Nachbearbeitung</h2>
|
||||
</div>
|
||||
{renderCoponents().map(component => component)}
|
||||
<LastLine version={version} vdate={vdate} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<FormProvider>
|
||||
<AppContent />
|
||||
</FormProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
57
sternwarte/beoanswer/src/FormContext.jsx
Normal file
57
sternwarte/beoanswer/src/FormContext.jsx
Normal file
@@ -0,0 +1,57 @@
|
||||
// ========================================
|
||||
// FormContext.jsx - Globaler State für alle Formulardaten
|
||||
// ========================================
|
||||
import { createContext, useContext, useState } from 'react'
|
||||
|
||||
const FormContext = createContext()
|
||||
|
||||
export function FormProvider({ children }) {
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
stattgefunden: '',
|
||||
besucher: '', // war: besucherAnzahl
|
||||
spendenArt: '',
|
||||
betrag: '', // war: barspende
|
||||
bemerkungen: '',
|
||||
neuesDatum: '', // war: neuertermin
|
||||
abgesagt: '', // für abgesagt/verschoben
|
||||
// Weitere Felder können hier hinzugefügt werden
|
||||
})
|
||||
|
||||
const updateFormData = (field, value) => {
|
||||
setFormData(prev => {
|
||||
const newData = {
|
||||
...prev,
|
||||
[field]: value
|
||||
}
|
||||
return newData
|
||||
})
|
||||
}
|
||||
|
||||
const resetFormData = () => {
|
||||
|
||||
setFormData({
|
||||
stattgefunden: '',
|
||||
besucher: '',
|
||||
spendenArt: '',
|
||||
betrag: '',
|
||||
bemerkungen: '',
|
||||
neuesDatum: '',
|
||||
abgesagt: ''
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<FormContext.Provider value={{ formData, updateFormData, resetFormData }}>
|
||||
{children}
|
||||
</FormContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useFormData() {
|
||||
const context = useContext(FormContext)
|
||||
if (!context) {
|
||||
throw new Error('useFormData muss innerhalb von FormProvider verwendet werden. Stelle sicher, dass deine Komponente von <FormProvider> umschlossen ist.')
|
||||
}
|
||||
return context
|
||||
}
|
||||
1
sternwarte/beoanswer/src/assets/react.svg
Normal file
1
sternwarte/beoanswer/src/assets/react.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
44
sternwarte/beoanswer/src/components/Bemerkungen.jsx
Normal file
44
sternwarte/beoanswer/src/components/Bemerkungen.jsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import { useState } from 'react'
|
||||
import { useFormData } from '../FormContext'
|
||||
|
||||
export default function Bemerkungen({ onNext, isCompleted }) {
|
||||
|
||||
const { formData, updateFormData } = useFormData()
|
||||
const [wert, setWert] = useState(formData.bemerkungen || '')
|
||||
|
||||
|
||||
const handleOK = () => {
|
||||
updateFormData('bemerkungen', wert)
|
||||
onNext()
|
||||
}
|
||||
|
||||
const handleKeyDown = (e) => {
|
||||
// Ctrl+Enter oder Cmd+Enter zum Speichern
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
|
||||
handleOK()
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<section id="bemerkungen">
|
||||
<h3>Bemerkungen (optional):</h3>
|
||||
<div className="bemerkdiv">
|
||||
<textarea
|
||||
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>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
57
sternwarte/beoanswer/src/components/BesucherBar.jsx
Normal file
57
sternwarte/beoanswer/src/components/BesucherBar.jsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import { useState } from 'react'
|
||||
import { useFormData } from '../FormContext'
|
||||
import Modal from './Modal'
|
||||
|
||||
export default function BesucherBar({ title, euro, onNext, isCompleted }) {
|
||||
|
||||
const { formData, updateFormData } = useFormData()
|
||||
|
||||
// Bestimme Feldname basierend auf dem title
|
||||
const fieldName = title.includes('Barspende') ? 'betrag' : 'besucher'
|
||||
|
||||
const [wert, setWert] = useState(formData[fieldName] || '')
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
|
||||
const handleOK = () => {
|
||||
if (wert) {
|
||||
updateFormData(fieldName, wert)
|
||||
onNext()
|
||||
} else {
|
||||
setShowModal(true)
|
||||
}
|
||||
}
|
||||
|
||||
const handleKeyDown = (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
handleOK()
|
||||
}
|
||||
}
|
||||
|
||||
const closeModal = () => {
|
||||
setShowModal(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<section id="besucherbar">
|
||||
<h3>{title}:</h3>
|
||||
<div className="besadiv">
|
||||
<input type='number' value={wert} onChange={(e) => setWert(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder={euro ? 'Betrag in Euro' : 'Anzahl'} disabled={isCompleted}
|
||||
/>
|
||||
{euro}
|
||||
<button className="okbutton" onClick={handleOK}>OK</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Modal
|
||||
isOpen={showModal}
|
||||
onClose={closeModal}
|
||||
title="Eingabe erforderlich"
|
||||
>
|
||||
<p>Bitte einen Wert eingeben</p>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
65
sternwarte/beoanswer/src/components/ConfirmModal.jsx
Normal file
65
sternwarte/beoanswer/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>
|
||||
)
|
||||
}
|
||||
41
sternwarte/beoanswer/src/components/FandStattVer.jsx
Normal file
41
sternwarte/beoanswer/src/components/FandStattVer.jsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import { useState } from 'react'
|
||||
import { useFormData } from '../FormContext'
|
||||
import Modal from './Modal'
|
||||
|
||||
export default function FandStattVer({left, right, title, onNext, radioName = "fst", setbackButton}) {
|
||||
const { formData, updateFormData } = useFormData()
|
||||
|
||||
// Bestimme das Feld basierend auf radioName
|
||||
const fieldName = radioName === 'abgesagt' ? 'abgesagt' : 'stattgefunden'
|
||||
const [auswahl, setAuswahl] = useState(formData[fieldName] || '')
|
||||
|
||||
const handleRadioChange = (e) => {
|
||||
const value = e.target.value
|
||||
updateFormData(fieldName, value)
|
||||
setbackButton(true)
|
||||
if(radioName !== 'abgesagt') {
|
||||
setAuswahl(value)
|
||||
onNext(value)
|
||||
} else {
|
||||
onNext()
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<section>
|
||||
<h3>{title}</h3>
|
||||
<div className="fstdiv">
|
||||
<label className="fsLabel">
|
||||
<input type="radio" name={radioName} value={left} checked={auswahl === left}
|
||||
onChange={handleRadioChange} />
|
||||
{left}
|
||||
</label>
|
||||
<label className="fsLabel">
|
||||
<input type="radio" name={radioName} value={right} checked={auswahl === right}
|
||||
onChange={handleRadioChange} />
|
||||
{right}
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
14
sternwarte/beoanswer/src/components/LastButtons.css
Normal file
14
sternwarte/beoanswer/src/components/LastButtons.css
Normal file
@@ -0,0 +1,14 @@
|
||||
.modal-content.custom-modal {
|
||||
width: 95vw; /* Nearly full window width */
|
||||
max-width: none; /* Remove base max-width constraint */
|
||||
height: 85vh; /* Tall modal */
|
||||
max-height: 85vh; /* Cap at viewport height */
|
||||
display: flex; /* Allow header/body/footer layout */
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.modal-content.custom-modal .modal-body {
|
||||
flex: 1; /* Fill remaining space */
|
||||
overflow: auto; /* Scroll body when content is taller than container */
|
||||
text-align: left; /* Better for long instructions */
|
||||
}
|
||||
388
sternwarte/beoanswer/src/components/LastButtons.jsx
Normal file
388
sternwarte/beoanswer/src/components/LastButtons.jsx
Normal file
@@ -0,0 +1,388 @@
|
||||
import { useState } from 'react'
|
||||
import { useFormData } from '../FormContext'
|
||||
import Modal from './Modal'
|
||||
import ConfirmModal from './ConfirmModal'
|
||||
import './LastButtons.css';
|
||||
|
||||
export default function LastButtons({ mitSend, mitBack, handleBack}) {
|
||||
|
||||
const { formData, resetFormData } = useFormData()
|
||||
const [isSending, setIsSending] = useState(false)
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
const [modalMessage, setModalMessage] = useState('')
|
||||
const [modalType, setModalType] = useState('error') // 'error' oder 'success'
|
||||
const [isModalHtml, setIsModalHtml] = useState(false)
|
||||
const [showConfirmModal, setShowConfirmModal] = useState(false)
|
||||
const [isSuccessModal, setIsSuccessModal] = useState(false)
|
||||
const [isWideModal, setIsWideModal] = useState(false)
|
||||
|
||||
|
||||
const handleSenden = async () => {
|
||||
console.log("Alle Formulardaten: ", formData)
|
||||
|
||||
setIsSending(true)
|
||||
|
||||
try {
|
||||
// API URL aus Environment Variable
|
||||
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.')
|
||||
}
|
||||
|
||||
// JSON-Objekt statt FormData erstellen
|
||||
const backendData = {
|
||||
cmd: 'UPDATEAFTER',
|
||||
id: id
|
||||
}
|
||||
|
||||
// Formulardaten zu Backend-Feldern mappen
|
||||
// Basis-Status
|
||||
if (formData.stattgefunden === 'ja') {
|
||||
backendData.stattgefunden = '1'
|
||||
|
||||
// Spenden-Informationen
|
||||
if (formData.spendenArt) {
|
||||
switch (formData.spendenArt) {
|
||||
case 'bar':
|
||||
backendData.bezahlt = `Kasse ${formData.betrag}€`
|
||||
break
|
||||
case 'ueber':
|
||||
backendData.bezahlt = 'Überweisung'
|
||||
break
|
||||
case 'kasse':
|
||||
backendData.bezahlt = 'Spendenkässle'
|
||||
break
|
||||
case 'keine':
|
||||
backendData.bezahlt = 'keine'
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
} else if (formData.stattgefunden === 'nein') {
|
||||
backendData.stattgefunden = '0'
|
||||
backendData.bezahlt = 'keine'
|
||||
|
||||
// Grund für Ausfall
|
||||
if (formData.abgesagt === 'abgesagt') {
|
||||
backendData.status = 3
|
||||
} else if (formData.abgesagt === 'verschoben') {
|
||||
backendData.wtermin = formData.neuesDatum || '1900-01-01 00:00:00'
|
||||
}
|
||||
}
|
||||
|
||||
// Bemerkungen
|
||||
backendData.remark = formData.bemerkungen || ''
|
||||
// Besucher
|
||||
backendData.besucher = formData.besucher || '0'
|
||||
|
||||
// Debug: JSON-Daten loggen
|
||||
console.log("=== JSON DATA DEBUG ===")
|
||||
console.log("Original formData aus Context:", formData)
|
||||
console.log("URL ID:", id)
|
||||
console.log("Backend JSON Daten:", JSON.stringify(backendData, null, 2))
|
||||
console.log("========================")
|
||||
|
||||
// HTTP Headers mit Basic Authentication und Content-Type
|
||||
const headers = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
if (username && password) {
|
||||
const credentials = btoa(`${username}:${password}`)
|
||||
headers['Authorization'] = `Basic ${credentials}`
|
||||
}
|
||||
|
||||
// Backend-Aufruf mit JSON
|
||||
const response = await fetch(APIURL, {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
body: JSON.stringify(backendData)
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Server-Fehler: ${response.status}`)
|
||||
}
|
||||
|
||||
// Backend Response auslesen
|
||||
const responseText = await response.text()
|
||||
console.log('Backend Response (raw):', responseText)
|
||||
|
||||
let result
|
||||
try {
|
||||
// Versuche JSON zu parsen
|
||||
result = JSON.parse(responseText)
|
||||
console.log('Backend Response (parsed):', result)
|
||||
} catch {
|
||||
// Falls kein JSON, behandle als einfachen Text
|
||||
console.log('Backend Response ist kein JSON, behandle als Text')
|
||||
result = { success: responseText.trim() === 'true', raw: responseText }
|
||||
}
|
||||
|
||||
// Erfolg prüfen - sowohl JSON als auch Text-Format unterstützen
|
||||
const isSuccess = result.success === true ||
|
||||
result.success === 'true' ||
|
||||
responseText.trim() === 'true'
|
||||
|
||||
if (isSuccess) {
|
||||
// E-Mail-Benachrichtigung senden (nicht blockierend)
|
||||
sendEmailNotification(id, formData, backendData, APIURL, headers)
|
||||
|
||||
setModalType('success')
|
||||
setModalMessage('✅ Daten erfolgreich gespeichert!')
|
||||
setIsSuccessModal(true)
|
||||
setIsWideModal(false)
|
||||
setShowModal(true)
|
||||
|
||||
} else {
|
||||
throw new Error(result.message || result.error || 'Unbekannter Fehler beim Speichern')
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Speichern:', error)
|
||||
setModalType('error')
|
||||
setModalMessage(`❌ Fehler beim Speichern: ${error.message}`)
|
||||
setIsSuccessModal(false)
|
||||
setIsWideModal(false)
|
||||
setShowModal(true)
|
||||
} finally {
|
||||
setIsSending(false)
|
||||
}
|
||||
}
|
||||
|
||||
// E-Mail-Benachrichtigung senden (asynchron, nicht blockierend)
|
||||
const sendEmailNotification = async (id, formData, backendData, apiUrl, headers) => {
|
||||
try {
|
||||
// Details zur Sonderführung laden (für BEO und Besucher-Name)
|
||||
let beoName = 'unbekannt'
|
||||
let visitorName = 'unbekannt'
|
||||
let termin = ''
|
||||
|
||||
try {
|
||||
const detailsResp = await fetch(apiUrl, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({ cmd: 'GET_ONE', id })
|
||||
})
|
||||
if (detailsResp.ok) {
|
||||
const details = await detailsResp.json()
|
||||
// Backend kann Objekt oder Array liefern; robust extrahieren
|
||||
const d = Array.isArray(details) ? (details[0] || {}) : (details || {})
|
||||
// Felder: mitarbeiter (BEO), name/vorname (Besucher), wtermin (Termin)
|
||||
beoName = d.mitarbeiter || beoName
|
||||
const vn = d.vorname || ''
|
||||
const nn = d.name || ''
|
||||
visitorName = (vn + ' ' + nn).trim() || visitorName
|
||||
termin = d.wtermin || ''
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Konnte Details für E-Mail nicht laden:', e)
|
||||
}
|
||||
// E-Mail-Betreff und Inhalt erstellen
|
||||
const stattgefunden = formData.stattgefunden === 'ja' ? 'Ja' : 'Nein'
|
||||
const besucher = formData.besucher || '0'
|
||||
const spenden = backendData.bezahlt || 'keine'
|
||||
const bemerkungen = formData.bemerkungen || 'keine'
|
||||
|
||||
const subject = `Sonderführung vom ${termin} - Nachbearbeitung`
|
||||
const body = `Nachbearbeitung für Sonderführung ID ${id}
|
||||
|
||||
BEO: ${beoName}
|
||||
Besucher: ${visitorName}
|
||||
Termin: ${termin}
|
||||
|
||||
Stattgefunden: ${stattgefunden}
|
||||
Anzahl Besucher: ${besucher}
|
||||
Spenden: ${spenden}
|
||||
Bemerkungen: ${bemerkungen}
|
||||
|
||||
Status: ${formData.stattgefunden === 'ja' ? 'Durchgeführt' :
|
||||
formData.abgesagt === 'abgesagt' ? 'Abgesagt' :
|
||||
formData.abgesagt === 'verschoben' ? `Verschoben auf ${formData.neuesDatum}` :
|
||||
'Unbekannt'}
|
||||
|
||||
Diese E-Mail wurde automatisch vom Nachbearbeitungs-System generiert.
|
||||
`
|
||||
|
||||
// E-Mail-Command an Backend senden
|
||||
const emailData = {
|
||||
cmd: 'SEND_CONFIRMATION',
|
||||
to: 'rxf@gmx.de',
|
||||
subject: subject,
|
||||
body: body
|
||||
}
|
||||
|
||||
const emailResponse = await fetch(apiUrl, {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
body: JSON.stringify(emailData)
|
||||
})
|
||||
|
||||
if (emailResponse.ok) {
|
||||
console.log('✅ E-Mail-Benachrichtigung erfolgreich gesendet')
|
||||
} else {
|
||||
console.warn('⚠️ E-Mail-Benachrichtigung konnte nicht gesendet werden:', emailResponse.status)
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
// Fehler beim E-Mail-Versand nicht kritisch - nur loggen
|
||||
console.warn('⚠️ E-Mail-Benachrichtigung fehlgeschlagen:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleAbbruch = () => {
|
||||
setShowConfirmModal(true)
|
||||
}
|
||||
|
||||
const confirmAbbruch = () => {
|
||||
setShowConfirmModal(false)
|
||||
|
||||
// Versuche das Browser-Fenster zu schließen (wie beim Speichern)
|
||||
window.close()
|
||||
|
||||
// Fallback: Falls window.close() nicht funktioniert
|
||||
setTimeout(() => {
|
||||
if (!window.closed) {
|
||||
// Wenn das Fenster nicht geschlossen werden kann, zur vorherigen Seite
|
||||
window.location.reload()
|
||||
}
|
||||
}, 100)
|
||||
}
|
||||
|
||||
const cancelAbbruch = () => {
|
||||
setShowConfirmModal(false)
|
||||
}
|
||||
|
||||
const handleAnleitung = async () => {
|
||||
try {
|
||||
// Anleitung soll im großen Modal erscheinen
|
||||
setIsWideModal(true)
|
||||
// Respect Vite base path in production (vite.config.js base: '/beoanswer/')
|
||||
const base = (import.meta.env && import.meta.env.BASE_URL) ? import.meta.env.BASE_URL : '/'
|
||||
const normalizedBase = base.endsWith('/') ? base : base + '/'
|
||||
const url = `${normalizedBase}Anleitung.html?v=${Date.now()}` // cache-bust
|
||||
|
||||
// Fetch Anleitung.html relative to app base
|
||||
let response = await fetch(url)
|
||||
|
||||
// Fallback: try relative path without base if first attempt failed
|
||||
if (!response.ok) {
|
||||
const fallbackUrl = `Anleitung.html?v=${Date.now()}`
|
||||
response = await fetch(fallbackUrl)
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Fehler beim Laden der Anleitung (${response.status}): ${response.url}`)
|
||||
}
|
||||
|
||||
const anleitungContent = await response.text()
|
||||
|
||||
// Display the content in the modal
|
||||
setModalType('info')
|
||||
setModalMessage(anleitungContent)
|
||||
setIsModalHtml(true)
|
||||
setShowModal(true)
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der Anleitung:', error)
|
||||
setModalType('error')
|
||||
setModalMessage('❌ Anleitung konnte nicht geladen werden.')
|
||||
setIsModalHtml(false)
|
||||
setIsWideModal(false)
|
||||
setShowModal(true)
|
||||
}
|
||||
}
|
||||
|
||||
const closeModal = () => {
|
||||
if (isSuccessModal) {
|
||||
// Bei erfolgreichem Speichern: Browser-Fenster schließen
|
||||
console.log('Schließe Browser-Fenster nach erfolgreichem Speichern...')
|
||||
|
||||
// Formular zurücksetzen (für den Fall, dass das Schließen nicht funktioniert)
|
||||
resetFormData()
|
||||
|
||||
// Browser-Fenster schließen
|
||||
window.close()
|
||||
|
||||
// Fallback: Falls window.close() nicht funktioniert (z.B. bei direkt aufgerufenen URLs)
|
||||
// Nach 100ms prüfen, ob das Fenster noch offen ist
|
||||
setTimeout(() => {
|
||||
// Wenn das Fenster noch offen ist, zur vorherigen Seite oder Neustart
|
||||
if (!window.closed) {
|
||||
console.log('Fenster konnte nicht geschlossen werden, führe Neustart durch...')
|
||||
window.location.reload()
|
||||
}
|
||||
}, 100)
|
||||
|
||||
} else {
|
||||
// Normales Modal schließen
|
||||
setShowModal(false)
|
||||
setModalMessage('')
|
||||
setIsModalHtml(false)
|
||||
setIsSuccessModal(false)
|
||||
setIsWideModal(false)
|
||||
}
|
||||
}
|
||||
|
||||
const sendeButton = mitSend ?
|
||||
<button
|
||||
className="btnsend"
|
||||
onClick={handleSenden}
|
||||
disabled={isSending}
|
||||
>
|
||||
{isSending ? 'Speichert...' : 'Senden'}
|
||||
</button>
|
||||
: null
|
||||
|
||||
const backButton = mitBack ?
|
||||
<button className="btnback" onClick={handleBack}>
|
||||
Zurück
|
||||
</button>
|
||||
: null
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="lastbuttons">
|
||||
<button className="btnabbruch" onClick={handleAbbruch}>
|
||||
Abbruch
|
||||
</button>
|
||||
<button className="btnanleit" onClick={handleAnleitung}>
|
||||
Anleitung
|
||||
</button>
|
||||
{backButton}
|
||||
{sendeButton}
|
||||
</div>
|
||||
|
||||
{showModal && (
|
||||
<Modal
|
||||
message={modalMessage}
|
||||
onClose={closeModal}
|
||||
type={modalType}
|
||||
isHtml={isModalHtml}
|
||||
className={isWideModal ? 'custom-modal' : ''}
|
||||
/>
|
||||
)}
|
||||
|
||||
{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?"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
13
sternwarte/beoanswer/src/components/LastLine.jsx
Normal file
13
sternwarte/beoanswer/src/components/LastLine.jsx
Normal file
@@ -0,0 +1,13 @@
|
||||
export default function LastLine({version, vdate}) {
|
||||
|
||||
return(
|
||||
<div className = "lastline">
|
||||
<div className = "mailto">
|
||||
<a href="mailto:rexfue@gmail.com">mailto:rexfue@gmail.com</a>
|
||||
</div>
|
||||
<div className = "versn">
|
||||
Version: {version} vom {vdate}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
220
sternwarte/beoanswer/src/components/Modal.css
Normal file
220
sternwarte/beoanswer/src/components/Modal.css
Normal file
@@ -0,0 +1,220 @@
|
||||
/* Modal Overlay - covers the entire screen */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
/* Modal Content Box */
|
||||
.modal-content {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3);
|
||||
max-width: 400px;
|
||||
width: 90%;
|
||||
max-height: 90vh;
|
||||
overflow: hidden;
|
||||
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 {
|
||||
from {
|
||||
transform: scale(0.9) translateY(-10px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: scale(1) translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Modal Header */
|
||||
.modal-header {
|
||||
background: #f8f9fa;
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
margin: 0;
|
||||
color: #333;
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
color: #666;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.modal-close:hover {
|
||||
background: #e9ecef;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* Modal Body */
|
||||
.modal-body {
|
||||
padding: 20px;
|
||||
color: #333;
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Modal Footer */
|
||||
.modal-footer {
|
||||
background: #f8f9fa;
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid #e9ecef;
|
||||
display: flex;
|
||||
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 {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 24px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.modal-button:hover {
|
||||
background: #0056b3;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.modal-button:focus {
|
||||
outline: 2px solid #80bdff;
|
||||
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 */
|
||||
@media (max-width: 480px) {
|
||||
.modal-content {
|
||||
margin: 20px;
|
||||
width: calc(100% - 40px);
|
||||
}
|
||||
|
||||
.modal-header,
|
||||
.modal-body,
|
||||
.modal-footer {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
}
|
||||
86
sternwarte/beoanswer/src/components/Modal.jsx
Normal file
86
sternwarte/beoanswer/src/components/Modal.jsx
Normal file
@@ -0,0 +1,86 @@
|
||||
import React from 'react'
|
||||
// Import des CSS direkt hier
|
||||
import './Modal.css'
|
||||
|
||||
export default function Modal({
|
||||
isOpen = true,
|
||||
onClose,
|
||||
title,
|
||||
children,
|
||||
message,
|
||||
type = 'info',
|
||||
isHtml = false,
|
||||
className,
|
||||
style,
|
||||
bodyClassName,
|
||||
bodyStyle
|
||||
}) {
|
||||
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') {
|
||||
onClose()
|
||||
}
|
||||
}
|
||||
|
||||
// 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}${className ? ' ' + className : ''}`
|
||||
}
|
||||
|
||||
const displayTitle = title || getDefaultTitle()
|
||||
|
||||
// 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 (
|
||||
<div className="modal-overlay" onClick={handleOverlayClick} onKeyDown={handleKeyDown} tabIndex={0}>
|
||||
<div className={getModalClass()} style={style}>
|
||||
<div className="modal-header">
|
||||
<h3 className="modal-title">{displayTitle}</h3>
|
||||
<button className="modal-close" onClick={onClose}>×</button>
|
||||
</div>
|
||||
<div className={`modal-body${bodyClassName ? ' ' + bodyClassName : ''}`} style={bodyStyle}>
|
||||
{getDisplayContent()}
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<button className="modal-button" onClick={onClose} autoFocus>OK</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
48
sternwarte/beoanswer/src/components/Spende.jsx
Normal file
48
sternwarte/beoanswer/src/components/Spende.jsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import { useState } from 'react'
|
||||
import { useFormData } from '../FormContext'
|
||||
|
||||
export default function Spende({ onNext, isCompleted }) {
|
||||
|
||||
const { formData, updateFormData } = useFormData()
|
||||
// Initialisiere State mit dem Wert aus formData (falls vorhanden)
|
||||
const [spendenArt, setSpendenArt] = useState(formData.spendenArt || '')
|
||||
|
||||
const handleRadioChange = (e) => {
|
||||
const art = e.target.value
|
||||
setSpendenArt(art)
|
||||
updateFormData('spendenArt', art)
|
||||
onNext()
|
||||
}
|
||||
|
||||
return (
|
||||
<section>
|
||||
<h3>Eine Spende</h3>
|
||||
<div className="radiogroup">
|
||||
<label>
|
||||
<input type="radio" name="spende" value="bar"
|
||||
checked={spendenArt === 'bar'} onChange={handleRadioChange} disabled={isCompleted}
|
||||
/>
|
||||
ist in bar eingegangen
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="spende" value="ueber"
|
||||
checked={spendenArt === 'ueber'} onChange={handleRadioChange} disabled={isCompleted}
|
||||
/>
|
||||
wird überwiesen
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="spende" value="kasse"
|
||||
checked={spendenArt === 'kasse'} onChange={handleRadioChange} disabled={isCompleted}
|
||||
/>
|
||||
ist in der Spendenkasse
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="spende" value="not"
|
||||
checked={spendenArt === 'not'} onChange={handleRadioChange} disabled={isCompleted}
|
||||
/>
|
||||
ist nicht vorgesehen
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
67
sternwarte/beoanswer/src/components/Verschoben.jsx
Normal file
67
sternwarte/beoanswer/src/components/Verschoben.jsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import { useState } from 'react'
|
||||
import { useFormData } from '../FormContext/'
|
||||
import Modal from './Modal'
|
||||
|
||||
export default function Verschoben({onNext, isCompleted}) {
|
||||
const { formData, updateFormData } = useFormData()
|
||||
|
||||
// State für das selektierte Datum
|
||||
const [selectedDate, setSelectedDate] = useState(formData.neuesDatum || '')
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
|
||||
const handleOK = () => {
|
||||
if (selectedDate) {
|
||||
updateFormData('neuesDatum', selectedDate)
|
||||
onNext()
|
||||
} else {
|
||||
setShowModal(true)
|
||||
}
|
||||
}
|
||||
|
||||
const handleKeyDown = (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
handleOK()
|
||||
}
|
||||
}
|
||||
|
||||
const closeModal = () => {
|
||||
setShowModal(false)
|
||||
}
|
||||
|
||||
let now = new Date()
|
||||
now.setMinutes(now.getMinutes() - now.getTimezoneOffset());
|
||||
|
||||
return (
|
||||
<>
|
||||
<section>
|
||||
<h3>Verschoben auf:</h3>
|
||||
<div className="verschoben">
|
||||
<input
|
||||
type="datetime-local"
|
||||
id="datetime"
|
||||
value={selectedDate}
|
||||
onChange={(e) => {
|
||||
let selD = e.target.value
|
||||
selD = selD + ':00'
|
||||
selD = selD.replace('T',' ')
|
||||
setSelectedDate(selD)
|
||||
}}
|
||||
onKeyDown={handleKeyDown}
|
||||
min={now.toISOString().slice(0,16)}
|
||||
disabled={isCompleted}
|
||||
/>
|
||||
<button className="okbutton" onClick={handleOK}>OK</button>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Modal
|
||||
isOpen={showModal}
|
||||
onClose={closeModal}
|
||||
title="Datum erforderlich"
|
||||
>
|
||||
<p>Bitte ein Datum auswählen</p>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
69
sternwarte/beoanswer/src/index.css
Normal file
69
sternwarte/beoanswer/src/index.css
Normal file
@@ -0,0 +1,69 @@
|
||||
:root {
|
||||
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
/* line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87); */
|
||||
background-color: #242424;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
/*
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1;
|
||||
}
|
||||
*/
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
background-color: #1a1a1a;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
button:hover {
|
||||
border-color: #646cff;
|
||||
}
|
||||
button:focus,
|
||||
button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
}
|
||||
/*
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
a:hover {
|
||||
color: #747bff;
|
||||
}
|
||||
button {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
}
|
||||
*/
|
||||
10
sternwarte/beoanswer/src/main.jsx
Normal file
10
sternwarte/beoanswer/src/main.jsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import './index.css'
|
||||
import App from './App.jsx'
|
||||
|
||||
createRoot(document.getElementById('root')).render(
|
||||
// <StrictMode>
|
||||
<App />
|
||||
// </StrictMode>,
|
||||
)
|
||||
32
sternwarte/beoanswer/vite.config.js
Normal file
32
sternwarte/beoanswer/vite.config.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig(({ mode }) => {
|
||||
return {
|
||||
plugins: [react()],
|
||||
base: mode === 'production' ? '/beoanswer/' : '/', // Nur in Production Unterverzeichnis
|
||||
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']
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
0
sternwarte/checkfuehrung/Entwicklung
Normal file
0
sternwarte/checkfuehrung/Entwicklung
Normal file
0
sternwarte/checkfuehrung/Produktion
Normal file
0
sternwarte/checkfuehrung/Produktion
Normal file
@@ -5,12 +5,6 @@ Checked per cron jeden Tag die SOFUE - Datenbank. Prüft, ob 'gestern' eine
|
||||
Führung hätte stattfinden sollen. Wenn ja, wird der BEO der Führung per mail
|
||||
benachrichtigt mit der Bitte, die Nachbearbeitungs-Webseite auszufüllen.
|
||||
|
||||
*******************
|
||||
2025-06-16:
|
||||
Da leider kein CRON auf dem Webserver läuft, wird dieses Prgramm bis auf
|
||||
Weiteres nicht weiter gepflegt, d.h. es wird nicht ausgeführt! Ebenso kann
|
||||
'beoanswer' nicht ausgeführt werden.
|
||||
*******************
|
||||
|
||||
|
||||
TODO
|
||||
@@ -21,25 +15,35 @@ Dieses Programm kann auch die Überwachung machen, dass Einträge in der DB gel
|
||||
|
||||
|
||||
Versions:
|
||||
V 1.0.1 2025-11-17 rxf
|
||||
- Übergabe der Tage bis zu 'gestern' als Commandline Parameter: '-d x'. Ohne -d wird 1 angesetzt.
|
||||
|
||||
V 1.0.0 2025-11-15 rxf
|
||||
- Mit Tricks kann das nun DOCH realisiert werden:
|
||||
Auf externem Rechner (z.Zt. 'strato_1'; Miet-Server von rxf) wird der cron angestoßen,
|
||||
der das Programm hier (checkfuehrung.js) auf dem Sternwartemserver aufruft.
|
||||
Das zugehörige 'beoanswer' ist eine Webseute, die auch hier auf dem Sternwarte-Server
|
||||
gehostet ist.
|
||||
|
||||
V 0.0 2019-02-04 rxf
|
||||
- Start
|
||||
*/
|
||||
"use strict"
|
||||
|
||||
const DEVELOP=1; // 1 -> Entwicklung 0-> Produktion
|
||||
const DAYS=9;
|
||||
|
||||
const nodemailer = require('nodemailer');
|
||||
const moment = require('moment');
|
||||
const axios = require('axios');
|
||||
const mysql = require('mysql2/promise');
|
||||
const nodemailer = require('nodemailer');
|
||||
|
||||
const beo_Url = 'beoanswer/beoanswer.php?fdate=';
|
||||
const beo_Url = 'beoanswer/?id=';
|
||||
const Url = DEVELOP ? 'http://localhost:8081/' : 'https://sternwarte-welzheim.de/';
|
||||
const DB_host = process.env.DB_HOST || 'localhost';
|
||||
const DB_port = process.env.DB_PORT || 3306;
|
||||
const DB_user = process.env.DB_USER || 'root';
|
||||
const DB_pass = process.env.DB_PASS || 'SFluorit';
|
||||
const DB_dbase = process.env.DB_NAME || 'sternwarte';
|
||||
const DB_host = DEVELOP ? 'localhost' : 'localhost';
|
||||
const DB_port = DEVELOP ? 3306 : 3306;
|
||||
const DB_user = DEVELOP ? 'root' : 'admin_310927';
|
||||
const DB_pass = DEVELOP ? 'SFluorit' : '5D5u49cKNFqf';
|
||||
const DB_dbase = DEVELOP ? 'sternwarte' : 'db310927';
|
||||
|
||||
const transporter = DEVELOP ? nodemailer.createTransport({
|
||||
host: 'localhost',
|
||||
@@ -87,15 +91,15 @@ async function fetchDatafromDB(conn,termin) {
|
||||
function send2BEO(info) {
|
||||
var mailOptions = {
|
||||
from: 'noreply@sternwarte-welzheim.de',
|
||||
// to: info.email,
|
||||
to: 'rexfue@gmail.com',
|
||||
to: info.email,
|
||||
cc: 'rexfue@gmail.com',
|
||||
subject: 'Sonderführung vom '+info.date,
|
||||
text: 'Hallo ' + info.name +',\n\n'
|
||||
+ 'Du hattest gestern Führung! '
|
||||
text: 'Hallo ' + info.name + ',\n\n'
|
||||
+ 'du hattest gestern Führung! '
|
||||
+ 'Bitte fülle folgendes Webformular aus:\n\n'
|
||||
+ Url + beo_Url + info.date + '&id=' + info.id
|
||||
+ Url + beo_Url + info.id
|
||||
+ '\n\nBitte nur über diesen Link zugreifen (oder exakt abschreiben),\n'
|
||||
+ 'da sonst die Zuordnung nicht hergestellt werden kann.\n'
|
||||
+ 'da sonst die Zuordnung nicht hergestellt werden kann.\n\n'
|
||||
+ 'Besten Dank.\n\nGrüße vom Sonderführungsteam'
|
||||
};
|
||||
|
||||
@@ -110,18 +114,24 @@ function send2BEO(info) {
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log(DB_host, DB_port, DB_user, DB_pass, DB_dbase);
|
||||
const yesterday = moment().subtract(DAYS, 'd').format('YYYY-MM-DD');
|
||||
console.log('Yesterday:', yesterday)
|
||||
// console.log(DB_host, DB_port, DB_user, DB_pass, DB_dbase);
|
||||
console.log('Start: ' + moment().format('YYYY-MM-DD HH:mm'))
|
||||
const connection = await mysql.createConnection({
|
||||
host: DB_host,
|
||||
port: DB_port,
|
||||
// host: DB_host,
|
||||
// port: DB_port,
|
||||
user: DB_user,
|
||||
password: DB_pass,
|
||||
database: DB_dbase,
|
||||
socketPath: '/var/lib/mysql/mysql.sock'
|
||||
});
|
||||
const yesterday = moment().subtract(DAYS, 'd').format('YYYY-MM-DD');
|
||||
console.log('Yesterday:', yesterday)
|
||||
await fetchDatafromDB(connection, yesterday);
|
||||
console.log("All done");
|
||||
}
|
||||
|
||||
const argv = require('minimist')(process.argv.slice(2));
|
||||
|
||||
const DAYS = argv.d || 1;
|
||||
|
||||
main().catch(console.error);
|
||||
|
||||
@@ -466,3 +466,11 @@ textarea {
|
||||
#maxpro {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.sondermeldung {
|
||||
font-weight: bold;
|
||||
border: 2px rgb(229, 162, 61) solid;
|
||||
color: red;
|
||||
padding: 5px;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
263
sternwarte/docs/API_DB4js_all.md
Normal file
263
sternwarte/docs/API_DB4js_all.md
Normal file
@@ -0,0 +1,263 @@
|
||||
# Sternwarte API – DB4js_all.php
|
||||
|
||||
Vereinheitlichte Backend-API für öffentliche Führungen, Sonderführungen, Mitarbeiter (BEO), Anmeldungen und Statistiken.
|
||||
|
||||
## Überblick
|
||||
Die Datei `DB4js_all.php` bündelt ehemals mehrere Endpunkte:
|
||||
- `DB4js.php` (öffentliche Führungen + Anmeldungen)
|
||||
- `sofueDB.php` (Sonderführungen)
|
||||
- `anmeldDB.php` (Anmeldungen alte Variante)
|
||||
- `sofianmeldDB.php` (Sonderführungs-Anmeldungen)
|
||||
- `statisticDB.php` (Statistiken)
|
||||
|
||||
Alle Aufrufe erfolgen über einen einzigen HTTP-POST (oder wenige GET) Request an die Datei mit dem Parameter `cmd`.
|
||||
Antwortformat immer JSON (`Content-Type: application/json; charset=utf-8`).
|
||||
|
||||
## Authentifizierung (optional)
|
||||
Falls die Environment-Variablen `API_USER` und `API_PASS` gesetzt sind, **muss** Basic-Auth verwendet werden.
|
||||
|
||||
Header Beispiel:
|
||||
```
|
||||
Authorization: Basic base64(API_USER:API_PASS)
|
||||
```
|
||||
|
||||
Ohne gesetzte ENV-Variablen ist die API offen (nur interne Nutzung empfohlen).
|
||||
|
||||
## Allgemeines Request-Format
|
||||
```http
|
||||
POST /api/DB4js_all.php
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"cmd": "GET_ANMELD",
|
||||
"id": 42
|
||||
}
|
||||
```
|
||||
|
||||
Erfolgreiche Antworten: HTTP 200. Fehler: passende HTTP-Status (z.B. 400, 401, 404, 422, 500) + `{"error": "Beschreibung"}`.
|
||||
|
||||
## Fehlerstruktur
|
||||
```json
|
||||
{
|
||||
"error": "Message",
|
||||
"...optional": "Zusatzinformationen"
|
||||
}
|
||||
```
|
||||
|
||||
## Kommandoliste
|
||||
| Command | Beschreibung |
|
||||
|---------|--------------|
|
||||
| PING | Health-Check (liefert Zeitstempel) |
|
||||
| LIST_COMMANDS | Liefert alle verfügbaren Kommandos mit Beschreibung |
|
||||
| GET_ANMELD | Liste öffentlicher Anmeldungen für `fid` |
|
||||
| GET_ONEANMELD | Einzelne Anmeldung per `id` |
|
||||
| GET_COUNTS | Anzahl Anmeldungen für `fid` |
|
||||
| GET_COUNTS_DATE | Anzahl Anmeldungen für Datum `date` (YYYY-MM-DD) |
|
||||
| INSERT_TLN | Neue öffentliche Anmeldung erstellen |
|
||||
| UPDATE_TLN | Öffentliche Anmeldung ändern |
|
||||
| DELETE_TLN | Öffentliche Anmeldung löschen |
|
||||
| GET_SOFIANMELD | Sonderführungs-Anmeldungen; optional `sofue_id` |
|
||||
| GET_ONESOFIANMELD | Einzelne Sonderführungs-Anmeldung per `id` |
|
||||
| GET_SOFIANMELD_COUNT | Anzahl Sonderführungs-Anmeldungen pro `sofue_id` |
|
||||
| INSERT_SOFIANMELD | Neue Sonderführungs-Anmeldung |
|
||||
| UPDATE_SOFIANMELD | Sonderführungs-Anmeldung ändern |
|
||||
| DELETE_SOFIANMELD | Sonderführungs-Anmeldung löschen |
|
||||
| GET_TERMINE | Öffentliche Führungstermine, optional `includeOld` |
|
||||
| GET_ONETERMIN | Termin per `id` |
|
||||
| GET_FID | Führungs-ID zu Datum `datum` |
|
||||
| GET_TIME | Uhrzeit zu Datum `date` + optional `typ` ("sonnen") |
|
||||
| GET_BEOS | Alle BEOs; optional `onlyguides` und `what` (Spalten) |
|
||||
| GET_ONEBEO | Einzelner BEO per `name` |
|
||||
| GET_ONE | Sonderführung per `id` |
|
||||
| GET_ONETERMIN_SOFUE | Sonderführung per `termin` (Datum) |
|
||||
| GET_MANY | Gefilterte Sonderführungen (status, rows, page, termin) |
|
||||
| UPDATE | Standard-Update einer Sonderführung |
|
||||
| UPDATEAFTER | Nachbearbeitung (stattgefunden, besucher, remark, bezahlt, status) |
|
||||
| DELETE | Sonderführung Soft-Delete |
|
||||
| GET_STATISTIK_SOFUE | Monatsstatistik Sonderführungen Jahr `year` |
|
||||
| GET_STATISTIK_ANMELD | Monatsstatistik öffentliche Führungen Jahr `year` |
|
||||
| GET_STATISTIK_BEO | BEO-Führungsstatistik Jahr `year` |
|
||||
| GET_STATISTIK_GESAMT | Gesamtstatistik Jahr `year` |
|
||||
| SEND_CONFIRMATION | Einfache Text-Mail |
|
||||
| SENDMAILZUSAGE | Zusage-Mail an Anfragenden |
|
||||
| SENDMAIL2BEO | Mail an Mitarbeiter (BEO) |
|
||||
| SENDMAIL2LISTE | Anfrage an Verteilerliste |
|
||||
| PUT2KALENDER | Placeholder für Kalender-Eintrag |
|
||||
|
||||
## Parameter & Beispiele
|
||||
|
||||
### 1. Öffentliche Anmeldungen
|
||||
#### GET_ANMELD
|
||||
```json
|
||||
{ "cmd": "GET_ANMELD", "id": 17 }
|
||||
```
|
||||
Antwort: Liste von Anmeldungen.
|
||||
|
||||
#### INSERT_TLN
|
||||
Pflichtfelder: `name`, `email`, `anzahl`, `fid`
|
||||
```json
|
||||
{
|
||||
"cmd": "INSERT_TLN",
|
||||
"name": "Müller",
|
||||
"email": "mueller@example.com",
|
||||
"anzahl": 4,
|
||||
"fid": 17,
|
||||
"remarks": "Kommt etwas früher"
|
||||
}
|
||||
```
|
||||
Antwort: `{ "success": true, "id": 123 }`
|
||||
|
||||
### 2. Sonderführungs-Anmeldungen
|
||||
#### INSERT_SOFIANMELD
|
||||
```json
|
||||
{
|
||||
"cmd": "INSERT_SOFIANMELD",
|
||||
"name": "Schule ABC",
|
||||
"email": "lehrer@schule.de",
|
||||
"anzahl": 22,
|
||||
"sofue_id": 55,
|
||||
"remarks": "Viele Fragen erwartet"
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Termine
|
||||
#### GET_TERMINE
|
||||
```json
|
||||
{ "cmd": "GET_TERMINE", "includeOld": "false" }
|
||||
```
|
||||
|
||||
### 4. BEOs
|
||||
#### GET_BEOS
|
||||
Hinweis: In der Tabelle `beos` heißt die E-Mail-Spalte `email_1`. Aus Kompatibilitätsgründen akzeptiert die API auch `email` und liefert diese als Alias zurück.
|
||||
```json
|
||||
{ "cmd": "GET_BEOS", "onlyguides": "true", "what": "id,name,email" }
|
||||
```
|
||||
Oder explizit mit Originalspalte:
|
||||
```json
|
||||
{ "cmd": "GET_BEOS", "onlyguides": true, "what": "id,name,email_1" }
|
||||
```
|
||||
|
||||
### 5. Sonderführungen
|
||||
#### GET_MANY
|
||||
Filterbar über Status/Termin/Pagination.
|
||||
```json
|
||||
{ "cmd": "GET_MANY", "status": "2", "rows": 20, "page": 1 }
|
||||
```
|
||||
|
||||
#### UPDATE (Standard)
|
||||
```json
|
||||
{
|
||||
"cmd": "UPDATE",
|
||||
"id": 55,
|
||||
"mitarbeiter": "beo_k1",
|
||||
"status": 2,
|
||||
"bemerkung": "Bestätigt",
|
||||
"wtermin": "2025-12-03 19:00:00"
|
||||
}
|
||||
```
|
||||
|
||||
#### UPDATEAFTER (Nachbearbeitung)
|
||||
Optional Felder: `stattgefunden`, `besucher`, `remark`, `bezahlt`, `status`, `wtermin`
|
||||
```json
|
||||
{
|
||||
"cmd": "UPDATEAFTER",
|
||||
"id": 55,
|
||||
"stattgefunden": 1,
|
||||
"besucher": 27,
|
||||
"remark": "Sehr interessiert",
|
||||
"bezahlt": "Kasse 50€"
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Statistiken
|
||||
#### Gesamtstatistik
|
||||
```json
|
||||
{ "cmd": "GET_STATISTIK_GESAMT", "year": 2025 }
|
||||
```
|
||||
|
||||
### 7. Mail
|
||||
#### SEND_CONFIRMATION
|
||||
```json
|
||||
{
|
||||
"cmd": "SEND_CONFIRMATION",
|
||||
"to": "rxf@gmx.de",
|
||||
"subject": "Test",
|
||||
"body": "Hallo Welt"
|
||||
}
|
||||
```
|
||||
|
||||
## GET Fallback / Health
|
||||
Ein GET ohne `cmd` liefert einfachen Status:
|
||||
```http
|
||||
GET /api/DB4js_all.php
|
||||
```
|
||||
Antwort:
|
||||
```json
|
||||
{ "status": "ok", "message": "API erreichbar" }
|
||||
```
|
||||
|
||||
## Typische Fehlerfälle
|
||||
| Status | Ursache | Beispiel |
|
||||
|--------|---------|---------|
|
||||
| 401 | Ungültige oder fehlende Basic-Auth | `{ "error": "Unauthorized" }` |
|
||||
| 422 | Fehlender `cmd` oder Pflichtfelder | `{ "error": "Command missing" }` |
|
||||
| 404 | Datensatz nicht gefunden | `{ "error": "Not found" }` |
|
||||
| 500 | Interner Fehler | `{ "error": "Internal error" }` |
|
||||
|
||||
## Sicherheit
|
||||
- Alle DB-Zugriffe über PDO Prepared Statements.
|
||||
- Optional Basic-Auth.
|
||||
- Keine direkte Ausgabe interner Fehlermeldungen an Client.
|
||||
- E-Mail-Versand minimalistisch (kein HTML-Injection-Risiko durch Plaintext).
|
||||
|
||||
## Migration Hinweise
|
||||
| Alt-Datei | Abgedeckt durch | Hinweise |
|
||||
|----------|-----------------|----------|
|
||||
| DB4js.php | Commands für öffentliche Anmeldungen & Termine | Parameter unverändert nutzbar |
|
||||
| sofueDB.php | Sonderführungen & Nachbearbeitung | `UPDATEAFTER` ersetzt alte updateAfter-Version |
|
||||
| anmeldDB.php | Enthalten in öffentlichen Anmeldungen | Zusammengeführt |
|
||||
| sofianmeldDB.php | SoFi-Anmeldungen | eigener Command-Namespace |
|
||||
| statisticDB.php | Statistik-Commands | Jahr-Parameter optional |
|
||||
|
||||
## Beispiel: JS Fetch
|
||||
```js
|
||||
async function getAnmeldungen(fid) {
|
||||
const res = await fetch('/api/DB4js_all.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ cmd: 'GET_ANMELD', id: fid })
|
||||
});
|
||||
return res.json();
|
||||
}
|
||||
```
|
||||
|
||||
## Beispiel: curl
|
||||
```bash
|
||||
curl -X POST https://example.com/api/DB4js_all.php \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"cmd":"PING"}'
|
||||
```
|
||||
Mit Basic-Auth:
|
||||
```bash
|
||||
curl -X POST https://example.com/api/DB4js_all.php \
|
||||
-u "$API_USER:$API_PASS" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"cmd":"GET_TERMINE"}'
|
||||
```
|
||||
|
||||
## Erweiterung neuer Commands
|
||||
1. Eintrag in `Commands::MAP` hinzufügen.
|
||||
2. Switch-Case im Dispatcher erweitern.
|
||||
3. Validierung & Antwortformat konsistent halten.
|
||||
|
||||
## Known Limitations / TODO
|
||||
- Kein Rate Limiting.
|
||||
- Kein Pagination bei öffentlichen Anmeldungen (nur Sonderführungen umgesetzt).
|
||||
- Kein zentraler Logger für Nutzungsstatistik.
|
||||
- Kalender-Funktion ist Platzhalter.
|
||||
|
||||
## Changelog (Unified Version)
|
||||
- Erstveröffentlichung: Zusammenführung aller Endpoints, PDO, Auth, Validation.
|
||||
|
||||
---
|
||||
Bei Fragen oder für neue Features bitte erweitern oder Issue anlegen.
|
||||
@@ -101,8 +101,7 @@
|
||||
Für diese Führungen gelten folgende Regeln
|
||||
<ul>
|
||||
<li>Besucher/innen müssen sich zur Führung online über unsere Homepage
|
||||
www.sternwarte-welzheim.de <strong>anmelden</strong> und erhalten eine Anmeldebestätigung,
|
||||
die zur Sternführung mitzubringen ist.</li>
|
||||
www.sternwarte-welzheim.de <strong>anmelden</strong>.</li>
|
||||
<li>Pro Führung sind maximal <strong><?php echo $maxBesucher; ?></strong> Personen zugelassen.
|
||||
</li>
|
||||
</ul>
|
||||
@@ -112,7 +111,7 @@
|
||||
</p>
|
||||
<p><a href="sonnenfuehrung.php">Anmeldung zu einer Sonnenführung</a>
|
||||
</p>
|
||||
<p class="lastchange">Letzte Änderungen: 2024-05-19 ae/rxf</p>
|
||||
<p class="lastchange">Letzte Änderungen: 2025-12-01 ae/rxf</p>
|
||||
|
||||
</div> <!-- end #mainContent -->
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ include 'maintenance.php';
|
||||
Für diese Führungen gelten folgende Regeln
|
||||
<ul>
|
||||
<li>Besucher/innen müssen sich zur Führung online über unsere Homepage
|
||||
www.sternwarte-welzheim.de <strong>anmelden</strong> und erhalten eine Anmeldebestätigung, die zur Sternführung mitzubringen ist.</li>
|
||||
www.sternwarte-welzheim.de <strong>anmelden</strong>.</li>
|
||||
<li>Pro Führung sind maximal <strong><?php echo $maxBesucher;?></strong> Personen zugelassen.</li>
|
||||
</ul>
|
||||
<p><a href="anmeldung.php">Anmeldung zu öffentlichen Führungen
|
||||
|
||||
@@ -225,3 +225,7 @@ th, td {
|
||||
justify-content: space-between;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.absdat {
|
||||
font-size: 60%;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
der aktuellen Wetterlage
|
||||
technischer Probleme
|
||||
einer hereinziehenden Wolkenfront
|
||||
Erkrankung des Führungspersonals
|
||||
Verhinderung durch Verkehrsprobleme
|
||||
anderer unvorhersehbarer Ereignisse
|
||||
der Erkrankung des Führungspersonals
|
||||
einer Verhinderung durch Verkehrsprobleme
|
||||
unvorhersehbarer Ereignisse
|
||||
der Erneuerung unserer Technik
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<?php
|
||||
$typ=$_GET['typ'] ?? 'regular';
|
||||
|
||||
$tit = "";
|
||||
if ($typ == 'regular') {
|
||||
$titel = 'Anmeldungen zur regulären Führung';
|
||||
} elseif ($typ == 'sonnen') {
|
||||
$titel = 'Anmeldungen zur Sonnenführung';
|
||||
$tit = "-Sonne";
|
||||
} else {
|
||||
http_response_code(400);
|
||||
die('Ungültiger Typ.');
|
||||
@@ -16,7 +17,7 @@ if ($typ == 'regular') {
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Anmeldungen</title>
|
||||
<title>Anmeldungen<?echo $tit?></title>
|
||||
<link rel="stylesheet" href="css/anmeld.css"> <!-- Falls du ein Stylesheet hast -->
|
||||
</head>
|
||||
<body>
|
||||
@@ -63,6 +64,14 @@ if ($typ == 'regular') {
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<!-- Error Dialog -->
|
||||
<dialog id="errordialog">
|
||||
<p id="errortext"></p>
|
||||
<div id="errorbutton">
|
||||
<button id="errordialog-ok">OK</button>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<!-- Info unter Tabelle -->
|
||||
<div id="author">
|
||||
<div>
|
||||
|
||||
@@ -17,29 +17,34 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
typ: urlParams.get('typ') || 'regular',
|
||||
storno: urlParams.get('storno') === 'true',
|
||||
double: urlParams.get('double') === 'true',
|
||||
name: urlParams.get('name') || 'Null'
|
||||
name: urlParams.get('name') || 'Null',
|
||||
datum: urlParams.get('datum') === 'true'
|
||||
};
|
||||
|
||||
const absagegrundListe = await ladeAbsagegruende();
|
||||
const fuehrung = query.typ === 'sonnen' ? 'Sonnenführung' : 'Sternführung';
|
||||
let absagegrund = "";
|
||||
const useDatum = query.datum
|
||||
|
||||
const ajaxURL = "php/anmeldDB.php";
|
||||
// Unified API endpoint
|
||||
const ajaxURL = "../../DB4js_all.php";
|
||||
const ANZAHL_DATES = query.typ === 'sonnen' ? 25 : 50;
|
||||
bodytext = ""
|
||||
betreff = ""
|
||||
const TEXTE = {
|
||||
|
||||
absagebutton: (abg) => `Absage ${abg ? `wurde gesendet am ${abg}` : `senden`}`,
|
||||
absagetext: "Absage an alle angemeldeten Besucher senden.",
|
||||
bittegrund: "Die Führung wird abgesagt wegen:",
|
||||
schonabgesagt: "Absage schon gesendet. Nochmal senden?",
|
||||
subject: `Absage der heutigen ${fuehrung}`,
|
||||
bodytext: `Sehr geehrte Dame, sehr geehrter Herr,
|
||||
subject: (useDatum, date) => `Absage der ${useDatum ? `${fuehrung} am ${date}` : `heutigen ${fuehrung}`}`,
|
||||
bodytext: (useDatum, date) => `Sehr geehrte Dame, sehr geehrter Herr,
|
||||
|
||||
Sie haben sich über unsere Webseite www.sternwarte-welzheim.de zur heutigen ${fuehrung} angemeldet.
|
||||
Aufgrund {absagegrund} können wir heute keine Sternführung anbieten.
|
||||
Sie haben sich über unsere Webseite www.sternwarte-welzheim.de zur ${useDatum ? `${fuehrung} am ${date}`: `heutigen ${fuehrung}`} angemeldet.
|
||||
Aufgrund {absagegrund} können wir ${useDatum ? `am ${date}` : 'heute'} keine ${fuehrung} anbieten.
|
||||
Bitte melden Sie sich für einen anderen Termin neu an.
|
||||
|
||||
Mit freundlichen Grüßen
|
||||
Beobachtergruppe Sternwarte Welzheim`,
|
||||
Beobachtergruppe Sternwarte Welzheim`
|
||||
,
|
||||
};
|
||||
|
||||
const liste = {
|
||||
@@ -47,10 +52,10 @@ Beobachtergruppe Sternwarte Welzheim`,
|
||||
ids: []
|
||||
};
|
||||
|
||||
let abgesagt = false;
|
||||
let abgesagt = null
|
||||
let actualdate;
|
||||
let isSmallScreen = false
|
||||
let DateTime = luxon.DateTime
|
||||
const DateTime = luxon.DateTime
|
||||
|
||||
|
||||
function $(selector) {
|
||||
@@ -65,7 +70,7 @@ Beobachtergruppe Sternwarte Welzheim`,
|
||||
body.typ = query.typ;
|
||||
const response = await fetch(ajaxURL, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/js'},
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
return await response.json();
|
||||
@@ -75,7 +80,7 @@ Beobachtergruppe Sternwarte Welzheim`,
|
||||
body.typ = query.typ;
|
||||
const response = await fetch(ajaxURL, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/js'},
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
return await response.json();
|
||||
@@ -94,9 +99,19 @@ Beobachtergruppe Sternwarte Welzheim`,
|
||||
}
|
||||
|
||||
async function storeAbsage(ids) {
|
||||
const update = { cmd: 'UPDATE', field: 'abgesagt', ids: ids, values: [1] };
|
||||
if (!DateTime || typeof DateTime.now !== 'function') {
|
||||
console.error('Luxon DateTime ist nicht verfügbar');
|
||||
throw new Error('Datum kann nicht erstellt werden - Luxon nicht geladen');
|
||||
}
|
||||
const dt = DateTime.now()
|
||||
const jetzt = dt.toFormat('yyyy-LL-dd HH:mm:ss')
|
||||
if (!jetzt || jetzt === 'undefined' || jetzt.includes('undefined')) {
|
||||
console.error('Ungültiges Datum generiert:', jetzt);
|
||||
throw new Error('Ungültiges Datum erstellt');
|
||||
}
|
||||
const update = { cmd: 'UPDATE_TLN_BULK', field: 'abgesagt', ids: ids, values: [jetzt] };
|
||||
await putToDbase(update);
|
||||
abgesagt = true;
|
||||
abgesagt = jetzt
|
||||
}
|
||||
|
||||
async function getDetailText(id) {
|
||||
@@ -105,7 +120,7 @@ Beobachtergruppe Sternwarte Welzheim`,
|
||||
const entry = holit[0];
|
||||
return `
|
||||
<div class="det">${entry.name} ${entry.vorname}<br /></div>
|
||||
<div class="det">${entry.email}<br /></div>
|
||||
<div class="det"><a href="mailto:${entry.email}">${entry.email}</a><br /></div>
|
||||
<div class="det">${entry.plz} ${entry.stadt}<br /></div>
|
||||
<div class="det">${entry.strasse}<br /></div>
|
||||
<div class="det">${entry.telefon}<br /></div>
|
||||
@@ -115,33 +130,32 @@ Beobachtergruppe Sternwarte Welzheim`,
|
||||
|
||||
// Teilnehmer aus 'anmeldungen' austragen und den count in 'fdatum1' anpassen
|
||||
const austragen = async (teilnehmer) => {
|
||||
let update = {cmd: 'DELETEONE', id: parseInt(teilnehmer.id)}
|
||||
let update = {cmd: 'DELETEONE', id: parseInt(teilnehmer.id), typ: query.typ}
|
||||
const erg1 = await putToDbase(update)
|
||||
// update = {cmd: 'UPDATECOUNT',date: parseInt(teilnehmer.fdatum), anzahl: parseInt(teilnehmer.anzahl)}
|
||||
update = {cmd: 'UPDATECOUNT', date: parseInt(teilnehmer.fdatum), anzahl: parseInt(teilnehmer.anzahl)}
|
||||
const erg2 = await putToDbase(update)
|
||||
console.log("Storno Ergebisse: ",erg1,erg2)
|
||||
showAktAnmeldungen(actualdate)
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function showAktAnmeldungen(date) {
|
||||
actualdate = date;
|
||||
liste.emails = [];
|
||||
liste.ids = [];
|
||||
abgesagt = true;
|
||||
let column = query.storno ? "col-2" : "col-3";
|
||||
const anmeldungen = await fetchFromDbase({cmd:'GET_ANMELD', id:date});
|
||||
let besucher = 0;
|
||||
|
||||
clearElement($('tbody'));
|
||||
clearElement($('#tabAnmeld tbody'));
|
||||
|
||||
for (let e of anmeldungen) {
|
||||
besucher += parseInt(e.anzahl);
|
||||
liste.emails.push(e.email);
|
||||
liste.ids.push(e.id);
|
||||
if (e.abgesagt !== '1') {
|
||||
abgesagt = false;
|
||||
}
|
||||
abgesagt = e.abgesagt ? e.abgesagt.slice(0,16) : null
|
||||
abgesagt = (abgesagt === '1900-01-01 00:00') ? null : abgesagt
|
||||
|
||||
// const selected = e.teilgenommen === "1" ? "checked" : "";
|
||||
const row = document.createElement('tr');
|
||||
@@ -170,9 +184,7 @@ Beobachtergruppe Sternwarte Welzheim`,
|
||||
|
||||
$('#tabAnmeld tbody').appendChild(row);
|
||||
}
|
||||
if (abgesagt) {
|
||||
$('#absagen').innerHTML = 'Absage<br />wurde gesendet';
|
||||
}
|
||||
$('#absagen').innerHTML = TEXTE.absagebutton(abgesagt)
|
||||
|
||||
if (besucher !== 0) {
|
||||
const sumRow = document.createElement('tr');
|
||||
@@ -187,6 +199,48 @@ Beobachtergruppe Sternwarte Welzheim`,
|
||||
}
|
||||
$('#tabAnmeld tbody').appendChild(sumRow);
|
||||
}
|
||||
|
||||
// Add group participants line below table
|
||||
let groupInfoDiv = $('#groupInfo');
|
||||
if (!groupInfoDiv) {
|
||||
groupInfoDiv = document.createElement('div');
|
||||
groupInfoDiv.id = 'groupInfo';
|
||||
groupInfoDiv.style.fontSize = '75%';
|
||||
groupInfoDiv.style.textAlign = 'center';
|
||||
groupInfoDiv.style.marginTop = '10px';
|
||||
$('#tabAnmeld').parentNode.insertBefore(groupInfoDiv, $('#tabAnmeld').nextSibling);
|
||||
}
|
||||
groupInfoDiv.innerHTML = '';
|
||||
|
||||
if (query.typ === 'regular' && besucher !== 0) {
|
||||
const groupName = await getGroupForDate(date);
|
||||
if (groupName) {
|
||||
const participants = await getGroupParticipants(groupName);
|
||||
if (participants.length > 0) {
|
||||
const participantNames = participants.map(p => `${p.vorname} `).join(', ');
|
||||
groupInfoDiv.innerHTML = `${groupName} -> ${participantNames}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
bodytext = TEXTE.bodytext(useDatum, DateTime.fromISO(date).setLocale("de").toFormat("cccc, 'den' d. LLLL yyyy"))
|
||||
betreff = TEXTE.subject(useDatum, DateTime.fromISO(date).setLocale("de").toFormat("cccc, 'den' d. LLLL yyyy"))
|
||||
console.log(betreff)
|
||||
}
|
||||
|
||||
async function getGroupForDate(date) {
|
||||
const dateInfo = await fetchFromDbase({cmd:'GET_DATES', anzahl: 1, date: date});
|
||||
return dateInfo && dateInfo.length > 0 ? dateInfo[0].grp : null;
|
||||
}
|
||||
|
||||
async function getGroupParticipants(groupName) {
|
||||
const allBeos = await fetchFromDbase({cmd:'GET_BEOS', onlyguides: true, what: 'vorname,name,gruppe'});
|
||||
return allBeos.filter(beo => {
|
||||
if (!beo.gruppe) return false;
|
||||
// Split gruppe by comma and check for exact match
|
||||
// This handles cases like "Mo I, Mo II", "A, B", etc.
|
||||
const groups = beo.gruppe.split(',').map(g => g.trim());
|
||||
return groups.includes(groupName);
|
||||
});
|
||||
}
|
||||
|
||||
async function findName(name) {
|
||||
@@ -258,23 +312,63 @@ Beobachtergruppe Sternwarte Welzheim`,
|
||||
const grundIndex = Array.from($all('input[name=grund]')).find(el => el.checked)?.value || 0;
|
||||
absagegrund = absagegrundListe[grundIndex];
|
||||
|
||||
// Versuche das Absagedatum zu speichern, aber breche nicht ab wenn es fehlschlägt
|
||||
let datumGespeichert = false;
|
||||
try {
|
||||
await storeAbsage(liste.ids);
|
||||
|
||||
if (query.typ !== 'sonnen') {
|
||||
await fetch('https://laufschrift.rexfue.de/switch/switch_on')
|
||||
datumGespeichert = true;
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Speichern des Absagedatums:', error);
|
||||
// Warnung anzeigen, aber weitermachen
|
||||
console.warn('Absagedatum konnte nicht gespeichert werden, aber Mail wird trotzdem versendet');
|
||||
}
|
||||
|
||||
const bodyText = TEXTE.bodytext.replace("{absagegrund}", absagegrund);
|
||||
await fetchFromDbase({
|
||||
// Laufschrift einschalten (falls regular)
|
||||
if (query.typ !== 'sonnen') {
|
||||
try {
|
||||
await fetch('https://laufschrift.rexfue.de/switch/switch_on')
|
||||
} catch (error) {
|
||||
console.error('Laufschrift konnte nicht eingeschaltet werden:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Mail senden (wichtigster Teil!)
|
||||
bodyText = bodytext.replace("{absagegrund}", absagegrund);
|
||||
try {
|
||||
const mailRet = await fetchFromDbase({
|
||||
cmd: 'SENDMYMAIL',
|
||||
to: ['rexfue@gmail.com'],
|
||||
betreff: TEXTE.subject,
|
||||
betreff: betreff,
|
||||
body: bodyText,
|
||||
bcc: liste.emails
|
||||
});
|
||||
console.log("Gesendet an: ", liste.emails)
|
||||
$('#absagen').innerHTML = 'Absage<br />wurde gesendet';
|
||||
|
||||
if (mailRet.error) {
|
||||
$('#errortext').innerHTML = mailRet.errortext
|
||||
$('#errordialog-ok').addEventListener('click', () => $('#errordialog').close())
|
||||
$('#errordialog').showModal();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Mailret: ", mailRet, "Gesendet an: ", liste.emails)
|
||||
|
||||
// Zeige Warnung an, wenn Datum nicht gespeichert werden konnte
|
||||
if (!datumGespeichert) {
|
||||
$('#errortext').innerHTML = `<strong>Hinweis:</strong><br>Die Absage-Mail wurde erfolgreich versendet.<br><br>Das Absagedatum konnte jedoch nicht in der Datenbank gespeichert werden. Bitte notieren Sie manuell, dass die Absage gesendet wurde.`;
|
||||
$('#errordialog-ok').addEventListener('click', () => $('#errordialog').close())
|
||||
$('#errordialog').showModal();
|
||||
}
|
||||
|
||||
$('#absagen').innerHTML = TEXTE.absagebutton(abgesagt)
|
||||
$('#absagedialog').close();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Mail-Versand:', error);
|
||||
$('#errortext').innerHTML = `<strong>Fehler beim Versenden der Absage-Mail:</strong><br>${error.message}`;
|
||||
$('#errordialog-ok').addEventListener('click', () => $('#errordialog').close())
|
||||
$('#errordialog').showModal();
|
||||
$('#absagedialog').close();
|
||||
}
|
||||
});
|
||||
|
||||
$('#absagedialog-cancel').addEventListener('click', () => {
|
||||
@@ -371,10 +465,14 @@ Beobachtergruppe Sternwarte Welzheim`,
|
||||
// t -> Uhrzeit
|
||||
// Return
|
||||
// neu formatierter Datums-String
|
||||
const bauDate = (w,d,t) => {
|
||||
let dd = d.replace(/^(\d{4})(\d{2})(\d{2})$/, '$3.$2.$1',);
|
||||
let dt = w.substr(0,2) + ', ' + dd + ' ' + t.substr(0,2) + ':00';
|
||||
return dt;
|
||||
const bauDate = (w, d, t) => {
|
||||
const ds = String(d ?? '');
|
||||
const ts = String(t ?? '');
|
||||
const ws = String(w ?? '');
|
||||
const dd = ds.replace(/^(\d{4})(\d{2})(\d{2})$/, '$3.$2.$1');
|
||||
const hh = ts.substr(0, 2);
|
||||
const ww = ws.substr(0, 2);
|
||||
return `${ww}, ${dd} ${hh}:00`;
|
||||
}
|
||||
|
||||
|
||||
@@ -424,7 +522,7 @@ Beobachtergruppe Sternwarte Welzheim`,
|
||||
$('#ftermin').addEventListener('change', async () => {
|
||||
const date = $('#ftermin').value;
|
||||
const index = $('#ftermin').selectedIndex;
|
||||
setVisibility('#absagen', index === 0);
|
||||
setVisibility('#absagen', index === 0 || useDatum);
|
||||
await showAktAnmeldungen(date);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,12 +1,38 @@
|
||||
// VersiosNummern und -Geschichte
|
||||
|
||||
const VERSION="1.7.3";
|
||||
const VDATE="2025-09-07";
|
||||
const VERSION="1.12.1";
|
||||
const VDATE="2026-01-20";
|
||||
|
||||
/* History
|
||||
|
||||
Rev. Datum Entwickler
|
||||
|
||||
1.12.1 2026-01-20 rxf
|
||||
- bei DELETEONE den typ mit übergeben (sonst geht storno bei sonne nicht)
|
||||
|
||||
1.12.0 2026-01-11 rxf
|
||||
- Abmeldedatum richtig als einfacher String mit Sekunden abspeichern
|
||||
|
||||
1.11.0 2025-12-24 rxf
|
||||
- Wenn das Absagedatum ungültig ist, eine Fehlermeldung
|
||||
erzeugen, aber die Mails trotzdem versenden
|
||||
|
||||
1.10.0 2025-11-17 rxf
|
||||
- als PHP-Interface nun das DB4js_all.php verwendet
|
||||
- unter Tabelle noch die Namen der Führenden anzeigen
|
||||
|
||||
1.9.0 2025-11-07 rxf
|
||||
- Datum der Absge mit in der DB (abgesagt). Wird angezeigt, wenn abgesagt wurde.
|
||||
|
||||
1.8.1 2025-10-19 rxf
|
||||
- Errormeldung, wenn bei 'anmeld.js' die Abmeldung nicht rausgeht
|
||||
|
||||
1.8.0 2025-10-17 rxf
|
||||
- intern Anmeldung kann nun mit Datum versehen werden (in der URL: &dateum=true), dass wird die
|
||||
Abmeldungn mit dem Datum versehen und es kann auch schon füher abgemeldet werden.
|
||||
- zusätzlicher Abmeldetext
|
||||
- der Mailer arbeitet nun mit der Adresse: sternwarte.welzheim@gmx.de (!)
|
||||
|
||||
1.7.3 2025-09-07 rxf
|
||||
- 'Laufschrift einschalten' wieder reingebaut, aber nicht bei Sonnenführung
|
||||
|
||||
|
||||
34
sternwarte/intern/sofue/js/sofue.js
Executable file → Normal file
34
sternwarte/intern/sofue/js/sofue.js
Executable file → Normal file
@@ -2,7 +2,7 @@
|
||||
//globale Variable
|
||||
var maint = false; // Wartung??
|
||||
|
||||
var ajaxURL="php/sofueDB.php";
|
||||
var ajaxURL="../../DB4js_all.php"
|
||||
var beos = [];
|
||||
|
||||
// 0 1 2 3 (4) (5)
|
||||
@@ -54,7 +54,7 @@ function buildEntryScreen(id) {
|
||||
|
||||
// Übergenen Zeit testen, ob sie mit 1900 oder 0000 startet
|
||||
function check1900(termin) {
|
||||
if (termin.startsWith("1900") || termin.startsWith("0000")) {
|
||||
if (termin === null || termin.startsWith("1900") || termin.startsWith("0000")) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -284,6 +284,7 @@ function addEditbox(detail) {
|
||||
|
||||
//bei der Eingabe der BEOs eine Autovervollständigung durchführen
|
||||
function buildBeosComplete() {
|
||||
|
||||
function split( val ) {
|
||||
return val.split( /,\s*/ );
|
||||
}
|
||||
@@ -291,6 +292,19 @@ function buildBeosComplete() {
|
||||
return split( term ).pop();
|
||||
}
|
||||
|
||||
// Konvertiere beos Array: wenn Objekte, extrahiere die Namen
|
||||
var beosNames = beos.map(function(item) {
|
||||
if (typeof item === 'string') {
|
||||
return item;
|
||||
} else if (item && item.name) {
|
||||
return item.name;
|
||||
} else {
|
||||
return String(item);
|
||||
}
|
||||
});
|
||||
|
||||
console.log("buildBeosComplete - Original beos:", beos);
|
||||
console.log("buildBeosComplete - Converted names:", beosNames);
|
||||
|
||||
$( "#curmar" )
|
||||
// don't navigate away from the field on tab when selecting an item
|
||||
@@ -304,8 +318,11 @@ function buildBeosComplete() {
|
||||
minLength: 0,
|
||||
source: function( request, response ) {
|
||||
// delegate back to autocomplete, but extract the last term
|
||||
response( $.ui.autocomplete.filter(
|
||||
beos, extractLast( request.term ) ) );
|
||||
var term = extractLast( request.term );
|
||||
console.log("Searching for:", term);
|
||||
var filtered = $.ui.autocomplete.filter(beosNames, term);
|
||||
console.log("Filtered results:", filtered);
|
||||
response( filtered );
|
||||
},
|
||||
focus: function() {
|
||||
// prevent value inserted on focus
|
||||
@@ -551,7 +568,7 @@ function saveSettings() {
|
||||
doAjaxCall_arr(ajaxURL,cmd,showajaxerg);
|
||||
|
||||
if(status == 1) { // offen -> mail an Liste
|
||||
sendmail2liste(id);
|
||||
sendmail2liste(id, termin);
|
||||
}
|
||||
if (status == 2) {
|
||||
sendmail2beo(id, marb, termin)
|
||||
@@ -595,12 +612,15 @@ function showdbase(val) {
|
||||
}
|
||||
|
||||
// Status wurde auf 'offen' gesetzt -> nun eine Mail an die Liste mit der Anfrage senden
|
||||
function sendmail2liste(id) {
|
||||
function sendmail2liste(id, termin) {
|
||||
const liste = 'sternwarte@planetariumsgesellschaft.de';
|
||||
const cmd = {cmd: 'SENDMAIL2LISTE', id: id, to: liste}
|
||||
let cmd = {cmd: 'SENDMAIL2LISTE', id: id, to: liste}
|
||||
console.log("Sende mail to Liste");
|
||||
doAjaxCall_arr(ajaxURL,cmd,showajaxerg);
|
||||
console.log("Mail gesendet");
|
||||
// in den Kalender als Platzhalter (mit ??) eintragen
|
||||
cmd = {cmd: 'PUT2KALENDER', id: id, termin: termin, mitarbeiter: '??'}
|
||||
doAjaxCall_arr(ajaxURL,cmd,showajaxerg);
|
||||
}
|
||||
|
||||
// Status wurde auf 'zugesgat' gesetzt -> nun eine Mail an den BEO senden
|
||||
|
||||
@@ -1,11 +1,22 @@
|
||||
// VersiosNummern und -Geschichte
|
||||
|
||||
var VERSION="1.92";
|
||||
var VDATE="2024-09-20";
|
||||
var VERSION="1.95";
|
||||
var VDATE="2026-01-19";
|
||||
|
||||
/* History
|
||||
|
||||
Rev. Datum Entwickler
|
||||
1.95 2026-01-19
|
||||
- Eintrag in den Kalender auch bei 'offen'
|
||||
|
||||
1.94 2025-12-22 rxf
|
||||
- Autocomplete repariert
|
||||
|
||||
1.93 2025-12-20 rxf
|
||||
- Texte in DB4js_all angepasst, so dass sie wie im alten sofue.js erscheinen
|
||||
- DB4js_all.php nun verwenden
|
||||
- prüfen auf 'termin === null' ich check1900()
|
||||
|
||||
1.92 2024-09-20 rxf
|
||||
- Probleme mit 'Zusgae sende' behoben
|
||||
- auch bei mehreren BEOs geht nun die Mail richtig
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
$(document).ready(function() {
|
||||
|
||||
// Globals:
|
||||
const url = "php/statistic.php";
|
||||
const url = "../../DB4js_all.php";
|
||||
const months = ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember' ];
|
||||
const colors = { besucher: ['#FFB60A','#FF6702','#E54818'], aktivitaet: ['#D1D8D1','#ABC1C5','#81ACBC'], gesamt: ['#CBD26A','#88AD63','#5A8A40'] };
|
||||
const barsPerPage = 23;
|
||||
|
||||
@@ -1,471 +0,0 @@
|
||||
// anmeldung.js rxf 2020-10.12
|
||||
// Dynamik für die Anmelde-Seite
|
||||
//
|
||||
|
||||
$(document).ready(() => {
|
||||
|
||||
const ajaxURL="../DB4js.php";
|
||||
const maxPersonen = 10;
|
||||
|
||||
const dummyTln = {
|
||||
name:"Zuname{z}",
|
||||
vorname:"",
|
||||
email:"rxf{z}@gmx.de",
|
||||
telefon:"0711123456789",
|
||||
plz:"12345",
|
||||
stadt:"Ort{z}",
|
||||
strasse:"Straße {z}",
|
||||
fid: "",
|
||||
anzahl:1,
|
||||
teilgenommen: 0,
|
||||
remarks:"Bemerkung Nr {z}",
|
||||
abgesagt: 0,
|
||||
angemeldet: moment().format("YYYY-MM-DD")
|
||||
};
|
||||
|
||||
let aktualTln = {
|
||||
name:"",
|
||||
vorname:"",
|
||||
email:"",
|
||||
telefon:"",
|
||||
plz:"",
|
||||
stadt:"",
|
||||
strasse:"",
|
||||
fid: "",
|
||||
anzahl:1,
|
||||
teilgenommen: 0,
|
||||
remarks:"",
|
||||
abgesagt: 0,
|
||||
angemeldet: ""
|
||||
};
|
||||
|
||||
const mandatory = {
|
||||
name:true,
|
||||
vorname:false,
|
||||
email:true,
|
||||
telefon:false,
|
||||
plz:true,
|
||||
stadt:true,
|
||||
strasse:true,
|
||||
fid: false,
|
||||
anzahl:false,
|
||||
teilgenommen: false,
|
||||
remarks:false,
|
||||
abgesagt: false,
|
||||
angemeldet: true
|
||||
};
|
||||
|
||||
let allParticipants = [];
|
||||
let errtext = "";
|
||||
|
||||
|
||||
// Von der Datenbank Werte abholen
|
||||
// Param:
|
||||
// body: Object mit cmd und param
|
||||
// Return:
|
||||
// angeforderte Daten als JSON
|
||||
const fetchFromDbase = async (body) => {
|
||||
const response = await fetch(ajaxURL,{
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/js'},
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
|
||||
// In die Datenbank Werte eintragen
|
||||
// Param:
|
||||
// body: Object mit cmd und param
|
||||
// Return:
|
||||
// angeforderte Daten als JSON
|
||||
const putToDbase = async (body) => {
|
||||
const response = await fetch(ajaxURL,{
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/js'},
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
|
||||
// Beim Senden der Anmeldeseite die Nummer der Führung mit schicken
|
||||
$('form').submit((event) => {
|
||||
let nr = $('#ftermin').find(':selected');
|
||||
console.log('nr: ', nr[0].value);
|
||||
$('#fid').val(nr[0].value);
|
||||
});
|
||||
|
||||
|
||||
// zum Testen die Anmelde-Seite automatisch ausfüllen
|
||||
$('#autofillbutton').click(() => {
|
||||
let rand = Math.floor(Math.random() * 100);
|
||||
for (let f in dummyTln) {
|
||||
if (f=='fid') {
|
||||
continue;
|
||||
}
|
||||
let x = dummyTln[f].toString();
|
||||
aktualTln[f] = x.replace("{z}",rand);
|
||||
$('#'+f).val(x.replace("{z}",rand));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Aus dem rohen Führungsdatum von der Datenbank ein besser
|
||||
// leserliches Format erzeugen
|
||||
// Params:
|
||||
// w -> Wocjentag
|
||||
// d -> Datum in DBase-Format
|
||||
// t -> Uhrzeit
|
||||
// Return
|
||||
// neu formatierter Datums-String
|
||||
const bauDate = (w,d,t) => {
|
||||
let dd = d.replace(/^(\d{4})(\d{2})(\d{2})$/, '$3.$2.$1',);
|
||||
return w.substr(0,2) + ', ' + dd + ' ' + t;
|
||||
}
|
||||
|
||||
|
||||
// Aus der Datenbank die nächsten 'n' Führungsdaten holen und
|
||||
// in den <option>-Tags anzeigen.
|
||||
// Zusätzlich für jeden Tag zählen, wieviel schon angemeldet sind.
|
||||
// Sind es >= 10, dass diese <option> als "disbaled" markieren.
|
||||
// Die Anzahl der noch freien Plätze mit anzeigen
|
||||
async function buildfuehrungdates(n) {
|
||||
const dates = await fetchFromDbase({cmd:'GET_DATES',anzahl:n, fid:0});
|
||||
|
||||
str = "";
|
||||
for (let d of dates) {
|
||||
const count = await fetchFromDbase({cmd: 'GET_COUNTS', fid: d.fid});
|
||||
str += `<option ${count >= maxPersonen ? "disabled" : ""} ` +
|
||||
`value=${d.fid}> ${bauDate(d.weekday, d.date, d.time)} Frei: ${maxPersonen - count}</>`;
|
||||
}
|
||||
$('#fid').append(str);
|
||||
}
|
||||
|
||||
|
||||
// Mit der fid das Führungsdatum aus der DB holen und in leserliche
|
||||
// Form umsetzen
|
||||
// Params:
|
||||
// fid -> ID des Eintrages in der DB
|
||||
// Return
|
||||
// String mit dem Führungsdatum
|
||||
const getfdateformatted = async (fid) => {
|
||||
const nxd = await fetchFromDbase({cmd: 'GET_DATES', anzahl: 1, fid: fid});
|
||||
let newdate = bauDate(nxd[0].weekday, nxd[0].date, nxd[0].time);
|
||||
return newdate.replace(/(\d+) Uhr/, "um $1 Uhr");
|
||||
}
|
||||
|
||||
// Infomail über die Anmeldung bzw. Stornierung an rexfue@gmail.com senden
|
||||
// Parameters:
|
||||
// storno -> true: Stornierung, fals: Anmeldung
|
||||
const sendInfoMail = async(storno) => {
|
||||
const fdatum = await getfdateformatted(aktualTln.fid);
|
||||
const subj = storno ? "Stornierung einer Stern-Führung" : "Neue Anmeldung zu einer Stern-Führung";
|
||||
const ret = await putToDbase({
|
||||
cmd: 'SEND_INFO_MAIL',
|
||||
to: 'rexfue@gmail.com',
|
||||
subject: subj,
|
||||
body: '<html>' +
|
||||
'<body><table>' +
|
||||
'<tr><td>Name, Vorname</td><td style="text-align: right;">' + aktualTln.name + ', ' + aktualTln.vorname + '</td></tr>' +
|
||||
'<tr><td>Strasse</td><td style="text-align: right;">' + aktualTln.strasse + '</td></tr>' +
|
||||
'<tr><td>Ort</td><td style="text-align: right;">' + aktualTln.plz + ', ' + aktualTln.stadt + '</td></tr>' +
|
||||
'<tr><td>Telefon</td><td style="text-align: right;">' + aktualTln.telefon + '</td></tr>' +
|
||||
'<tr><td>E-mail</td><td style="text-align: right;">' + aktualTln.email + '</td></tr>' +
|
||||
'<tr><td>Termin</td><td style="text-align: right;">' + fdatum + '</td></tr>' +
|
||||
'<tr><td>Personen</td><td style="text-align: right;">' + aktualTln.anzahl + '</td></tr>' +
|
||||
'<tr><td>Bemerkungen</td><td class="fr">' + aktualTln.remarks + '</td></tr>' +
|
||||
'</table></body></html>'
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Bestätigungsmail an den Anmeldeneden senden
|
||||
// Params:
|
||||
// storno -> true: Stornierung, fals: Anmeldung
|
||||
const sendConfirmation = async (storno) => {
|
||||
const fdatum = await getfdateformatted(aktualTln.fid);
|
||||
let body = "Sehr geehrte Dame, sehr geehrter Herr, \r\n\r\nhiermit bestätigen wir Ihre "
|
||||
body += storno ? "Stornierung der " : "";
|
||||
body += "Anmeldung zu der Führung auf der Sternwarte Welzheim am\r\n\r\n" +
|
||||
`${fdatum} für ${aktualTln.name} ${aktualTln.vorname} mit ${aktualTln.anzahl} ${aktualTln.anzahl==1?"Person.":"Personen."}` +
|
||||
"\r\n\r\n";
|
||||
if (!storno) {
|
||||
body +=
|
||||
"Bitte bringen Sie diese Bestätigung als Ausdruck oder digital zur Führung mit. \r\n" +
|
||||
"Ohne diese Bestätigung erfolgt ausnahmslos k e i n Einlass.\r\n\r\n" +
|
||||
"Falls Sie den Führungstermin absagen wollen, verwenden Sie bitte folgenden Link \r\n";
|
||||
body += develop == "true" ?
|
||||
"http://localhost:8082/anmeldung.php?storno=" :
|
||||
"https://sternwarte-welzheim.de/anmeldung.php?storno=";
|
||||
body += md5(aktualTln.email + aktualTln.fid) + "\n\r";
|
||||
body += "Die Führung findet NUR bei sternklarem Himmel statt. Falls der Himmel bedeckt ist \r\n" +
|
||||
"und die Führung ausfällt, bitten wir Sie um eine neue Anmeldung.\r\n\r\n" +
|
||||
"Die Hygienevorschriften sind zu beachten: Die Teilnehmer/-innen müssen Gesichtsmasken \r\n" +
|
||||
"tragen und den vorgeschriebenen Abstand halten. " +
|
||||
"Nicht teilnehmen dürfen Personen, \r\ndie in den letzten vierzehn Tagen Kontakt " +
|
||||
"mit einem Coronavirus-Infizierten \r\nhatten oder Infektionssymptome zeigen.\r\n\r\n";
|
||||
}
|
||||
body += "Mit freundlichen Grüßen\r\n\r\n" +
|
||||
"Beobachterteam der Sternwarte Welzheim\r\n\r\n" +
|
||||
"www.sternwarte-welzheim.de"
|
||||
const ret = await putToDbase({
|
||||
cmd: 'SEND_MAIL',
|
||||
to: aktualTln.email,
|
||||
subject: storno ?
|
||||
"Stornierung Ihrer Anmeldung zu einer Führung auf der Sternwarte Welzheim" :
|
||||
"Anmeldung zu einer Führung auf der Sternwarte Welzheim",
|
||||
body: body,
|
||||
});
|
||||
console.log("hier wird die Email gesendet");
|
||||
sendInfoMail(storno);
|
||||
}
|
||||
|
||||
|
||||
// MD5-Hash ausrechnen
|
||||
const md5 = (value) => {
|
||||
return CryptoJS.MD5(value).toString();
|
||||
}
|
||||
|
||||
|
||||
// Den neuen Teilnehmer in die DB eintragen
|
||||
const insertEntry = async () => {
|
||||
let ret = await putToDbase({cmd: 'INSERT_TLN', data: aktualTln})
|
||||
sendConfirmation(false);
|
||||
}
|
||||
|
||||
|
||||
// Den Teilnehmer mit der ID id aus der DB löschen
|
||||
const deleteEntry = async (id) => {
|
||||
let ret = await putToDbase({cmd: 'DELETE_TLN', id:id});
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
// Den Teilnehmer mit der ID id mit den neuen Daten versorgen
|
||||
const updateEntry = async (id) => {
|
||||
let ret = await putToDbase({cmd: 'UPDATE_TLN', data: aktualTln, id: id })
|
||||
sendConfirmation(false);
|
||||
}
|
||||
|
||||
|
||||
// Dialogbox für 'doppelt eingetragen'
|
||||
$("#dialogdoppelt").dialog({
|
||||
dialogClass: 'no-close',
|
||||
autoOpen: false,
|
||||
closeOnEscape: false,
|
||||
width: 700,
|
||||
modal: true,
|
||||
position: 'center',
|
||||
title: 'Doppelter Eintrag',
|
||||
open: () => {
|
||||
showdoppelText();
|
||||
},
|
||||
buttons: [
|
||||
{
|
||||
text: 'Auswahl übernehmen',
|
||||
click: async function() {
|
||||
let gewaehlt = [];
|
||||
$.each($("input[name='dopp']:checked"), function() {
|
||||
gewaehlt.push($(this).val());
|
||||
});
|
||||
let tln = $("#dialogdoppelt").data('fids').otln;
|
||||
if (gewaehlt.length === 1) {
|
||||
if (gewaehlt[0] == "new") {
|
||||
deleteEntry($("#dialogdoppelt").data('fids').otln.id);
|
||||
insertEntry();
|
||||
tln = $("#dialogdoppelt").data('fids').ntln;
|
||||
}
|
||||
} else {
|
||||
insertEntry();
|
||||
tln = $("#dialogdoppelt").data('fids').ntln;
|
||||
}
|
||||
$(this).dialog('close');
|
||||
let meld = await afterDialogtext(tln);
|
||||
$('#mainContent').html(meld);
|
||||
$('#mainContent').css('height','430px');
|
||||
}
|
||||
}
|
||||
]
|
||||
}).prev(".ui-dialog-titlebar").css("background","red");
|
||||
|
||||
|
||||
// Der Eintrag wird jetzt doppelt: dieses melden in einer
|
||||
// kleinen Dialog-Box
|
||||
function showDoppelt(oldone, newone) {
|
||||
$('#dialogdoppelt').data('fids', {otln: oldone, ntln: newone}).dialog('open');
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Text für doppelten Eintrag anzeigen
|
||||
async function showdoppelText() {
|
||||
const ofdate = await fetchFromDbase({
|
||||
cmd: 'GET_ONE_DATE',
|
||||
fid: $("#dialogdoppelt").data('fids').otln.fid
|
||||
});
|
||||
const nfdate = await fetchFromDbase({
|
||||
cmd: 'GET_ONE_DATE',
|
||||
fid: $("#dialogdoppelt").data('fids').ntln.fid
|
||||
});
|
||||
$('#dialogdoppelttext').html('Sie haben sich doppelt eingetragen.<br />' +
|
||||
'Wenn das so gewollt ist, dann kreuzen Sie bitte unten beide Führungen an,<br />' +
|
||||
'andernfalls nur diejenige, an der Sie wirklich teilnehmen wollen.<br /> <br />' +
|
||||
'<input type="checkbox" name="dopp" value="new" checked style="margin-right:10px;">' +
|
||||
bauDate(nfdate.wtag, nfdate.datum, nfdate.uhrzeit) + '<br />' +
|
||||
'<input type="checkbox" value="old" name="dopp" style="margin-right:10px;">' +
|
||||
bauDate(ofdate.wtag, ofdate.datum, ofdate.uhrzeit) + '<br />');
|
||||
}
|
||||
|
||||
const errnum = {'no':1, 'error':2, 'double':3};
|
||||
|
||||
|
||||
// Alle Einträge auf Plausibilität prüfen
|
||||
// Return:
|
||||
// Object mit:
|
||||
// error: 'true', 'false'
|
||||
// action: 'store', 'exit', 'none'
|
||||
async function checkPlausibilität() {
|
||||
let error = false;
|
||||
// Zuerst prüfen, ob auch alle notwendigen Felder ausgefüllt sind
|
||||
for (let feld in aktualTln) {
|
||||
aktualTln[feld] = $('#' + feld).val(); // Feld des actualTln auf dem Wert setzen
|
||||
if (!mandatory[feld]) {
|
||||
continue;
|
||||
}
|
||||
if (aktualTln[feld] == "") { // Falls das Feld leer ist, den Rahmen
|
||||
$('#' + feld).css('border-color', 'red'); // rot machen und das
|
||||
error = true; // errorfralg setzen
|
||||
}
|
||||
}
|
||||
if (error) { // wenn Error, dieses melden
|
||||
errtext = "Bitte korrigieren Sie die fehlenden Einträge in den rot umrandeten Feldern";
|
||||
return errnum.error; // und raus
|
||||
}
|
||||
// Als Nächstes die PLZ prüfen: Das müssen 5 Ziffern sein
|
||||
const plz = $('#plz').val();
|
||||
let re = RegExp(/^[0-9]{5}$/);
|
||||
if(!re.test(plz)) {
|
||||
$('#plz').css('border-color', 'red');
|
||||
errtext = "Bitte geben Sie bei der Postleitzahl mindestens fünf Ziffern und keine Buchstaben ein";
|
||||
return errnum.error; // und raus
|
||||
}
|
||||
// Die Email prüfen
|
||||
re = RegExp(/^[a-z0-9]+([-_\.]?[a-z0-9])+@[a-z0-9]+([-_\.]?[a-z0-9])+\.[a-z]{2,4}/);
|
||||
let mail = $('#email').val().toLowerCase();
|
||||
if(!re.test(mail)) {
|
||||
$('#email').css('border-color', 'red');
|
||||
errtext = "Bitte geben Sie eine gültige E-Mail-Adresse ein";
|
||||
return errnum.error; // und raus
|
||||
}
|
||||
// Nun noch doppelte Einträge checken
|
||||
// dazu prüfen, ob die email_Adresse in den Teilnehmern schon enthalten ist
|
||||
const aktMail = aktualTln.email;
|
||||
let allParticipants = await fetchAlleTln();
|
||||
const find = allParticipants.findIndex((f) => f.email === aktMail);
|
||||
if (find == -1) {
|
||||
insertEntry();
|
||||
return errnum.no;
|
||||
}
|
||||
let oldTln = allParticipants[find];
|
||||
// Email enthalten. Ist der Führungstag identisch? Wenn ja, dann einfach die neuen
|
||||
// Werte eintragen (also überschreiben)
|
||||
if (aktualTln.fid === oldTln.fid) {
|
||||
updateEntry(oldTln.id);
|
||||
return errnum.no;
|
||||
} else {
|
||||
// Hier nun 'Doppelt' melden
|
||||
showDoppelt(oldTln, aktualTln);
|
||||
return errnum.double;
|
||||
}
|
||||
return errnum.no;
|
||||
}
|
||||
|
||||
|
||||
// Text für den Dialog der Anmeldung
|
||||
const afterDialogtext = async(tln) => {
|
||||
let fdate = await getfdateformatted(tln.fid);
|
||||
return '<p>Vielen Dank für Ihre Anmeldung. <br /><br />' +
|
||||
'Wir freuen uns über Ihren Besuch auf der Sternwarte Welzheim am<br /> ' +
|
||||
'<div id="antwort_fdatum" style="text-align:center;">' +
|
||||
`${fdate} mit ${tln.anzahl} ${tln.anzahl == 1 ? "Person" : "Personen"}` +
|
||||
'</div> <br />' +
|
||||
'Wir haben Ihnen die Anmelde-Bestätigung per Email zugesandt.</p>' +
|
||||
'<p><a class="button" href="/index.php">Zurück</a></p>';
|
||||
}
|
||||
|
||||
|
||||
// Formular ist ausgefüllt - der Anmelde-Knopf wurde gerdückt.
|
||||
// Alle Eingaben in den aktuelTln eintragen und dann
|
||||
// eine Plausibilitäts-Prüfung durchführen und wenn Alles
|
||||
// OK ist, die Einträge in die Datenbank schreiben
|
||||
$('input[name="submit"]').click(async () => {
|
||||
$('#errordiv').css('visibility','hidden'); // erst mal die evtl. vorhandenen Fehlermeldung
|
||||
for ( let f in aktualTln) { // löschen und alle Rahmen
|
||||
$('#'+f).css('border-color','black'); // wieder schwarz machen
|
||||
}
|
||||
let nr = $('#fid').find(':selected');
|
||||
aktualTln.fid = nr[0].value; // selektiertes Führungsdatum merken
|
||||
const ret = await checkPlausibilität();
|
||||
if (ret === errnum.error) {
|
||||
$('#errordiv').html(errtext);
|
||||
$('#errordiv').css('visibility', 'visible');
|
||||
} else if (ret === errnum.no) {
|
||||
let meld = await afterDialogtext(aktualTln);
|
||||
$('#mainContent').html(meld);
|
||||
$('#mainContent').css('height','430px');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Storno
|
||||
// Der Link für die Stornierung wurde aufgerufen:
|
||||
// Zuerst die email-Adresse suchen (mit Hilfe des übergebenen md5-hash),
|
||||
// dann die Stornierung anzeigen und dann noch eine Bestätigung senden
|
||||
const doStorno = async(md5hash) => {
|
||||
let emails = await fetchFromDbase({cmd:'GET_ALL_EMAILS'});
|
||||
for (let em of emails) {
|
||||
if(md5((em.email+em.fid)) == md5hash) {
|
||||
// komplette Info dieses teilnehmers holen
|
||||
let teilnehmer = await fetchFromDbase({cmd: 'GET_TEILN', id: em.id, isid: true});
|
||||
aktualTln = Object.assign({}, teilnehmer[0]);
|
||||
// Löschen des Eintrages
|
||||
let ret = putToDbase({cmd:'DELETE_TLN', id: em.id});
|
||||
// Meldung anzeigen
|
||||
let fdate = await getfdateformatted(em.fid);
|
||||
let meld = '<p><h1 style="text-align:center">Stornierung</h1><br />' +
|
||||
'Ihre Anmeldung für die Führung auf der Sternwarte Welzheim am<br /><br />' +
|
||||
'<div id="antwort_fdatum" style="text-align:center;">' +
|
||||
`${fdate} mit ${aktualTln.anzahl} ${aktualTln.anzahl == 1 ? "Person" : "Personen"}` +
|
||||
'</div> <br />' +
|
||||
'wurde storniert.<br /><br /> Wir haben Ihnen die Stornierungs-Bestätigung per Email zugesandt.</p>' +
|
||||
'<p><a class="button" href="/index.php">Zurück</a></p>';
|
||||
$('#mainContent').html(meld);
|
||||
$('#mainContent').css('height','430px');
|
||||
sendConfirmation(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Alle eingetragenen Teilnehmer ab 'morgen' in das globale Array einlesen
|
||||
const fetchAlleTln = async () => {
|
||||
const nxtDate = await fetchFromDbase({cmd: 'GET_DATES', anzahl: 1, fid:0});
|
||||
return await fetchFromDbase({cmd: 'GET_ALLTEILN', fid: nxtDate[0].fid});
|
||||
}
|
||||
|
||||
|
||||
async function main() {
|
||||
// Falls Storno angefordert, dann anzeigen
|
||||
if(md5Hash != "") {
|
||||
await doStorno(md5Hash);
|
||||
return;
|
||||
}
|
||||
// Alle eingetragenen Teilnehmer ab 'morgen' in das globale Array einlesen
|
||||
// die nächste 30 Führungsdaten anzeigen
|
||||
buildfuehrungdates(30);
|
||||
}
|
||||
|
||||
main().catch(console.err);
|
||||
|
||||
});
|
||||
7
sternwarte/javascript/bootstrap.min.js
vendored
7
sternwarte/javascript/bootstrap.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -1,40 +0,0 @@
|
||||
// Javascript-Part für index.php (Home-Page)
|
||||
(async function() {
|
||||
const url = 'DB4js.php';
|
||||
|
||||
console.log("komme in das Script");
|
||||
|
||||
// Von der Datenbank Werte abholen
|
||||
// Param:
|
||||
// body: Object mit cmd und param
|
||||
// Return:
|
||||
// angeforderte Daten als JSON
|
||||
const fetchFromDbase = async (body) => {
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/js'},
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
let maintenance = await fetchFromDbase({cmd: 'GET_MAINT'});
|
||||
|
||||
let dialogMaint = $('#maint').dialog({
|
||||
autoOpen: false,
|
||||
open: function () {
|
||||
$(this).load('maintenance.html', function () {
|
||||
});
|
||||
},
|
||||
width: 700,
|
||||
height: 200,
|
||||
modal: true,
|
||||
resizable: false,
|
||||
position: {my: 'center top+30%', at: 'center top+20%'},
|
||||
});
|
||||
|
||||
if (maintenance == true) {
|
||||
dialogMaint.dialog('open');
|
||||
}
|
||||
|
||||
})();
|
||||
@@ -1,184 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
|
||||
<head>
|
||||
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
|
||||
<title>Sternwarte Welzheim</title>
|
||||
|
||||
<!--[if IE 5]>
|
||||
|
||||
<link href="css/ie5.css" rel="stylesheet" type="text/css" />
|
||||
|
||||
<![endif]--><!--[if IE]>
|
||||
|
||||
<style type="text/css">
|
||||
|
||||
<link href="css/ie.css" rel="stylesheet" type="text/css" />
|
||||
|
||||
</style>
|
||||
|
||||
<![endif]-->
|
||||
|
||||
<link href="css/sternwarte1.css" rel="stylesheet" type="text/css" />
|
||||
|
||||
<script type="text/javascript" src="/javascript/jquery.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="/css/prettyPhoto.css" type="text/css" media="screen" charset="utf-8" />
|
||||
|
||||
<script src="/javascript/jquery.prettyPhoto.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
|
||||
$(document).ready(function(){
|
||||
|
||||
$("a[rel^='prettyPhoto']").prettyPhoto({
|
||||
|
||||
animationSpeed: 'normal', /* fast/slow/normal */
|
||||
|
||||
padding: 20, /* padding for each side of the picture */
|
||||
|
||||
opacity: 1, /* Value betwee 0 and 1 */
|
||||
|
||||
showTitle: false, /* true/false */
|
||||
|
||||
allowresize: true, /* true/false */
|
||||
|
||||
counter_separator_label: '/', /* The separator for the gallery counter 1 "of" 2 */
|
||||
|
||||
theme: 'dark_rounded', /* light_rounded / dark_rounded / light_square / dark_square */
|
||||
|
||||
callback: function(){}
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
</head>
|
||||
|
||||
|
||||
|
||||
<body class="thrColFixHdr">
|
||||
|
||||
|
||||
|
||||
<div id="container">
|
||||
|
||||
<?php include 'header.php'; ?>
|
||||
|
||||
<?php include 'navi.php'; ?>
|
||||
|
||||
<div id="sidebar2">
|
||||
|
||||
<?php include 'fdatum.php'; ?>
|
||||
|
||||
<?php include 'himmelerg.php'; ?>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div id="mainContent">
|
||||
|
||||
<h1>Willkommen auf der Seite der Sternwarte Welzheim!</h1>
|
||||
|
||||
<p><img src="bilder/sternwarte_winter1.jpg" width="320" height="209" alt="Sternwarte Welzheim " class="fltlft" />Die Beobachtung des Sternenhimmels ist von unseren lichtüberfluteten Städten aus kaum mehr möglich. Die Staub und Dunstglocken und der Lichtstrom künstlicher Beleuchtung haben in den letzten Jahren stark zugenommen. </p>
|
||||
|
||||
<p> Bedingt durch diese Umweltemissionen ist die Aufhellung des Nachthimmels enorm angestiegen und beeinträchtigt astronomische Beobachtungen erheblich.<br />
|
||||
|
||||
<br />
|
||||
|
||||
<a href="sternwarte.php" class="textklein90">weiter
|
||||
|
||||
...</a><br />
|
||||
|
||||
<br />
|
||||
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
<hr />
|
||||
|
||||
<!-- <h1><strong>Venustransit am 5./6. Juni 2012 -</strong> Sonderführung auf der Sternwarte Welzheim</h1>
|
||||
<p><img src="bilder/aktuell/venustransit2012.jpg" width="320" height="320" class="fltlft" />In der Nacht vom 5. auf 6. Juni 2012 findet das seltene Himmelsschauspiel eines Venustransits statt. Unser Nachbarplanet überholt die Erde auf der Innenbahn. </p>
|
||||
<p>Zur Beobachtung dieses seltenen Ereignisses bietet die Sternwarte Welzheim am Mittwoch,
|
||||
6. Juni 2012 eine Sonderführung an. Interessierte Beobachter treffen sich um 5:20 Uhr am Parkplatz der Sternwarte. </p>
|
||||
<p><a href='venustransit.php' class="textklein90">mehr</a><br />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<hr />-->
|
||||
|
||||
|
||||
|
||||
<h1>Allsky- und Wettercam an der Sternwarte Welzheim</h1>
|
||||
|
||||
<p><a href='http://www.gfpw.org/skycam/allsky.jpg' rel="prettyPhoto[sky]" title="Aktuelles Bild Allskycam an der Sternwarte Welzheim"><img src="http://www.gfpw.org/skycam/skythumb.jpg" alt="Allskycam Sternwarte Welzheim" width="320" height="240" class="fltlft" /></a>Sie sind sich nicht sicher, ob eine Führung in Welzheim stattfinden wird, weil bei Ihnen zu Hause der Himmel noch bedeckt ist. Mit unserer Allsky- Wettercam können Sie feststellen wie die Wolkenlage an der Sternwarte ist. <br />
|
||||
|
||||
<br />
|
||||
|
||||
<a href='http://www.gfpw.org/skycam/allsky.jpg' rel="prettyPhoto[pw]" title="Aktuelles Bild Allskycam an der Sternwarte Welzheim" class="textklein90">zur aktuellen Großansicht der Bildes</a></p>
|
||||
|
||||
<p> </p>
|
||||
|
||||
<p><br />
|
||||
|
||||
<br />
|
||||
|
||||
<br />
|
||||
|
||||
<br />
|
||||
|
||||
<br />
|
||||
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
<!-- <hr />
|
||||
|
||||
|
||||
|
||||
<h1>Partielle Sonnenfinsternis am 4. Januar</h1>
|
||||
|
||||
<img src="bilder/sofi201101/sofi_start.jpg" alt="Sternwarte Welzheim im Winter" width="320" height="200" class="fltlft" />Gleich zu Beginn des neuen Jahres bot uns der Himmel ein spannendes Ereignis. Am Dienstag, 4. Januar 2011 fand eine partielle Sonnenfinsternis statt, die von Deutschland aus fast in ihrer gesamten Länge in den Morgenstunden zu verfolgen war. <br />
|
||||
|
||||
<br />
|
||||
|
||||
<a href="sofi2011.php" class="textklein90"> weitere Bilder der Sonnenfinsternis
|
||||
|
||||
..</a>--><br class="clear" />
|
||||
|
||||
|
||||
|
||||
<p> </p>
|
||||
|
||||
<!-- end #mainContent --></div>
|
||||
|
||||
<!-- Dieses clear-Element sollte direkt auf das #mainContent-div folgen, um das #container-div anzuweisen, alle untergeordneten Floats aufzunehmen. -->
|
||||
|
||||
<br class="clearfloat" />
|
||||
|
||||
<!-- end #container -->
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
$(document).ready(function () {
|
||||
$("a[rel^='prettyPhoto']").prettyPhoto({
|
||||
animationSpeed: 'normal', /* fast/slow/normal */
|
||||
opacity: 1, /* Value betwee 0 and 1 */// show_title: true, /* true/false */
|
||||
allow_resize: false, /* true/false */
|
||||
theme: 'dark_square', /* light_rounded / dark_rounded / light_square / dark_square */
|
||||
social_tools:''
|
||||
});
|
||||
});
|
||||
4
sternwarte/javascript/jquery-1.11.0.min.js
vendored
4
sternwarte/javascript/jquery-1.11.0.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
sternwarte/javascript/jquery.js
vendored
1
sternwarte/javascript/jquery.js
vendored
@@ -1 +0,0 @@
|
||||
jquery-1.11.0.min.js
|
||||
File diff suppressed because one or more lines are too long
@@ -1,562 +0,0 @@
|
||||
/* ------------------------------------------------------------------------
|
||||
Class: prettyPhoto
|
||||
Use: Lightbox clone for jQuery
|
||||
Author: Stephane Caron (http://www.no-margin-for-errors.com)
|
||||
Version: 2.4.2
|
||||
------------------------------------------------------------------------- */
|
||||
|
||||
var $pp_pic_holder;
|
||||
var $ppt;
|
||||
|
||||
(function($) {
|
||||
$.fn.prettyPhoto = function(settings) {
|
||||
// global Variables
|
||||
var doresize = true;
|
||||
var percentBased = false;
|
||||
var imagesArray = [];
|
||||
var setPosition = 0; /* Position in the set */
|
||||
var pp_contentHeight;
|
||||
var pp_contentWidth;
|
||||
var pp_containerHeight;
|
||||
var pp_containerWidth;
|
||||
var pp_type = 'image';
|
||||
|
||||
// Global elements
|
||||
var $caller;
|
||||
var $scrollPos = _getScroll();
|
||||
|
||||
// Fallback to a supported theme for IE6
|
||||
if($.browser.msie && $.browser.version == 6 && (settings.theme == 'light_rounded' || settings.theme == 'dark_rounded' || settings.theme == 'dark_square')){
|
||||
settings.theme = "light_square";
|
||||
}
|
||||
|
||||
$(window).scroll(function(){ $scrollPos = _getScroll(); _centerPicture(); });
|
||||
$(window).resize(function(){ _centerPicture(); _resizeOverlay(); });
|
||||
$(document).keypress(function(e){
|
||||
switch(e.keyCode){
|
||||
case 37:
|
||||
if (setPosition == 1) return;
|
||||
changePicture('previous');
|
||||
break;
|
||||
case 39:
|
||||
if (setPosition == setCount) return;
|
||||
changePicture('next');
|
||||
break;
|
||||
case 27:
|
||||
close();
|
||||
break;
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
settings = jQuery.extend({
|
||||
animationSpeed: 'normal', /* fast/slow/normal */
|
||||
padding: 20, /* padding for each side of the picture */
|
||||
opacity: 0, /* Value between 0 and 1 */
|
||||
showTitle: false, /* true/false */
|
||||
allowresize: true, /* true/false */
|
||||
counter_separator_label: '/', /* The separator for the gallery counter 1 "of" 2 */
|
||||
theme: 'dark_square', /* light_rounded / dark_rounded / light_square / dark_square */
|
||||
callback: function(){}
|
||||
}, settings);
|
||||
|
||||
$(this).each(function(){
|
||||
var hasTitle = false;
|
||||
var isSet = false;
|
||||
var setCount = 0; /* Total images in the set */
|
||||
var arrayPosition = 0; /* Total position in the array */
|
||||
|
||||
imagesArray[imagesArray.length] = this;
|
||||
$(this).bind('click',function(){
|
||||
open(this);
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
function open(el) {
|
||||
$caller = $(el);
|
||||
|
||||
// Find out if the picture is part of a set
|
||||
theRel = $caller.attr('rel');
|
||||
galleryRegExp = /\[(?:.*)\]/;
|
||||
theGallery = galleryRegExp.exec(theRel);
|
||||
|
||||
// Calculate the number of items in the set, and the position of the clicked picture.
|
||||
isSet = false;
|
||||
setCount = 0;
|
||||
|
||||
_getFileType();
|
||||
|
||||
for (i = 0; i < imagesArray.length; i++){
|
||||
if($(imagesArray[i]).attr('rel').indexOf(theGallery) != -1){
|
||||
setCount++;
|
||||
if(setCount > 1) isSet = true;
|
||||
|
||||
if($(imagesArray[i]).attr('href') == $caller.attr('href')){
|
||||
setPosition = setCount;
|
||||
arrayPosition = i;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
_buildOverlay();
|
||||
|
||||
// Display the current position
|
||||
$pp_pic_holder.find('p.currentTextHolder').text(setPosition + settings.counter_separator_label + setCount);
|
||||
|
||||
// Position the picture in the center of the viewing area
|
||||
_centerPicture();
|
||||
|
||||
$('#pp_full_res').hide();
|
||||
$pp_pic_holder.find('.pp_loaderIcon').show();
|
||||
};
|
||||
|
||||
showimage = function(width,height,containerWidth,containerHeight,contentHeight,contentWidth,resized){
|
||||
$('.pp_loaderIcon').hide();
|
||||
|
||||
if($.browser.opera) {
|
||||
windowHeight = window.innerHeight;
|
||||
windowWidth = window.innerWidth;
|
||||
}else{
|
||||
windowHeight = $(window).height();
|
||||
windowWidth = $(window).width();
|
||||
};
|
||||
|
||||
$pp_pic_holder.find('.pp_content').animate({'height':contentHeight},settings.animationSpeed);
|
||||
|
||||
projectedTop = $scrollPos['scrollTop'] + ((windowHeight/2) - (containerHeight/2));
|
||||
if(projectedTop < 0) projectedTop = 0 + $pp_pic_holder.find('.ppt').height();
|
||||
|
||||
// Resize the holder
|
||||
$pp_pic_holder.animate({
|
||||
'top': projectedTop,
|
||||
'left': ((windowWidth/2) - (containerWidth/2)),
|
||||
'width': containerWidth
|
||||
},settings.animationSpeed,function(){
|
||||
$pp_pic_holder.width(containerWidth);
|
||||
$pp_pic_holder.find('.pp_hoverContainer,#fullResImage').height(height).width(width);
|
||||
|
||||
// Fade the new image
|
||||
$pp_pic_holder.find('#pp_full_res').fadeIn(settings.animationSpeed,function(){
|
||||
$(this).find('object,embed').css('visibility','visible');
|
||||
});
|
||||
|
||||
// Show the nav elements
|
||||
_showContent();
|
||||
|
||||
// Fade the resizing link if the image is resized
|
||||
if(resized) $('a.pp_expand,a.pp_contract').fadeIn(settings.animationSpeed);
|
||||
});
|
||||
};
|
||||
|
||||
function _showContent(){
|
||||
// Show the nav
|
||||
if(isSet && pp_type=="image") { $pp_pic_holder.find('.pp_hoverContainer').fadeIn(settings.animationSpeed); }else{ $pp_pic_holder.find('.pp_hoverContainer').hide(); }
|
||||
$pp_pic_holder.find('.pp_details').fadeIn(settings.animationSpeed);
|
||||
|
||||
// Show the title
|
||||
if(settings.showTitle && hasTitle){
|
||||
$ppt.css({
|
||||
'top' : $pp_pic_holder.offset().top - 22,
|
||||
'left' : $pp_pic_holder.offset().left + (settings.padding/2),
|
||||
'display' : 'none'
|
||||
});
|
||||
|
||||
$ppt.fadeIn(settings.animationSpeed);
|
||||
};
|
||||
}
|
||||
|
||||
function _hideContent(){
|
||||
// Fade out the current picture
|
||||
$pp_pic_holder.find('.pp_hoverContainer,.pp_details').fadeOut(settings.animationSpeed);
|
||||
$pp_pic_holder.find('#pp_full_res object,#pp_full_res embed').css('visibility','hidden');
|
||||
$pp_pic_holder.find('#pp_full_res').fadeOut(settings.animationSpeed,function(){
|
||||
$('.pp_loaderIcon').show();
|
||||
|
||||
// Preload the image
|
||||
_preload();
|
||||
});
|
||||
|
||||
// Hide the title
|
||||
$ppt.fadeOut(settings.animationSpeed);
|
||||
}
|
||||
|
||||
function changePicture(direction){
|
||||
if(direction == 'previous') {
|
||||
arrayPosition--;
|
||||
setPosition--;
|
||||
}else{
|
||||
arrayPosition++;
|
||||
setPosition++;
|
||||
};
|
||||
|
||||
// Allow the resizing of the images
|
||||
if(!doresize) doresize = true;
|
||||
|
||||
_hideContent();
|
||||
$('a.pp_expand,a.pp_contract').fadeOut(settings.animationSpeed,function(){
|
||||
$(this).removeClass('pp_contract').addClass('pp_expand');
|
||||
});
|
||||
};
|
||||
|
||||
function close(){
|
||||
$pp_pic_holder.find('object,embed').css('visibility','hidden');
|
||||
|
||||
$('div.pp_pic_holder,div.ppt').fadeOut(settings.animationSpeed);
|
||||
|
||||
$('div.pp_overlay').fadeOut(settings.animationSpeed, function(){
|
||||
$('div.pp_overlay,div.pp_pic_holder,div.ppt').remove();
|
||||
|
||||
// To fix the bug with IE select boxes
|
||||
if($.browser.msie && $.browser.version == 6){
|
||||
$('select').css('visibility','visible');
|
||||
};
|
||||
|
||||
settings.callback();
|
||||
});
|
||||
|
||||
doresize = true;
|
||||
};
|
||||
|
||||
function _checkPosition(){
|
||||
// If at the end, hide the next link
|
||||
if(setPosition == setCount) {
|
||||
$pp_pic_holder.find('a.pp_next').css('visibility','hidden');
|
||||
$pp_pic_holder.find('a.pp_arrow_next').addClass('disabled').unbind('click');
|
||||
}else{
|
||||
$pp_pic_holder.find('a.pp_next').css('visibility','visible');
|
||||
$pp_pic_holder.find('a.pp_arrow_next.disabled').removeClass('disabled').bind('click',function(){
|
||||
changePicture('next');
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
// If at the beginning, hide the previous link
|
||||
if(setPosition == 1) {
|
||||
$pp_pic_holder.find('a.pp_previous').css('visibility','hidden');
|
||||
$pp_pic_holder.find('a.pp_arrow_previous').addClass('disabled').unbind('click');
|
||||
}else{
|
||||
$pp_pic_holder.find('a.pp_previous').css('visibility','visible');
|
||||
$pp_pic_holder.find('a.pp_arrow_previous.disabled').removeClass('disabled').bind('click',function(){
|
||||
changePicture('previous');
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
// Change the current picture text
|
||||
$pp_pic_holder.find('p.currentTextHolder').text(setPosition + settings.counter_separator_label + setCount);
|
||||
|
||||
$caller = (isSet) ? $(imagesArray[arrayPosition]) : $caller;
|
||||
_getFileType();
|
||||
|
||||
if($caller.attr('title')){
|
||||
$pp_pic_holder.find('.pp_description').show().html(unescape($caller.attr('title')));
|
||||
}else{
|
||||
$pp_pic_holder.find('.pp_description').hide().text('');
|
||||
};
|
||||
|
||||
if($caller.find('img').attr('alt') && settings.showTitle){
|
||||
hasTitle = true;
|
||||
$ppt.html(unescape($caller.find('img').attr('alt')));
|
||||
}else{
|
||||
hasTitle = false;
|
||||
};
|
||||
};
|
||||
|
||||
function _fitToViewport(width,height){
|
||||
hasBeenResized = false;
|
||||
|
||||
_getDimensions(width,height);
|
||||
|
||||
// Define them in case there's no resize needed
|
||||
imageWidth = width;
|
||||
imageHeight = height;
|
||||
|
||||
windowHeight = $(window).height();
|
||||
windowWidth = $(window).width();
|
||||
|
||||
if( ((pp_containerWidth > windowWidth) || (pp_containerHeight > windowHeight)) && doresize && settings.allowresize && !percentBased) {
|
||||
hasBeenResized = true;
|
||||
notFitting = true;
|
||||
|
||||
while (notFitting){
|
||||
if((pp_containerWidth > windowWidth)){
|
||||
imageWidth = (windowWidth - 200);
|
||||
imageHeight = (height/width) * imageWidth;
|
||||
}else if((pp_containerHeight > windowHeight)){
|
||||
imageHeight = (windowHeight - 200);
|
||||
imageWidth = (width/height) * imageHeight;
|
||||
}else{
|
||||
notFitting = false;
|
||||
};
|
||||
|
||||
pp_containerHeight = imageHeight;
|
||||
pp_containerWidth = imageWidth;
|
||||
};
|
||||
|
||||
_getDimensions(imageWidth,imageHeight);
|
||||
};
|
||||
|
||||
return {
|
||||
width:imageWidth,
|
||||
height:imageHeight,
|
||||
containerHeight:pp_containerHeight,
|
||||
containerWidth:pp_containerWidth,
|
||||
contentHeight:pp_contentHeight,
|
||||
contentWidth:pp_contentWidth,
|
||||
resized:hasBeenResized
|
||||
};
|
||||
};
|
||||
|
||||
function _getDimensions(width,height){
|
||||
$pp_pic_holder.find('.pp_details').width(width).find('.pp_description').width(width - parseFloat($pp_pic_holder.find('a.pp_close').css('width'))); /* To have the correct height */
|
||||
|
||||
// Get the container size, to resize the holder to the right dimensions
|
||||
pp_contentHeight = height + $pp_pic_holder.find('.pp_details').height() + parseFloat($pp_pic_holder.find('.pp_details').css('marginTop')) + parseFloat($pp_pic_holder.find('.pp_details').css('marginBottom'));
|
||||
pp_contentWidth = width;
|
||||
pp_containerHeight = pp_contentHeight + $pp_pic_holder.find('.ppt').height() + $pp_pic_holder.find('.pp_top').height() + $pp_pic_holder.find('.pp_bottom').height();
|
||||
pp_containerWidth = width + settings.padding;
|
||||
}
|
||||
|
||||
function _getFileType(){
|
||||
if ($caller.attr('href').match(/youtube\.com\/watch/i)) {
|
||||
pp_type = 'youtube';
|
||||
}else if($caller.attr('href').indexOf('.mov') != -1){
|
||||
pp_type = 'quicktime';
|
||||
}else if($caller.attr('href').indexOf('.swf') != -1){
|
||||
pp_type = 'flash';
|
||||
}else if($caller.attr('href').indexOf('iframe') != -1){
|
||||
pp_type = 'iframe'
|
||||
}else{
|
||||
pp_type = 'image';
|
||||
}
|
||||
}
|
||||
|
||||
function _centerPicture(){
|
||||
if ($pp_pic_holder){ if($pp_pic_holder.size() == 0){ return; }}else{ return; }; //Make sure the gallery is open
|
||||
|
||||
if($.browser.opera) {
|
||||
windowHeight = window.innerHeight;
|
||||
windowWidth = window.innerWidth;
|
||||
}else{
|
||||
windowHeight = $(window).height();
|
||||
windowWidth = $(window).width();
|
||||
};
|
||||
|
||||
if(doresize) {
|
||||
$pHeight = $pp_pic_holder.height();
|
||||
$pWidth = $pp_pic_holder.width();
|
||||
$tHeight = $ppt.height();
|
||||
|
||||
projectedTop = (windowHeight/2) + $scrollPos['scrollTop'] - ($pHeight/2);
|
||||
if(projectedTop < 0) projectedTop = 0 + $tHeight;
|
||||
|
||||
$pp_pic_holder.css({
|
||||
'top': projectedTop,
|
||||
'left': (windowWidth/2) + $scrollPos['scrollLeft'] - ($pWidth/2)
|
||||
});
|
||||
|
||||
$ppt.css({
|
||||
'top' : projectedTop - $tHeight,
|
||||
'left' : (windowWidth/2) + $scrollPos['scrollLeft'] - ($pWidth/2) + (settings.padding/2)
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
function _preload(){
|
||||
// Hide the next/previous links if on first or last images.
|
||||
_checkPosition();
|
||||
|
||||
if(pp_type == 'image'){
|
||||
// Set the new image
|
||||
imgPreloader = new Image();
|
||||
|
||||
// Preload the neighbour images
|
||||
nextImage = new Image();
|
||||
if(isSet && setPosition > setCount) nextImage.src = $(imagesArray[arrayPosition + 1]).attr('href');
|
||||
prevImage = new Image();
|
||||
if(isSet && imagesArray[arrayPosition - 1]) prevImage.src = $(imagesArray[arrayPosition - 1]).attr('href');
|
||||
|
||||
pp_typeMarkup = '<img id="fullResImage" src="" />';
|
||||
$pp_pic_holder.find('#pp_full_res')[0].innerHTML = pp_typeMarkup;
|
||||
|
||||
$pp_pic_holder.find('.pp_content').css('overflow','hidden');
|
||||
$pp_pic_holder.find('#fullResImage').attr('src',$caller.attr('href'));
|
||||
|
||||
imgPreloader.onload = function(){
|
||||
var correctSizes = _fitToViewport(imgPreloader.width,imgPreloader.height);
|
||||
imgPreloader.width = correctSizes['width'];
|
||||
imgPreloader.height = correctSizes['height'];
|
||||
showimage(imgPreloader.width,imgPreloader.height,correctSizes["containerWidth"],correctSizes["containerHeight"],correctSizes["contentHeight"],correctSizes["contentWidth"],correctSizes["resized"]);
|
||||
};
|
||||
|
||||
imgPreloader.src = $caller.attr('href');
|
||||
}else{
|
||||
// Get the dimensions
|
||||
movie_width = ( parseFloat(grab_param('width',$caller.attr('href'))) ) ? grab_param('width',$caller.attr('href')) : 425;
|
||||
movie_height = ( parseFloat(grab_param('height',$caller.attr('href'))) ) ? grab_param('height',$caller.attr('href')) : 344;
|
||||
|
||||
// If the size is % based
|
||||
if(movie_width.indexOf('%') != -1 || movie_height.indexOf('%') != -1){
|
||||
movie_height = ($(window).height() * parseFloat(movie_height) / 100) - 100;
|
||||
movie_width = ($(window).width() * parseFloat(movie_width) / 100) - 100;
|
||||
parsentBased = true;
|
||||
}else{
|
||||
movie_height = parseFloat(movie_height);
|
||||
movie_width = parseFloat(movie_width);
|
||||
}
|
||||
|
||||
if(pp_type == 'quicktime'){ movie_height+=13; }
|
||||
|
||||
// Fit them to viewport
|
||||
correctSizes = _fitToViewport(movie_width,movie_height);
|
||||
|
||||
if(pp_type == 'youtube'){
|
||||
pp_typeMarkup = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="'+correctSizes['width']+'" height="'+correctSizes['height']+'"><param name="allowfullscreen" value="true" /><param name="allowscriptaccess" value="always" /><param name="movie" value="http://www.youtube.com/v/'+grab_param('v',$caller.attr('href'))+'" /><embed src="http://www.youtube.com/v/'+grab_param('v',$caller.attr('href'))+'" type="application/x-shockwave-flash" allowfullscreen="true" allowscriptaccess="always" width="'+correctSizes['width']+'" height="'+correctSizes['height']+'"></embed></object>';
|
||||
}else if(pp_type == 'quicktime'){
|
||||
pp_typeMarkup = '<object classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" codebase="http://www.apple.com/qtactivex/qtplugin.cab" height="'+correctSizes['height']+'" width="'+correctSizes['width']+'"><param name="src" value="'+$caller.attr('href')+'"><param name="autoplay" value="true"><param name="type" value="video/quicktime"><embed src="'+$caller.attr('href')+'" height="'+correctSizes['height']+'" width="'+correctSizes['width']+'" autoplay="true" type="video/quicktime" pluginspage="http://www.apple.com/quicktime/download/"></embed></object>';
|
||||
}else if(pp_type == 'flash'){
|
||||
flash_vars = $caller.attr('href');
|
||||
flash_vars = flash_vars.substring($caller.attr('href').indexOf('flashvars') + 10,$caller.attr('href').length);
|
||||
|
||||
filename = $caller.attr('href');
|
||||
filename = filename.substring(0,filename.indexOf('?'));
|
||||
|
||||
pp_typeMarkup = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="'+correctSizes['width']+'" height="'+correctSizes['height']+'"><param name="allowfullscreen" value="true" /><param name="allowscriptaccess" value="always" /><param name="movie" value="'+filename+'?'+flash_vars+'" /><embed src="'+filename+'?'+flash_vars+'" type="application/x-shockwave-flash" allowfullscreen="true" allowscriptaccess="always" width="'+correctSizes['width']+'" height="'+correctSizes['height']+'"></embed></object>';
|
||||
}else if(pp_type == 'iframe'){
|
||||
movie_url = $caller.attr('href');
|
||||
movie_url = movie_url.substr(0,movie_url.indexOf('?'));
|
||||
|
||||
pp_typeMarkup = '<iframe src ="'+movie_url+'" width="'+(correctSizes['width']-10)+'" height="'+(correctSizes['height']-10)+'" frameborder="no"></iframe>';
|
||||
}
|
||||
// Append HTML
|
||||
$pp_pic_holder.find('#pp_full_res')[0].innerHTML = pp_typeMarkup;
|
||||
|
||||
// Show content
|
||||
showimage(correctSizes['width'],correctSizes['height'],correctSizes["containerWidth"],correctSizes["containerHeight"],correctSizes["contentHeight"],correctSizes["contentWidth"],correctSizes["resized"]);
|
||||
}
|
||||
};
|
||||
|
||||
function _getScroll(){
|
||||
if (self.pageYOffset) {
|
||||
scrollTop = self.pageYOffset;
|
||||
scrollLeft = self.pageXOffset;
|
||||
} else if (document.documentElement && document.documentElement.scrollTop) { // Explorer 6 Strict
|
||||
scrollTop = document.documentElement.scrollTop;
|
||||
scrollLeft = document.documentElement.scrollLeft;
|
||||
} else if (document.body) {// all other Explorers
|
||||
scrollTop = document.body.scrollTop;
|
||||
scrollLeft = document.body.scrollLeft;
|
||||
}
|
||||
|
||||
return {scrollTop:scrollTop,scrollLeft:scrollLeft};
|
||||
};
|
||||
|
||||
function _resizeOverlay() {
|
||||
$('div.pp_overlay').css({
|
||||
'height':$(document).height(),
|
||||
'width':$(window).width()
|
||||
});
|
||||
};
|
||||
|
||||
function _buildOverlay(){
|
||||
toInject = "";
|
||||
|
||||
// Build the background overlay div
|
||||
toInject += "<div class='pp_overlay'></div>";
|
||||
|
||||
// Define the markup to append, depending on the content type.
|
||||
if(pp_type == 'image'){
|
||||
pp_typeMarkup = '<img id="fullResImage" src="" />';
|
||||
}else{
|
||||
pp_typeMarkup = '';
|
||||
}
|
||||
|
||||
// Basic HTML for the picture holder
|
||||
toInject += '<div class="pp_pic_holder"><div class="pp_top"><div class="pp_left"></div><div class="pp_middle"></div><div class="pp_right"></div></div><div class="pp_content"><a href="#" class="pp_expand" title="Expand the image">Expand</a><div class="pp_loaderIcon"></div><div class="pp_hoverContainer"><a class="pp_next" href="#">next</a><a class="pp_previous" href="#">previous</a></div><div id="pp_full_res">'+ pp_typeMarkup +'</div><div class="pp_details clearfix"><a class="pp_close" href="#">Close</a><p class="pp_description"></p><div class="pp_nav"><a href="#" class="pp_arrow_previous">Previous</a><p class="currentTextHolder">0'+settings.counter_separator_label+'0</p><a href="#" class="pp_arrow_next">Next</a></div></div></div><div class="pp_bottom"><div class="pp_left"></div><div class="pp_middle"></div><div class="pp_right"></div></div></div>';
|
||||
|
||||
// Basic html for the title holder
|
||||
toInject += '<div class="ppt"></div>';
|
||||
|
||||
$('body').append(toInject);
|
||||
|
||||
// Set my global selectors
|
||||
$pp_pic_holder = $('.pp_pic_holder');
|
||||
$ppt = $('.ppt');
|
||||
|
||||
$('div.pp_overlay').css('height',$(document).height()).bind('click',function(){
|
||||
close();
|
||||
});
|
||||
|
||||
$pp_pic_holder.css({'opacity': 0}).addClass(settings.theme);
|
||||
|
||||
$('a.pp_close').bind('click',function(){ close(); return false; });
|
||||
|
||||
$('a.pp_expand').bind('click',function(){
|
||||
$this = $(this);
|
||||
|
||||
// Expand the image
|
||||
if($this.hasClass('pp_expand')){
|
||||
$this.removeClass('pp_expand').addClass('pp_contract');
|
||||
doresize = false;
|
||||
}else{
|
||||
$this.removeClass('pp_contract').addClass('pp_expand');
|
||||
doresize = true;
|
||||
};
|
||||
|
||||
_hideContent();
|
||||
|
||||
$pp_pic_holder.find('.pp_hoverContainer, #pp_full_res, .pp_details').fadeOut(settings.animationSpeed,function(){
|
||||
_preload();
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
$pp_pic_holder.find('.pp_previous, .pp_arrow_previous').bind('click',function(){
|
||||
changePicture('previous');
|
||||
return false;
|
||||
});
|
||||
|
||||
$pp_pic_holder.find('.pp_next, .pp_arrow_next').bind('click',function(){
|
||||
changePicture('next');
|
||||
return false;
|
||||
});
|
||||
|
||||
$pp_pic_holder.find('.pp_hoverContainer').css({
|
||||
'margin-left': settings.padding/2
|
||||
});
|
||||
|
||||
// If it's not a set, hide the links
|
||||
if(!isSet) {
|
||||
$pp_pic_holder.find('.pp_hoverContainer,.pp_nav').hide();
|
||||
};
|
||||
|
||||
|
||||
// To fix the bug with IE select boxes
|
||||
if($.browser.msie && $.browser.version == 6){
|
||||
$('body').addClass('ie6');
|
||||
$('select').css('visibility','hidden');
|
||||
};
|
||||
|
||||
// Then fade it in
|
||||
$('div.pp_overlay').css('opacity',0).fadeTo(settings.animationSpeed,settings.opacity, function(){
|
||||
$pp_pic_holder.css('opacity',0).fadeIn(settings.animationSpeed,function(){
|
||||
$pp_pic_holder.attr('style','left:'+$pp_pic_holder.css('left')+';top:'+$pp_pic_holder.css('top')+';');
|
||||
_preload();
|
||||
});
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
function grab_param(name,url){
|
||||
name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
|
||||
var regexS = "[\\?&]"+name+"=([^&#]*)";
|
||||
var regex = new RegExp( regexS );
|
||||
var results = regex.exec( url );
|
||||
if( results == null )
|
||||
return "";
|
||||
else
|
||||
return results[1];
|
||||
}
|
||||
})(jQuery);
|
||||
@@ -1,911 +0,0 @@
|
||||
/* ------------------------------------------------------------------------
|
||||
Class: prettyPhoto
|
||||
Use: Lightbox clone for jQuery
|
||||
Author: Stephane Caron (http://www.no-margin-for-errors.com)
|
||||
Version: 3.1.6
|
||||
------------------------------------------------------------------------- */
|
||||
(function($) {
|
||||
$.prettyPhoto = {version: '3.1.6'};
|
||||
|
||||
$.fn.prettyPhoto = function(pp_settings) {
|
||||
pp_settings = jQuery.extend({
|
||||
hook: 'rel', /* the attribute tag to use for prettyPhoto hooks. default: 'rel'. For HTML5, use "data-rel" or similar. */
|
||||
animation_speed: 'fast', /* fast/slow/normal */
|
||||
ajaxcallback: function() {},
|
||||
slideshow: 5000, /* false OR interval time in ms */
|
||||
autoplay_slideshow: false, /* true/false */
|
||||
opacity: 0.80, /* Value between 0 and 1 */
|
||||
show_title: true, /* true/false */
|
||||
allow_resize: true, /* Resize the photos bigger than viewport. true/false */
|
||||
allow_expand: true, /* Allow the user to expand a resized image. true/false */
|
||||
default_width: 500,
|
||||
default_height: 344,
|
||||
counter_separator_label: '/', /* The separator for the gallery counter 1 "of" 2 */
|
||||
theme: 'pp_default', /* light_rounded / dark_rounded / light_square / dark_square / facebook */
|
||||
horizontal_padding: 20, /* The padding on each side of the picture */
|
||||
hideflash: false, /* Hides all the flash object on a page, set to TRUE if flash appears over prettyPhoto */
|
||||
wmode: 'opaque', /* Set the flash wmode attribute */
|
||||
autoplay: true, /* Automatically start videos: True/False */
|
||||
modal: false, /* If set to true, only the close button will close the window */
|
||||
deeplinking: true, /* Allow prettyPhoto to update the url to enable deeplinking. */
|
||||
overlay_gallery: true, /* If set to true, a gallery will overlay the fullscreen image on mouse over */
|
||||
overlay_gallery_max: 30, /* Maximum number of pictures in the overlay gallery */
|
||||
keyboard_shortcuts: true, /* Set to false if you open forms inside prettyPhoto */
|
||||
changepicturecallback: function(){}, /* Called everytime an item is shown/changed */
|
||||
callback: function(){}, /* Called when prettyPhoto is closed */
|
||||
ie6_fallback: true,
|
||||
markup: '<div class="pp_pic_holder"> \
|
||||
<div class="ppt"> </div> \
|
||||
<div class="pp_top"> \
|
||||
<div class="pp_left"></div> \
|
||||
<div class="pp_middle"></div> \
|
||||
<div class="pp_right"></div> \
|
||||
</div> \
|
||||
<div class="pp_content_container"> \
|
||||
<div class="pp_left"> \
|
||||
<div class="pp_right"> \
|
||||
<div class="pp_content"> \
|
||||
<div class="pp_loaderIcon"></div> \
|
||||
<div class="pp_fade"> \
|
||||
<a href="#" class="pp_expand" title="Expand the image">Expand</a> \
|
||||
<div class="pp_hoverContainer"> \
|
||||
<a class="pp_next" href="#">next</a> \
|
||||
<a class="pp_previous" href="#">previous</a> \
|
||||
</div> \
|
||||
<div id="pp_full_res"></div> \
|
||||
<div class="pp_details"> \
|
||||
<div class="pp_nav"> \
|
||||
<a href="#" class="pp_arrow_previous">Previous</a> \
|
||||
<p class="currentTextHolder">0/0</p> \
|
||||
<a href="#" class="pp_arrow_next">Next</a> \
|
||||
</div> \
|
||||
<p class="pp_description"></p> \
|
||||
<div class="pp_social">{pp_social}</div> \
|
||||
<a class="pp_close" href="#">Close</a> \
|
||||
</div> \
|
||||
</div> \
|
||||
</div> \
|
||||
</div> \
|
||||
</div> \
|
||||
</div> \
|
||||
<div class="pp_bottom"> \
|
||||
<div class="pp_left"></div> \
|
||||
<div class="pp_middle"></div> \
|
||||
<div class="pp_right"></div> \
|
||||
</div> \
|
||||
</div> \
|
||||
<div class="pp_overlay"></div>',
|
||||
gallery_markup: '<div class="pp_gallery"> \
|
||||
<a href="#" class="pp_arrow_previous">Previous</a> \
|
||||
<div> \
|
||||
<ul> \
|
||||
{gallery} \
|
||||
</ul> \
|
||||
</div> \
|
||||
<a href="#" class="pp_arrow_next">Next</a> \
|
||||
</div>',
|
||||
image_markup: '<img id="fullResImage" src="{path}" />',
|
||||
flash_markup: '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="{width}" height="{height}"><param name="wmode" value="{wmode}" /><param name="allowfullscreen" value="true" /><param name="allowscriptaccess" value="always" /><param name="movie" value="{path}" /><embed src="{path}" type="application/x-shockwave-flash" allowfullscreen="true" allowscriptaccess="always" width="{width}" height="{height}" wmode="{wmode}"></embed></object>',
|
||||
quicktime_markup: '<object classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" codebase="http://www.apple.com/qtactivex/qtplugin.cab" height="{height}" width="{width}"><param name="src" value="{path}"><param name="autoplay" value="{autoplay}"><param name="type" value="video/quicktime"><embed src="{path}" height="{height}" width="{width}" autoplay="{autoplay}" type="video/quicktime" pluginspage="http://www.apple.com/quicktime/download/"></embed></object>',
|
||||
iframe_markup: '<iframe src ="{path}" width="{width}" height="{height}" frameborder="no"></iframe>',
|
||||
inline_markup: '<div class="pp_inline">{content}</div>',
|
||||
custom_markup: '',
|
||||
social_tools: '<div class="twitter"><a href="http://twitter.com/share" class="twitter-share-button" data-count="none">Tweet</a><script type="text/javascript" src="http://platform.twitter.com/widgets.js"></script></div><div class="facebook"><iframe src="//www.facebook.com/plugins/like.php?locale=en_US&href={location_href}&layout=button_count&show_faces=true&width=500&action=like&font&colorscheme=light&height=23" scrolling="no" frameborder="0" style="border:none; overflow:hidden; width:500px; height:23px;" allowTransparency="true"></iframe></div>' /* html or false to disable */
|
||||
}, pp_settings);
|
||||
|
||||
// Global variables accessible only by prettyPhoto
|
||||
var matchedObjects = this, percentBased = false, pp_dimensions, pp_open,
|
||||
|
||||
// prettyPhoto container specific
|
||||
pp_contentHeight, pp_contentWidth, pp_containerHeight, pp_containerWidth,
|
||||
|
||||
// Window size
|
||||
windowHeight = $(window).height(), windowWidth = $(window).width(),
|
||||
|
||||
// Global elements
|
||||
pp_slideshow;
|
||||
|
||||
doresize = true, scroll_pos = _get_scroll();
|
||||
|
||||
// Window/Keyboard events
|
||||
$(window).unbind('resize.prettyphoto').bind('resize.prettyphoto',function(){ _center_overlay(); _resize_overlay(); });
|
||||
|
||||
if(pp_settings.keyboard_shortcuts) {
|
||||
$(document).unbind('keydown.prettyphoto').bind('keydown.prettyphoto',function(e){
|
||||
if(typeof $pp_pic_holder != 'undefined'){
|
||||
if($pp_pic_holder.is(':visible')){
|
||||
switch(e.keyCode){
|
||||
case 37:
|
||||
$.prettyPhoto.changePage('previous');
|
||||
e.preventDefault();
|
||||
break;
|
||||
case 39:
|
||||
$.prettyPhoto.changePage('next');
|
||||
e.preventDefault();
|
||||
break;
|
||||
case 27:
|
||||
if(!settings.modal)
|
||||
$.prettyPhoto.close();
|
||||
e.preventDefault();
|
||||
break;
|
||||
};
|
||||
// return false;
|
||||
};
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize prettyPhoto.
|
||||
*/
|
||||
$.prettyPhoto.initialize = function() {
|
||||
|
||||
settings = pp_settings;
|
||||
|
||||
if(settings.theme == 'pp_default') settings.horizontal_padding = 16;
|
||||
|
||||
// Find out if the picture is part of a set
|
||||
theRel = $(this).attr(settings.hook);
|
||||
galleryRegExp = /\[(?:.*)\]/;
|
||||
isSet = (galleryRegExp.exec(theRel)) ? true : false;
|
||||
|
||||
// Put the SRCs, TITLEs, ALTs into an array.
|
||||
pp_images = (isSet) ? jQuery.map(matchedObjects, function(n, i){ if($(n).attr(settings.hook).indexOf(theRel) != -1) return $(n).attr('href'); }) : $.makeArray($(this).attr('href'));
|
||||
pp_titles = (isSet) ? jQuery.map(matchedObjects, function(n, i){ if($(n).attr(settings.hook).indexOf(theRel) != -1) return ($(n).find('img').attr('alt')) ? $(n).find('img').attr('alt') : ""; }) : $.makeArray($(this).find('img').attr('alt'));
|
||||
pp_descriptions = (isSet) ? jQuery.map(matchedObjects, function(n, i){ if($(n).attr(settings.hook).indexOf(theRel) != -1) return ($(n).attr('title')) ? $(n).attr('title') : ""; }) : $.makeArray($(this).attr('title'));
|
||||
|
||||
if(pp_images.length > settings.overlay_gallery_max) settings.overlay_gallery = false;
|
||||
|
||||
set_position = jQuery.inArray($(this).attr('href'), pp_images); // Define where in the array the clicked item is positionned
|
||||
rel_index = (isSet) ? set_position : $("a["+settings.hook+"^='"+theRel+"']").index($(this));
|
||||
|
||||
_build_overlay(this); // Build the overlay {this} being the caller
|
||||
|
||||
if(settings.allow_resize)
|
||||
$(window).bind('scroll.prettyphoto',function(){ _center_overlay(); });
|
||||
|
||||
|
||||
$.prettyPhoto.open();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Opens the prettyPhoto modal box.
|
||||
* @param image {String,Array} Full path to the image to be open, can also be an array containing full images paths.
|
||||
* @param title {String,Array} The title to be displayed with the picture, can also be an array containing all the titles.
|
||||
* @param description {String,Array} The description to be displayed with the picture, can also be an array containing all the descriptions.
|
||||
*/
|
||||
$.prettyPhoto.open = function(event) {
|
||||
if(typeof settings == "undefined"){ // Means it's an API call, need to manually get the settings and set the variables
|
||||
settings = pp_settings;
|
||||
pp_images = $.makeArray(arguments[0]);
|
||||
pp_titles = (arguments[1]) ? $.makeArray(arguments[1]) : $.makeArray("");
|
||||
pp_descriptions = (arguments[2]) ? $.makeArray(arguments[2]) : $.makeArray("");
|
||||
isSet = (pp_images.length > 1) ? true : false;
|
||||
set_position = (arguments[3])? arguments[3]: 0;
|
||||
_build_overlay(event.target); // Build the overlay {this} being the caller
|
||||
}
|
||||
|
||||
if(settings.hideflash) $('object,embed,iframe[src*=youtube],iframe[src*=vimeo]').css('visibility','hidden'); // Hide the flash
|
||||
|
||||
_checkPosition($(pp_images).size()); // Hide the next/previous links if on first or last images.
|
||||
|
||||
$('.pp_loaderIcon').show();
|
||||
|
||||
if(settings.deeplinking)
|
||||
setHashtag();
|
||||
|
||||
// Rebuild Facebook Like Button with updated href
|
||||
if(settings.social_tools){
|
||||
facebook_like_link = settings.social_tools.replace('{location_href}', encodeURIComponent(location.href));
|
||||
$pp_pic_holder.find('.pp_social').html(facebook_like_link);
|
||||
}
|
||||
|
||||
// Fade the content in
|
||||
if($ppt.is(':hidden')) $ppt.css('opacity',0).show();
|
||||
$pp_overlay.show().fadeTo(settings.animation_speed,settings.opacity);
|
||||
|
||||
// Display the current position
|
||||
$pp_pic_holder.find('.currentTextHolder').text((set_position+1) + settings.counter_separator_label + $(pp_images).size());
|
||||
|
||||
// Set the description
|
||||
if(typeof pp_descriptions[set_position] != 'undefined' && pp_descriptions[set_position] != ""){
|
||||
$pp_pic_holder.find('.pp_description').show().html(unescape(pp_descriptions[set_position]));
|
||||
}else{
|
||||
$pp_pic_holder.find('.pp_description').hide();
|
||||
}
|
||||
|
||||
// Get the dimensions
|
||||
movie_width = ( parseFloat(getParam('width',pp_images[set_position])) ) ? getParam('width',pp_images[set_position]) : settings.default_width.toString();
|
||||
movie_height = ( parseFloat(getParam('height',pp_images[set_position])) ) ? getParam('height',pp_images[set_position]) : settings.default_height.toString();
|
||||
|
||||
// If the size is % based, calculate according to window dimensions
|
||||
percentBased=false;
|
||||
if(movie_height.indexOf('%') != -1) { movie_height = parseFloat(($(window).height() * parseFloat(movie_height) / 100) - 150); percentBased = true; }
|
||||
if(movie_width.indexOf('%') != -1) { movie_width = parseFloat(($(window).width() * parseFloat(movie_width) / 100) - 150); percentBased = true; }
|
||||
|
||||
// Fade the holder
|
||||
$pp_pic_holder.fadeIn(function(){
|
||||
// Set the title
|
||||
(settings.show_title && pp_titles[set_position] != "" && typeof pp_titles[set_position] != "undefined") ? $ppt.html(unescape(pp_titles[set_position])) : $ppt.html(' ');
|
||||
|
||||
imgPreloader = "";
|
||||
skipInjection = false;
|
||||
|
||||
// Inject the proper content
|
||||
switch(_getFileType(pp_images[set_position])){
|
||||
case 'image':
|
||||
imgPreloader = new Image();
|
||||
|
||||
// Preload the neighbour images
|
||||
nextImage = new Image();
|
||||
if(isSet && set_position < $(pp_images).size() -1) nextImage.src = pp_images[set_position + 1];
|
||||
prevImage = new Image();
|
||||
if(isSet && pp_images[set_position - 1]) prevImage.src = pp_images[set_position - 1];
|
||||
|
||||
$pp_pic_holder.find('#pp_full_res')[0].innerHTML = settings.image_markup.replace(/{path}/g,pp_images[set_position]);
|
||||
|
||||
imgPreloader.onload = function(){
|
||||
// Fit item to viewport
|
||||
pp_dimensions = _fitToViewport(imgPreloader.width,imgPreloader.height);
|
||||
|
||||
_showContent();
|
||||
};
|
||||
|
||||
imgPreloader.onerror = function(){
|
||||
alert('Image cannot be loaded. Make sure the path is correct and image exist.');
|
||||
$.prettyPhoto.close();
|
||||
};
|
||||
|
||||
imgPreloader.src = pp_images[set_position];
|
||||
break;
|
||||
|
||||
case 'youtube':
|
||||
pp_dimensions = _fitToViewport(movie_width,movie_height); // Fit item to viewport
|
||||
|
||||
// Regular youtube link
|
||||
movie_id = getParam('v',pp_images[set_position]);
|
||||
|
||||
// youtu.be link
|
||||
if(movie_id == ""){
|
||||
movie_id = pp_images[set_position].split('youtu.be/');
|
||||
movie_id = movie_id[1];
|
||||
if(movie_id.indexOf('?') > 0)
|
||||
movie_id = movie_id.substr(0,movie_id.indexOf('?')); // Strip anything after the ?
|
||||
|
||||
if(movie_id.indexOf('&') > 0)
|
||||
movie_id = movie_id.substr(0,movie_id.indexOf('&')); // Strip anything after the &
|
||||
}
|
||||
|
||||
movie = 'http://www.youtube.com/embed/'+movie_id;
|
||||
(getParam('rel',pp_images[set_position])) ? movie+="?rel="+getParam('rel',pp_images[set_position]) : movie+="?rel=1";
|
||||
|
||||
if(settings.autoplay) movie += "&autoplay=1";
|
||||
|
||||
toInject = settings.iframe_markup.replace(/{width}/g,pp_dimensions['width']).replace(/{height}/g,pp_dimensions['height']).replace(/{wmode}/g,settings.wmode).replace(/{path}/g,movie);
|
||||
break;
|
||||
|
||||
case 'vimeo':
|
||||
pp_dimensions = _fitToViewport(movie_width,movie_height); // Fit item to viewport
|
||||
|
||||
movie_id = pp_images[set_position];
|
||||
var regExp = /http(s?):\/\/(www\.)?vimeo.com\/(\d+)/;
|
||||
var match = movie_id.match(regExp);
|
||||
|
||||
movie = 'http://player.vimeo.com/video/'+ match[3] +'?title=0&byline=0&portrait=0';
|
||||
if(settings.autoplay) movie += "&autoplay=1;";
|
||||
|
||||
vimeo_width = pp_dimensions['width'] + '/embed/?moog_width='+ pp_dimensions['width'];
|
||||
|
||||
toInject = settings.iframe_markup.replace(/{width}/g,vimeo_width).replace(/{height}/g,pp_dimensions['height']).replace(/{path}/g,movie);
|
||||
break;
|
||||
|
||||
case 'quicktime':
|
||||
pp_dimensions = _fitToViewport(movie_width,movie_height); // Fit item to viewport
|
||||
pp_dimensions['height']+=15; pp_dimensions['contentHeight']+=15; pp_dimensions['containerHeight']+=15; // Add space for the control bar
|
||||
|
||||
toInject = settings.quicktime_markup.replace(/{width}/g,pp_dimensions['width']).replace(/{height}/g,pp_dimensions['height']).replace(/{wmode}/g,settings.wmode).replace(/{path}/g,pp_images[set_position]).replace(/{autoplay}/g,settings.autoplay);
|
||||
break;
|
||||
|
||||
case 'flash':
|
||||
pp_dimensions = _fitToViewport(movie_width,movie_height); // Fit item to viewport
|
||||
|
||||
flash_vars = pp_images[set_position];
|
||||
flash_vars = flash_vars.substring(pp_images[set_position].indexOf('flashvars') + 10,pp_images[set_position].length);
|
||||
|
||||
filename = pp_images[set_position];
|
||||
filename = filename.substring(0,filename.indexOf('?'));
|
||||
|
||||
toInject = settings.flash_markup.replace(/{width}/g,pp_dimensions['width']).replace(/{height}/g,pp_dimensions['height']).replace(/{wmode}/g,settings.wmode).replace(/{path}/g,filename+'?'+flash_vars);
|
||||
break;
|
||||
|
||||
case 'iframe':
|
||||
pp_dimensions = _fitToViewport(movie_width,movie_height); // Fit item to viewport
|
||||
|
||||
frame_url = pp_images[set_position];
|
||||
frame_url = frame_url.substr(0,frame_url.indexOf('iframe')-1);
|
||||
|
||||
toInject = settings.iframe_markup.replace(/{width}/g,pp_dimensions['width']).replace(/{height}/g,pp_dimensions['height']).replace(/{path}/g,frame_url);
|
||||
break;
|
||||
|
||||
case 'ajax':
|
||||
doresize = false; // Make sure the dimensions are not resized.
|
||||
pp_dimensions = _fitToViewport(movie_width,movie_height);
|
||||
doresize = true; // Reset the dimensions
|
||||
|
||||
skipInjection = true;
|
||||
$.get(pp_images[set_position],function(responseHTML){
|
||||
toInject = settings.inline_markup.replace(/{content}/g,responseHTML);
|
||||
$pp_pic_holder.find('#pp_full_res')[0].innerHTML = toInject;
|
||||
_showContent();
|
||||
});
|
||||
|
||||
break;
|
||||
|
||||
case 'custom':
|
||||
pp_dimensions = _fitToViewport(movie_width,movie_height); // Fit item to viewport
|
||||
|
||||
toInject = settings.custom_markup;
|
||||
break;
|
||||
|
||||
case 'inline':
|
||||
// to get the item height clone it, apply default width, wrap it in the prettyPhoto containers , then delete
|
||||
myClone = $(pp_images[set_position]).clone().append('<br clear="all" />').css({'width':settings.default_width}).wrapInner('<div id="pp_full_res"><div class="pp_inline"></div></div>').appendTo($('body')).show();
|
||||
doresize = false; // Make sure the dimensions are not resized.
|
||||
pp_dimensions = _fitToViewport($(myClone).width(),$(myClone).height());
|
||||
doresize = true; // Reset the dimensions
|
||||
$(myClone).remove();
|
||||
toInject = settings.inline_markup.replace(/{content}/g,$(pp_images[set_position]).html());
|
||||
break;
|
||||
};
|
||||
|
||||
if(!imgPreloader && !skipInjection){
|
||||
$pp_pic_holder.find('#pp_full_res')[0].innerHTML = toInject;
|
||||
|
||||
// Show content
|
||||
_showContent();
|
||||
};
|
||||
});
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Change page in the prettyPhoto modal box
|
||||
* @param direction {String} Direction of the paging, previous or next.
|
||||
*/
|
||||
$.prettyPhoto.changePage = function(direction){
|
||||
currentGalleryPage = 0;
|
||||
|
||||
if(direction == 'previous') {
|
||||
set_position--;
|
||||
if (set_position < 0) set_position = $(pp_images).size()-1;
|
||||
}else if(direction == 'next'){
|
||||
set_position++;
|
||||
if(set_position > $(pp_images).size()-1) set_position = 0;
|
||||
}else{
|
||||
set_position=direction;
|
||||
};
|
||||
|
||||
rel_index = set_position;
|
||||
|
||||
if(!doresize) doresize = true; // Allow the resizing of the images
|
||||
if(settings.allow_expand) {
|
||||
$('.pp_contract').removeClass('pp_contract').addClass('pp_expand');
|
||||
}
|
||||
|
||||
_hideContent(function(){ $.prettyPhoto.open(); });
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Change gallery page in the prettyPhoto modal box
|
||||
* @param direction {String} Direction of the paging, previous or next.
|
||||
*/
|
||||
$.prettyPhoto.changeGalleryPage = function(direction){
|
||||
if(direction=='next'){
|
||||
currentGalleryPage ++;
|
||||
|
||||
if(currentGalleryPage > totalPage) currentGalleryPage = 0;
|
||||
}else if(direction=='previous'){
|
||||
currentGalleryPage --;
|
||||
|
||||
if(currentGalleryPage < 0) currentGalleryPage = totalPage;
|
||||
}else{
|
||||
currentGalleryPage = direction;
|
||||
};
|
||||
|
||||
slide_speed = (direction == 'next' || direction == 'previous') ? settings.animation_speed : 0;
|
||||
|
||||
slide_to = currentGalleryPage * (itemsPerPage * itemWidth);
|
||||
|
||||
$pp_gallery.find('ul').animate({left:-slide_to},slide_speed);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Start the slideshow...
|
||||
*/
|
||||
$.prettyPhoto.startSlideshow = function(){
|
||||
if(typeof pp_slideshow == 'undefined'){
|
||||
$pp_pic_holder.find('.pp_play').unbind('click').removeClass('pp_play').addClass('pp_pause').click(function(){
|
||||
$.prettyPhoto.stopSlideshow();
|
||||
return false;
|
||||
});
|
||||
pp_slideshow = setInterval($.prettyPhoto.startSlideshow,settings.slideshow);
|
||||
}else{
|
||||
$.prettyPhoto.changePage('next');
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Stop the slideshow...
|
||||
*/
|
||||
$.prettyPhoto.stopSlideshow = function(){
|
||||
$pp_pic_holder.find('.pp_pause').unbind('click').removeClass('pp_pause').addClass('pp_play').click(function(){
|
||||
$.prettyPhoto.startSlideshow();
|
||||
return false;
|
||||
});
|
||||
clearInterval(pp_slideshow);
|
||||
pp_slideshow=undefined;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Closes prettyPhoto.
|
||||
*/
|
||||
$.prettyPhoto.close = function(){
|
||||
if($pp_overlay.is(":animated")) return;
|
||||
|
||||
$.prettyPhoto.stopSlideshow();
|
||||
|
||||
$pp_pic_holder.stop().find('object,embed').css('visibility','hidden');
|
||||
|
||||
$('div.pp_pic_holder,div.ppt,.pp_fade').fadeOut(settings.animation_speed,function(){ $(this).remove(); });
|
||||
|
||||
$pp_overlay.fadeOut(settings.animation_speed, function(){
|
||||
|
||||
if(settings.hideflash) $('object,embed,iframe[src*=youtube],iframe[src*=vimeo]').css('visibility','visible'); // Show the flash
|
||||
|
||||
$(this).remove(); // No more need for the prettyPhoto markup
|
||||
|
||||
$(window).unbind('scroll.prettyphoto');
|
||||
|
||||
clearHashtag();
|
||||
|
||||
settings.callback();
|
||||
|
||||
doresize = true;
|
||||
|
||||
pp_open = false;
|
||||
|
||||
delete settings;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the proper sizes on the containers and animate the content in.
|
||||
*/
|
||||
function _showContent(){
|
||||
$('.pp_loaderIcon').hide();
|
||||
|
||||
// Calculate the opened top position of the pic holder
|
||||
projectedTop = scroll_pos['scrollTop'] + ((windowHeight/2) - (pp_dimensions['containerHeight']/2));
|
||||
if(projectedTop < 0) projectedTop = 0;
|
||||
|
||||
$ppt.fadeTo(settings.animation_speed,1);
|
||||
|
||||
// Resize the content holder
|
||||
$pp_pic_holder.find('.pp_content')
|
||||
.animate({
|
||||
height:pp_dimensions['contentHeight'],
|
||||
width:pp_dimensions['contentWidth']
|
||||
},settings.animation_speed);
|
||||
|
||||
// Resize picture the holder
|
||||
$pp_pic_holder.animate({
|
||||
'top': projectedTop,
|
||||
'left': ((windowWidth/2) - (pp_dimensions['containerWidth']/2) < 0) ? 0 : (windowWidth/2) - (pp_dimensions['containerWidth']/2),
|
||||
width:pp_dimensions['containerWidth']
|
||||
},settings.animation_speed,function(){
|
||||
$pp_pic_holder.find('.pp_hoverContainer,#fullResImage').height(pp_dimensions['height']).width(pp_dimensions['width']);
|
||||
|
||||
$pp_pic_holder.find('.pp_fade').fadeIn(settings.animation_speed); // Fade the new content
|
||||
|
||||
// Show the nav
|
||||
if(isSet && _getFileType(pp_images[set_position])=="image") { $pp_pic_holder.find('.pp_hoverContainer').show(); }else{ $pp_pic_holder.find('.pp_hoverContainer').hide(); }
|
||||
|
||||
if(settings.allow_expand) {
|
||||
if(pp_dimensions['resized']){ // Fade the resizing link if the image is resized
|
||||
$('a.pp_expand,a.pp_contract').show();
|
||||
}else{
|
||||
$('a.pp_expand').hide();
|
||||
}
|
||||
}
|
||||
|
||||
if(settings.autoplay_slideshow && !pp_slideshow && !pp_open) $.prettyPhoto.startSlideshow();
|
||||
|
||||
settings.changepicturecallback(); // Callback!
|
||||
|
||||
pp_open = true;
|
||||
});
|
||||
|
||||
_insert_gallery();
|
||||
pp_settings.ajaxcallback();
|
||||
};
|
||||
|
||||
/**
|
||||
* Hide the content...DUH!
|
||||
*/
|
||||
function _hideContent(callback){
|
||||
// Fade out the current picture
|
||||
$pp_pic_holder.find('#pp_full_res object,#pp_full_res embed').css('visibility','hidden');
|
||||
$pp_pic_holder.find('.pp_fade').fadeOut(settings.animation_speed,function(){
|
||||
$('.pp_loaderIcon').show();
|
||||
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Check the item position in the gallery array, hide or show the navigation links
|
||||
* @param setCount {integer} The total number of items in the set
|
||||
*/
|
||||
function _checkPosition(setCount){
|
||||
(setCount > 1) ? $('.pp_nav').show() : $('.pp_nav').hide(); // Hide the bottom nav if it's not a set.
|
||||
};
|
||||
|
||||
/**
|
||||
* Resize the item dimensions if it's bigger than the viewport
|
||||
* @param width {integer} Width of the item to be opened
|
||||
* @param height {integer} Height of the item to be opened
|
||||
* @return An array containin the "fitted" dimensions
|
||||
*/
|
||||
function _fitToViewport(width,height){
|
||||
resized = false;
|
||||
|
||||
_getDimensions(width,height);
|
||||
|
||||
// Define them in case there's no resize needed
|
||||
imageWidth = width, imageHeight = height;
|
||||
|
||||
if( ((pp_containerWidth > windowWidth) || (pp_containerHeight > windowHeight)) && doresize && settings.allow_resize && !percentBased) {
|
||||
resized = true, fitting = false;
|
||||
|
||||
while (!fitting){
|
||||
if((pp_containerWidth > windowWidth)){
|
||||
imageWidth = (windowWidth - 200);
|
||||
imageHeight = (height/width) * imageWidth;
|
||||
}else if((pp_containerHeight > windowHeight)){
|
||||
imageHeight = (windowHeight - 200);
|
||||
imageWidth = (width/height) * imageHeight;
|
||||
}else{
|
||||
fitting = true;
|
||||
};
|
||||
|
||||
pp_containerHeight = imageHeight, pp_containerWidth = imageWidth;
|
||||
};
|
||||
|
||||
|
||||
|
||||
if((pp_containerWidth > windowWidth) || (pp_containerHeight > windowHeight)){
|
||||
_fitToViewport(pp_containerWidth,pp_containerHeight)
|
||||
};
|
||||
|
||||
_getDimensions(imageWidth,imageHeight);
|
||||
};
|
||||
|
||||
return {
|
||||
width:Math.floor(imageWidth),
|
||||
height:Math.floor(imageHeight),
|
||||
containerHeight:Math.floor(pp_containerHeight),
|
||||
containerWidth:Math.floor(pp_containerWidth) + (settings.horizontal_padding * 2),
|
||||
contentHeight:Math.floor(pp_contentHeight),
|
||||
contentWidth:Math.floor(pp_contentWidth),
|
||||
resized:resized
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the containers dimensions according to the item size
|
||||
* @param width {integer} Width of the item to be opened
|
||||
* @param height {integer} Height of the item to be opened
|
||||
*/
|
||||
function _getDimensions(width,height){
|
||||
width = parseFloat(width);
|
||||
height = parseFloat(height);
|
||||
|
||||
// Get the details height, to do so, I need to clone it since it's invisible
|
||||
$pp_details = $pp_pic_holder.find('.pp_details');
|
||||
$pp_details.width(width);
|
||||
detailsHeight = parseFloat($pp_details.css('marginTop')) + parseFloat($pp_details.css('marginBottom'));
|
||||
|
||||
$pp_details = $pp_details.clone().addClass(settings.theme).width(width).appendTo($('body')).css({
|
||||
'position':'absolute',
|
||||
'top':-10000
|
||||
});
|
||||
detailsHeight += $pp_details.height();
|
||||
detailsHeight = (detailsHeight <= 34) ? 36 : detailsHeight; // Min-height for the details
|
||||
$pp_details.remove();
|
||||
|
||||
// Get the titles height, to do so, I need to clone it since it's invisible
|
||||
$pp_title = $pp_pic_holder.find('.ppt');
|
||||
$pp_title.width(width);
|
||||
titleHeight = parseFloat($pp_title.css('marginTop')) + parseFloat($pp_title.css('marginBottom'));
|
||||
$pp_title = $pp_title.clone().appendTo($('body')).css({
|
||||
'position':'absolute',
|
||||
'top':-10000
|
||||
});
|
||||
titleHeight += $pp_title.height();
|
||||
$pp_title.remove();
|
||||
|
||||
// Get the container size, to resize the holder to the right dimensions
|
||||
pp_contentHeight = height + detailsHeight;
|
||||
pp_contentWidth = width;
|
||||
pp_containerHeight = pp_contentHeight + titleHeight + $pp_pic_holder.find('.pp_top').height() + $pp_pic_holder.find('.pp_bottom').height();
|
||||
pp_containerWidth = width;
|
||||
}
|
||||
|
||||
function _getFileType(itemSrc){
|
||||
if (itemSrc.match(/youtube\.com\/watch/i) || itemSrc.match(/youtu\.be/i)) {
|
||||
return 'youtube';
|
||||
}else if (itemSrc.match(/vimeo\.com/i)) {
|
||||
return 'vimeo';
|
||||
}else if(itemSrc.match(/\b.mov\b/i)){
|
||||
return 'quicktime';
|
||||
}else if(itemSrc.match(/\b.swf\b/i)){
|
||||
return 'flash';
|
||||
}else if(itemSrc.match(/\biframe=true\b/i)){
|
||||
return 'iframe';
|
||||
}else if(itemSrc.match(/\bajax=true\b/i)){
|
||||
return 'ajax';
|
||||
}else if(itemSrc.match(/\bcustom=true\b/i)){
|
||||
return 'custom';
|
||||
}else if(itemSrc.substr(0,1) == '#'){
|
||||
return 'inline';
|
||||
}else{
|
||||
return 'image';
|
||||
};
|
||||
};
|
||||
|
||||
function _center_overlay(){
|
||||
if(doresize && typeof $pp_pic_holder != 'undefined') {
|
||||
scroll_pos = _get_scroll();
|
||||
contentHeight = $pp_pic_holder.height(), contentwidth = $pp_pic_holder.width();
|
||||
|
||||
projectedTop = (windowHeight/2) + scroll_pos['scrollTop'] - (contentHeight/2);
|
||||
if(projectedTop < 0) projectedTop = 0;
|
||||
|
||||
if(contentHeight > windowHeight)
|
||||
return;
|
||||
|
||||
$pp_pic_holder.css({
|
||||
'top': projectedTop,
|
||||
'left': (windowWidth/2) + scroll_pos['scrollLeft'] - (contentwidth/2)
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
function _get_scroll(){
|
||||
if (self.pageYOffset) {
|
||||
return {scrollTop:self.pageYOffset,scrollLeft:self.pageXOffset};
|
||||
} else if (document.documentElement && document.documentElement.scrollTop) { // Explorer 6 Strict
|
||||
return {scrollTop:document.documentElement.scrollTop,scrollLeft:document.documentElement.scrollLeft};
|
||||
} else if (document.body) {// all other Explorers
|
||||
return {scrollTop:document.body.scrollTop,scrollLeft:document.body.scrollLeft};
|
||||
};
|
||||
};
|
||||
|
||||
function _resize_overlay() {
|
||||
windowHeight = $(window).height(), windowWidth = $(window).width();
|
||||
|
||||
if(typeof $pp_overlay != "undefined") $pp_overlay.height($(document).height()).width(windowWidth);
|
||||
};
|
||||
|
||||
function _insert_gallery(){
|
||||
if(isSet && settings.overlay_gallery && _getFileType(pp_images[set_position])=="image") {
|
||||
itemWidth = 52+5; // 52 beign the thumb width, 5 being the right margin.
|
||||
navWidth = (settings.theme == "facebook" || settings.theme == "pp_default") ? 50 : 30; // Define the arrow width depending on the theme
|
||||
|
||||
itemsPerPage = Math.floor((pp_dimensions['containerWidth'] - 100 - navWidth) / itemWidth);
|
||||
itemsPerPage = (itemsPerPage < pp_images.length) ? itemsPerPage : pp_images.length;
|
||||
totalPage = Math.ceil(pp_images.length / itemsPerPage) - 1;
|
||||
|
||||
// Hide the nav in the case there's no need for links
|
||||
if(totalPage == 0){
|
||||
navWidth = 0; // No nav means no width!
|
||||
$pp_gallery.find('.pp_arrow_next,.pp_arrow_previous').hide();
|
||||
}else{
|
||||
$pp_gallery.find('.pp_arrow_next,.pp_arrow_previous').show();
|
||||
};
|
||||
|
||||
galleryWidth = itemsPerPage * itemWidth;
|
||||
fullGalleryWidth = pp_images.length * itemWidth;
|
||||
|
||||
// Set the proper width to the gallery items
|
||||
$pp_gallery
|
||||
.css('margin-left',-((galleryWidth/2) + (navWidth/2)))
|
||||
.find('div:first').width(galleryWidth+5)
|
||||
.find('ul').width(fullGalleryWidth)
|
||||
.find('li.selected').removeClass('selected');
|
||||
|
||||
goToPage = (Math.floor(set_position/itemsPerPage) < totalPage) ? Math.floor(set_position/itemsPerPage) : totalPage;
|
||||
|
||||
$.prettyPhoto.changeGalleryPage(goToPage);
|
||||
|
||||
$pp_gallery_li.filter(':eq('+set_position+')').addClass('selected');
|
||||
}else{
|
||||
$pp_pic_holder.find('.pp_content').unbind('mouseenter mouseleave');
|
||||
// $pp_gallery.hide();
|
||||
}
|
||||
}
|
||||
|
||||
function _build_overlay(caller){
|
||||
// Inject Social Tool markup into General markup
|
||||
if(settings.social_tools)
|
||||
facebook_like_link = settings.social_tools.replace('{location_href}', encodeURIComponent(location.href));
|
||||
|
||||
settings.markup = settings.markup.replace('{pp_social}','');
|
||||
|
||||
$('body').append(settings.markup); // Inject the markup
|
||||
|
||||
$pp_pic_holder = $('.pp_pic_holder') , $ppt = $('.ppt'), $pp_overlay = $('div.pp_overlay'); // Set my global selectors
|
||||
|
||||
// Inject the inline gallery!
|
||||
if(isSet && settings.overlay_gallery) {
|
||||
currentGalleryPage = 0;
|
||||
toInject = "";
|
||||
for (var i=0; i < pp_images.length; i++) {
|
||||
if(!pp_images[i].match(/\b(jpg|jpeg|png|gif)\b/gi)){
|
||||
classname = 'default';
|
||||
img_src = '';
|
||||
}else{
|
||||
classname = '';
|
||||
img_src = pp_images[i];
|
||||
}
|
||||
toInject += "<li class='"+classname+"'><a href='#'><img src='" + img_src + "' width='50' alt='' /></a></li>";
|
||||
};
|
||||
|
||||
toInject = settings.gallery_markup.replace(/{gallery}/g,toInject);
|
||||
|
||||
$pp_pic_holder.find('#pp_full_res').after(toInject);
|
||||
|
||||
$pp_gallery = $('.pp_pic_holder .pp_gallery'), $pp_gallery_li = $pp_gallery.find('li'); // Set the gallery selectors
|
||||
|
||||
$pp_gallery.find('.pp_arrow_next').click(function(){
|
||||
$.prettyPhoto.changeGalleryPage('next');
|
||||
$.prettyPhoto.stopSlideshow();
|
||||
return false;
|
||||
});
|
||||
|
||||
$pp_gallery.find('.pp_arrow_previous').click(function(){
|
||||
$.prettyPhoto.changeGalleryPage('previous');
|
||||
$.prettyPhoto.stopSlideshow();
|
||||
return false;
|
||||
});
|
||||
|
||||
$pp_pic_holder.find('.pp_content').hover(
|
||||
function(){
|
||||
$pp_pic_holder.find('.pp_gallery:not(.disabled)').fadeIn();
|
||||
},
|
||||
function(){
|
||||
$pp_pic_holder.find('.pp_gallery:not(.disabled)').fadeOut();
|
||||
});
|
||||
|
||||
itemWidth = 52+5; // 52 beign the thumb width, 5 being the right margin.
|
||||
$pp_gallery_li.each(function(i){
|
||||
$(this)
|
||||
.find('a')
|
||||
.click(function(){
|
||||
$.prettyPhoto.changePage(i);
|
||||
$.prettyPhoto.stopSlideshow();
|
||||
return false;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// Inject the play/pause if it's a slideshow
|
||||
if(settings.slideshow){
|
||||
$pp_pic_holder.find('.pp_nav').prepend('<a href="#" class="pp_play">Play</a>')
|
||||
$pp_pic_holder.find('.pp_nav .pp_play').click(function(){
|
||||
$.prettyPhoto.startSlideshow();
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
$pp_pic_holder.attr('class','pp_pic_holder ' + settings.theme); // Set the proper theme
|
||||
|
||||
$pp_overlay
|
||||
.css({
|
||||
'opacity':0,
|
||||
'height':$(document).height(),
|
||||
'width':$(window).width()
|
||||
})
|
||||
.bind('click',function(){
|
||||
if(!settings.modal) $.prettyPhoto.close();
|
||||
});
|
||||
|
||||
$('a.pp_close').bind('click',function(){ $.prettyPhoto.close(); return false; });
|
||||
|
||||
|
||||
if(settings.allow_expand) {
|
||||
$('a.pp_expand').bind('click',function(e){
|
||||
// Expand the image
|
||||
if($(this).hasClass('pp_expand')){
|
||||
$(this).removeClass('pp_expand').addClass('pp_contract');
|
||||
doresize = false;
|
||||
}else{
|
||||
$(this).removeClass('pp_contract').addClass('pp_expand');
|
||||
doresize = true;
|
||||
};
|
||||
|
||||
_hideContent(function(){ $.prettyPhoto.open(); });
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
$pp_pic_holder.find('.pp_previous, .pp_nav .pp_arrow_previous').bind('click',function(){
|
||||
$.prettyPhoto.changePage('previous');
|
||||
$.prettyPhoto.stopSlideshow();
|
||||
return false;
|
||||
});
|
||||
|
||||
$pp_pic_holder.find('.pp_next, .pp_nav .pp_arrow_next').bind('click',function(){
|
||||
$.prettyPhoto.changePage('next');
|
||||
$.prettyPhoto.stopSlideshow();
|
||||
return false;
|
||||
});
|
||||
|
||||
_center_overlay(); // Center it
|
||||
};
|
||||
|
||||
if(!pp_alreadyInitialized && getHashtag()){
|
||||
pp_alreadyInitialized = true;
|
||||
|
||||
// Grab the rel index to trigger the click on the correct element
|
||||
hashIndex = getHashtag();
|
||||
hashRel = hashIndex;
|
||||
hashIndex = hashIndex.substring(hashIndex.indexOf('/')+1,hashIndex.length-1);
|
||||
hashRel = hashRel.substring(0,hashRel.indexOf('/'));
|
||||
|
||||
// Little timeout to make sure all the prettyPhoto initialize scripts has been run.
|
||||
// Useful in the event the page contain several init scripts.
|
||||
setTimeout(function(){ $("a["+pp_settings.hook+"^='"+hashRel+"']:eq("+hashIndex+")").trigger('click'); },50);
|
||||
}
|
||||
|
||||
return this.unbind('click.prettyphoto').bind('click.prettyphoto',$.prettyPhoto.initialize); // Return the jQuery object for chaining. The unbind method is used to avoid click conflict when the plugin is called more than once
|
||||
};
|
||||
|
||||
function getHashtag(){
|
||||
var url = location.href;
|
||||
hashtag = (url.indexOf('#prettyPhoto') !== -1) ? decodeURI(url.substring(url.indexOf('#prettyPhoto')+1,url.length)) : false;
|
||||
if(hashtag){ hashtag = hashtag.replace(/<|>/g,''); }
|
||||
return hashtag;
|
||||
};
|
||||
|
||||
function setHashtag(){
|
||||
if(typeof theRel == 'undefined') return; // theRel is set on normal calls, it's impossible to deeplink using the API
|
||||
location.hash = theRel + '/'+rel_index+'/';
|
||||
};
|
||||
|
||||
function clearHashtag(){
|
||||
if ( location.href.indexOf('#prettyPhoto') !== -1 ) location.hash = "prettyPhoto";
|
||||
}
|
||||
|
||||
function getParam(name,url){
|
||||
name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
|
||||
var regexS = "[\\?&]"+name+"=([^&#]*)";
|
||||
var regex = new RegExp( regexS );
|
||||
var results = regex.exec( url );
|
||||
return ( results == null ) ? "" : results[1];
|
||||
}
|
||||
|
||||
})(jQuery);
|
||||
|
||||
var pp_alreadyInitialized = false; // Used for the deep linking to make sure not to call the same function several times.
|
||||
2
sternwarte/javascript/moment.min.js
vendored
2
sternwarte/javascript/moment.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -1,33 +0,0 @@
|
||||
// Dialog zur Anzeige der Datenschutz-Erklärung
|
||||
|
||||
$(document).ready(function() {
|
||||
|
||||
// Klick auf den Anleitungs-Button
|
||||
$('#dschu').click(function() {
|
||||
$("#datenschutz").dialog('open');
|
||||
return false;
|
||||
});
|
||||
|
||||
|
||||
$("#datenschutz").dialog({
|
||||
autoOpen: false,
|
||||
width: 800,
|
||||
modal: true,
|
||||
position: 'center',
|
||||
title: 'Datenschutz',
|
||||
open:
|
||||
function() {
|
||||
$(this).load('datenschutztext.php');
|
||||
},
|
||||
buttons: [
|
||||
{
|
||||
text: "Schließen",
|
||||
click : function() {
|
||||
$(this).dialog("close");
|
||||
},
|
||||
width: 150,
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,100 +0,0 @@
|
||||
// sternwarte.js rxf 2020-09-25
|
||||
// Diverse Hilfsroutinen für die einzelnen Seiten
|
||||
//
|
||||
|
||||
$(document).ready(() => {
|
||||
|
||||
let maintenance = false;
|
||||
|
||||
// Optionen für die Anzeige der Fotos
|
||||
// $("a[rel^='prettyPhoto']").prettyPhoto({
|
||||
// animationSpeed: 'normal', /* fast/slow/normal */
|
||||
// padding: 20, /* padding for each side of the picture */
|
||||
// opacity: 1, /* Value betwee 0 and 1 */
|
||||
// showTitle: false, /* true/false */
|
||||
// allowresize: true, /* true/false */
|
||||
// counter_separator_label: '/', /* The separator for the gallery counter 1 "of" 2 */
|
||||
// theme: 'dark_rounded', /* light_rounded / dark_rounded / light_square / dark_square */
|
||||
// callback: function () {
|
||||
// }
|
||||
// });
|
||||
|
||||
// Klick auf den Datenschutz-Button
|
||||
$('#dschu').click(function() {
|
||||
$("#datenschutz").dialog('open');
|
||||
return false;
|
||||
});
|
||||
|
||||
// Anzeige der Datenschutzerklärung
|
||||
$("#datenschutz").dialog({
|
||||
autoOpen: false,
|
||||
width: 800,
|
||||
modal: true,
|
||||
position: 'center',
|
||||
title: 'Datenschutz',
|
||||
open:
|
||||
function() {
|
||||
$(this).load('datenschutztext.html');
|
||||
},
|
||||
buttons: [
|
||||
{
|
||||
text: "Schließen",
|
||||
click : function() {
|
||||
$(this).dialog("close");
|
||||
},
|
||||
width: 150,
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
// Click auf 'Zeiten' auf der HOME-Page: Dialogbox öffnen
|
||||
$('a[href="#zeiten"]').click(() => {
|
||||
$("#fzeiten").dialog('open');
|
||||
return false;
|
||||
});
|
||||
|
||||
// Anzeige der Führungszeiten
|
||||
$("#fzeiten").dialog({
|
||||
autoOpen: false,
|
||||
width: 800,
|
||||
modal: true,
|
||||
position: 'center',
|
||||
title: 'Führungszeiten',
|
||||
open:
|
||||
function() {
|
||||
$(this).load('fuehrungen_txt.html');
|
||||
},
|
||||
buttons: [
|
||||
{
|
||||
text: "Schließen",
|
||||
click : function() {
|
||||
$(this).dialog("close");
|
||||
},
|
||||
width: 150,
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
// ggf. den Maintenance-Dialog einblenden
|
||||
var dialogMaint = $('#maintdialog').dialog({
|
||||
autoOpen:false,
|
||||
closeOnEscape: false,
|
||||
open: function(event, ui) {
|
||||
$(".ui-dialog-titlebar-close").hide();
|
||||
$(this).load('maint.html', function() {
|
||||
});
|
||||
},
|
||||
width:800,
|
||||
modal: true,
|
||||
resizable:false,
|
||||
position: {my:'center top+30%', at: 'center top+30%'},
|
||||
});
|
||||
|
||||
if (maintenance == true) {
|
||||
dialogMaint.dialog('open');
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
|
||||
@@ -47,3 +47,7 @@ body {
|
||||
#beonamen {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
.fc {
|
||||
--fc-today-bg-color: rgba(129, 222, 201, 0.75);
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
<?php
|
||||
|
||||
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use PHPMailer\PHPMailer\Exception;
|
||||
use PHPMailer\PHPMailer\SMTP;
|
||||
|
||||
require 'vendor/autoload.php';
|
||||
|
||||
@@ -12,47 +11,89 @@ function sendmail($subject, $from, $body, $cc=[], $bcc=[], $to=[]) {
|
||||
$ret = [];
|
||||
$ret['error'] = false;
|
||||
|
||||
$develop = 'true';
|
||||
|
||||
$mail = new PHPMailer();
|
||||
$mail->CharSet = 'utf-8';
|
||||
$mail = new PHPMailer(true);
|
||||
|
||||
try {
|
||||
// Debug-Einstellungen
|
||||
$mail->SMTPDebug = SMTP::DEBUG_SERVER;
|
||||
$mail->Debugoutput = function($str, $level) {
|
||||
file_put_contents(__DIR__ . '/phpmailer_debug.log',
|
||||
date('Y-m-d H:i:s') . " [Level $level] $str\n", FILE_APPEND);
|
||||
};
|
||||
|
||||
// Basis-Einstellungen
|
||||
$mail->CharSet = 'UTF-8';
|
||||
$mail->isSMTP();
|
||||
|
||||
if ($develop == 'true') {
|
||||
$mail->Host = 'mailhog';
|
||||
$mail->Port = 1025;
|
||||
$mail->SMTPAuth = false;
|
||||
} else {
|
||||
// GMX Einstellungen
|
||||
$mail->Host = 'smtp.gmx.com'; // ✅ Korrekter Host
|
||||
$mail->Port = 465;
|
||||
$mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
|
||||
$mail->SMTPAuth = true;
|
||||
$mail->Host = "sslout.df.eu";
|
||||
$mail->Port = "465";
|
||||
$mail->SMTPSecure = "ssl";
|
||||
$mail->Username = "sonderfuehrung@sternwarte-welzheim.de";
|
||||
$mail->Password = "v|kR9D8m}K";
|
||||
$mail->Username = 'sternwarte.welzheim@gmx.de';
|
||||
$mail->Password = '4NT&%nH9&5wz'; // ✅ Tippfehler korrigiert
|
||||
|
||||
// Optional: Zusätzliche Authentifizierung
|
||||
$mail->AuthType = 'LOGIN';
|
||||
|
||||
// Optional: Timeout erhöhen
|
||||
$mail->Timeout = 60;
|
||||
}
|
||||
$mail->setFrom($from);
|
||||
|
||||
// Absender
|
||||
$mail->setFrom('sternwarte.welzheim@gmx.de', 'Sternwarte-Welzheim');
|
||||
|
||||
// Empfänger
|
||||
if (count($to) != 0) {
|
||||
foreach ($to as $t) {
|
||||
$mail->addAddress($t);
|
||||
}
|
||||
}
|
||||
$mail->Subject = $subject;
|
||||
$mail->Body = $body;
|
||||
|
||||
// CC
|
||||
if (count($cc) != 0) {
|
||||
foreach ($cc as $c) {
|
||||
$mail->addCC($c);
|
||||
}
|
||||
}
|
||||
if(count($bcc) != 0) {
|
||||
|
||||
// BCC
|
||||
if (count($bcc) != 0) {
|
||||
foreach ($bcc as $bc) {
|
||||
if ($develop == 'true') {
|
||||
$mail->addCC($bc);
|
||||
} else {
|
||||
$mail->addBCC($bc);
|
||||
}
|
||||
}
|
||||
$mail->addReplyTo($from);
|
||||
|
||||
if (!$mail->send()) {
|
||||
$ret['error'] = true;
|
||||
$ret['errortext'] = $mail->ErrorInfo;
|
||||
}
|
||||
|
||||
// Reply-To
|
||||
if (!empty($from)) {
|
||||
$mail->addReplyTo($from);
|
||||
}
|
||||
|
||||
// Inhalt
|
||||
$mail->Subject = $subject;
|
||||
$mail->isHTML(false); // Oder true, je nach Bedarf
|
||||
$mail->Body = $body;
|
||||
|
||||
// Senden
|
||||
$mail->send();
|
||||
$ret['oktext'] = 'Mail erfolgreich versendet';
|
||||
|
||||
} catch (Exception $e) {
|
||||
$ret['error'] = true;
|
||||
$ret['errortext'] = "Mailer Error: {$mail->ErrorInfo}";
|
||||
error_log("PHPMailer Error: " . $e->getMessage());
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
@@ -252,8 +252,6 @@
|
||||
"$stern_vorname $stern_name um $stern_zeit Uhr " .
|
||||
"für $stern_teil $person." .
|
||||
"\r\n\r\n" .
|
||||
"Bitte bringen Sie diese Bestätigung als Ausdruck oder digital zur Führung mit. \r\n".
|
||||
"Ohne diese Bestätigung erfolgt ausnahmslos k e i n Einlass.\r\n\r\n" .
|
||||
"Die Führung findet NUR bei klarem Himmel statt. Falls der Himmel bedeckt ist " .
|
||||
"und die Führung ausfällt, erhalten Sie bis spätestens eine Stunde vor Beginn der Finsternis " .
|
||||
"eine Email.\r\n\r\n" .
|
||||
@@ -269,8 +267,6 @@
|
||||
"auf der Sternwarte Welzheim für</p>" .
|
||||
"<p style='text-align: center;'>$stern_vorname $stern_name um $stern_zeit Uhr " .
|
||||
"für $stern_teil $person.</p>" .
|
||||
"<p>Bitte bringen Sie diese Bestätigung als Ausdruck oder digital zur Führung mit.<br />".
|
||||
"Ohne diese Bestätigung erfolgt ausnahmslos <strong>kein</strong> Einlass.</p>" .
|
||||
"<p>Die Führung findet <strong>nur</strong> bei klarem Himmel statt. Falls der Himmel bedeckt ist " .
|
||||
"und die Führung ausfällt, erhalten Sie bis spätestens eine Stunde vor Beginn der Finsternis " .
|
||||
"eine Email.<p>" .
|
||||
@@ -357,8 +353,7 @@
|
||||
</p>
|
||||
<p>
|
||||
Wenn Sie alle Felder ausgefüllt und abgeschickt haben (mit dem "Anmeldung senden"-Knopf),
|
||||
erhalten Sie eine Anmeldebestätigung per e-mail. Diese bitte unbedingt zur Führung
|
||||
ausgedruckt oder in digitaler Form mitbringen!
|
||||
erhalten Sie eine Anmeldebestätigung per e-mail.
|
||||
|
||||
<div style="text-align:center;"><strong>Ohne die mitgebrachte Anmeldebestätigung erfolgt ausnahmslos
|
||||
k e i n Einlass.</strong>
|
||||
@@ -472,7 +467,7 @@
|
||||
<p>
|
||||
Hinweis zum Datenschutz: <a href="" id="dschu">Datenschutzerklärung</a>
|
||||
</p>
|
||||
<p class="lastchange">Letzte Änderungen: 2022-08-17 rxf</p>
|
||||
<p class="lastchange">Letzte Änderungen: 2025-12-01 rxf</p>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -269,6 +269,11 @@ einer unserer freien Mitarbeiter/-innen die Sonderführung außerhalb unserer
|
||||
<p>
|
||||
<a name="form"> </a><?PHP echo $stern_error_msg ?></p>
|
||||
<p>
|
||||
<p class="sondermeldung">
|
||||
Unsere Sternwarte bleibt bis voraussichtlich 14. November 2025 wegen neuer technischer Einrichtung
|
||||
geschlossen. <br />Bis dahin können keine Führungen durchgeführt werden.
|
||||
</p>
|
||||
|
||||
<strong>Unverbindliche Anfrage zu einer Sonderführung auf der
|
||||
Sternwarte Welzheim</strong><br/>
|
||||
<div id="formular" class="fltrt">
|
||||
@@ -436,6 +441,7 @@ einer unserer freien Mitarbeiter/-innen die Sonderführung außerhalb unserer
|
||||
);
|
||||
// Heute
|
||||
$timestamp = time(); // jetzt (heute)
|
||||
$timestamp = 1762001873; //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
|
||||
$timestamp1 = $timestamp + 1209600; // 14 Tage weiter
|
||||
// 365 Tage in die Zukunft...
|
||||
for ($i = 0; $i < 365; $i++) {
|
||||
@@ -508,7 +514,7 @@ einer unserer freien Mitarbeiter/-innen die Sonderführung außerhalb unserer
|
||||
|
||||
<p><br/><br/>Hinweis zum Datenschutz:
|
||||
<a href="" id="dschu">Datenschutzerklärung</a></p>
|
||||
<p class="lastchange">Letzte Änderungen: 2023-10-04 rxf</p>
|
||||
<p class="lastchange">Letzte Änderungen: 2025-10-22 rxf</p>
|
||||
</form>
|
||||
</div>
|
||||
<?
|
||||
|
||||
@@ -97,6 +97,7 @@
|
||||
$enddatum = new DateTime('now');
|
||||
$enddatum->modify($monthstoadd);
|
||||
$enddatum = $enddatum->format('Ymd');
|
||||
$datum_heute = 20251114; //<<<<<<<<<<<<<<<<<<<<<<<<<
|
||||
$sql_sel = "SELECT * FROM sonnedatum where datum >='$datum_heute' && datum <= '$enddatum' order by datum ASC";
|
||||
$result = mysqli_query($db, $sql_sel) or die(mysqli_error($db));
|
||||
while ($row = mysqli_fetch_assoc($result)) {
|
||||
@@ -381,6 +382,11 @@
|
||||
<p>
|
||||
<?PHP echo $stern_error_msg ?>
|
||||
</p>
|
||||
<p class="sondermeldung">
|
||||
Unsere Sternwarte bleibt bis voraussichtlich 14. November 2025 wegen neuer technischer Einrichtung
|
||||
geschlossen. <br />Bis dahin können keine Führungen durchgeführt werden.
|
||||
</p>
|
||||
|
||||
<strong>Anmeldung:</strong>
|
||||
<span class="textklein">(Alle Felder mit '*' müssen ausgefüllt werden)</span><br />
|
||||
<br />
|
||||
@@ -465,7 +471,7 @@
|
||||
<p>
|
||||
Hinweis zum Datenschutz: <a href="" id="dschu">Datenschutzerklärung</a>
|
||||
</p>
|
||||
<p class="lastchange">Letzte Änderungen: 2024-09-19 rxf</p>
|
||||
<p class="lastchange">Letzte Änderungen: 2025-10-22 rxf</p>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
.storno {
|
||||
font-size: 14px;
|
||||
min-height: 500px;
|
||||
min-height: 550px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -131,3 +131,8 @@ h5 {
|
||||
margin-left: 10px;
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
#abbrechen {
|
||||
background-color: gray;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
Für diese E-Mail Adresse ist keine Führung angemeldet !<br />
|
||||
</div>
|
||||
<div id="anmeldid" class="col-12 col-xm-8 text-center">
|
||||
Sie sind angemeldet für: <br /><br />
|
||||
Sie sind angemeldet für eine <span id="fart">Sternführung</span> am: <br /><br />
|
||||
<div id="anmeldung">
|
||||
2022-07-12 22:00 Uhr 4 Personen
|
||||
</div>
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
$(document).ready(() => {
|
||||
|
||||
// Globale Konstanten und Variable
|
||||
// const ajaxURL="php/anmeldDB.php";
|
||||
const ajaxURL="../../DB4js.php";
|
||||
// const ajaxURL="php/anmeldDB.php";
|
||||
const ajaxURL = "../../DB4js_all.php";
|
||||
|
||||
const maxVisitors = 25
|
||||
const months2add = 3
|
||||
@@ -21,9 +21,9 @@ $(document).ready(() => {
|
||||
// Return:
|
||||
// angeforderte Daten als JSON
|
||||
const fetchFromDbase = async (body) => {
|
||||
const response = await fetch(ajaxURL,{
|
||||
const response = await fetch(ajaxURL, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/js'},
|
||||
headers: { 'Content-Type': 'application/js' },
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
let rt = await response.json();
|
||||
@@ -37,22 +37,41 @@ $(document).ready(() => {
|
||||
// Return:
|
||||
// angeforderte Daten als JSON
|
||||
const putToDbase = async (body) => {
|
||||
const response = await fetch(ajaxURL,{
|
||||
const response = await fetch(ajaxURL, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/js'},
|
||||
headers: { 'Content-Type': 'application/js' },
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
// Dat Führungsdatum extrahieren
|
||||
// Wochentag aus integer Datum extrahieren
|
||||
function getWochentag(datumInt) {
|
||||
const d = new Date(
|
||||
Math.floor(datumInt / 10000), // Jahr
|
||||
Math.floor((datumInt % 10000) / 100) - 1, // Monat (0-basiert)
|
||||
datumInt % 100 // Tag
|
||||
);
|
||||
return ["Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"][d.getDay()];
|
||||
}
|
||||
|
||||
// Aus dem integer Führungsdatum (aus anmeldungen) den Wochentag und
|
||||
// die Uhrzeit aus der Tabelle fdatum 1 holen
|
||||
const getFuhrzeit = async (dt, typ) => {
|
||||
return await fetchFromDbase({ cmd: 'GET_TIME_BY_DATE', dt: dt, typ: typ })
|
||||
|
||||
}
|
||||
// Das Führungsdatum extrahieren
|
||||
const buildDatum = async (tn, short) => {
|
||||
const person = tn.anzahl === '1' ? 'Person' : 'Personen'
|
||||
const datum = await fetchFromDbase({cmd: 'GET_ONE_DATE', fid: tn.fid})
|
||||
if(short) {
|
||||
return `${moment(datum.datum).format('DD.MM.YYYY')}`
|
||||
const uhrzeit = await getFuhrzeit(tn.fdatum, tn.typ)
|
||||
if (short) {
|
||||
return `${moment(tn.fdatum).format('DD.MM.YYYY')}`
|
||||
}
|
||||
return `${datum.wtag}, den ${moment(datum.datum).format('DD.MM.YYYY')} um ${datum.uhrzeit} mit ${tn.anzahl} ${person}`
|
||||
if(tn.typ === 'sonnen') {
|
||||
document.getElementById('fart').innerHTML = "Sonnenführung"
|
||||
}
|
||||
return `${getWochentag(tn.fdatum)}, den ${moment(tn.fdatum).format('DD.MM.YYYY')} um ${uhrzeit} mit ${tn.anzahl} ${person}`
|
||||
}
|
||||
|
||||
// Das Führungsdatum anzeigen
|
||||
@@ -67,13 +86,13 @@ $(document).ready(() => {
|
||||
}
|
||||
|
||||
const austragen = async (teilnehmer) => {
|
||||
// console.log("Austragen von ", teilnehmer)
|
||||
// return
|
||||
oldtndata = {...teilnehmer}
|
||||
let delstr = {cmd: 'DELETE_ENTRY', id: parseInt(teilnehmer.id)}
|
||||
// console.log("Austragen von ", teilnehmer)
|
||||
// return
|
||||
oldtndata = { ...teilnehmer }
|
||||
let delstr = { cmd: 'DELETE_ENTRY', id: parseInt(teilnehmer.id) }
|
||||
const erg1 = await putToDbase(delstr)
|
||||
console.log("Storno Ergebnis: ",erg1)
|
||||
if(erg1) {
|
||||
console.log("Storno Ergebnis: ", erg1)
|
||||
if (erg1) {
|
||||
$('#ausgetragen').show()
|
||||
$('#lower_text').show()
|
||||
sendEmail(teilnehmer, true)
|
||||
@@ -84,44 +103,48 @@ $(document).ready(() => {
|
||||
}
|
||||
|
||||
const umbuchen = async (teilnehmer) => {
|
||||
// console.log('Umbuchen von ',teilnehmer)
|
||||
// return
|
||||
oldtndata = {...teilnehmer}
|
||||
// console.log('Umbuchen von ',teilnehmer)
|
||||
// return
|
||||
oldtndata = { ...teilnehmer }
|
||||
let start = moment()
|
||||
start = start.add(vorlauf, 'h')
|
||||
let end = moment()
|
||||
end = end.add(months2add, 'M')
|
||||
let add = months2add
|
||||
if (teilnehmer.typ === 'sonnen') {
|
||||
add = 12
|
||||
}
|
||||
end = end.add(add, 'M')
|
||||
const anzahl = parseInt(teilnehmer.anzahl)
|
||||
let fuehrungen = await fetchFromDbase({cmd: 'GET_FUEHRUNGEN', start: start.format('YYYYMMDD'), end: end.format('YYYYMMDD')})
|
||||
let fuehrungen = await fetchFromDbase({ cmd: 'GET_FUEHRUNGEN', start: start.format('YYYYMMDD'), end: end.format('YYYYMMDD'), typ: teilnehmer.typ})
|
||||
let r = `<label for "ftermin" class="labeltext"><strong>Umbuchung auf</strong></label><br /><select name="ftermin" id="ftermin"><option>-- Bitte wählen Sie ein Datum aus--</option>`
|
||||
for (let f of fuehrungen) {
|
||||
if(f.datum == teilnehmer.fdatum) {
|
||||
if (f.datum == teilnehmer.fdatum) {
|
||||
continue
|
||||
}
|
||||
let count = await fetchFromDbase({cmd: 'GET_COUNTS_DATE', date: f.datum})
|
||||
let count = await fetchFromDbase({ cmd: 'GET_COUNTS_DATE', date: f.datum })
|
||||
count = count ? parseInt(count) : 0
|
||||
if(count + anzahl >= maxVisitors) {
|
||||
if (count + anzahl >= maxVisitors) {
|
||||
continue // wenn der Platz nicht reicht, nicht anzeigen
|
||||
}
|
||||
r += `<option id=${f.id} value=${f.datum}>`
|
||||
r += `${f.wtag.substring(0,2)} , ${moment(f.datum).format('DD.MM.YYYY')} ${f.uhrzeit} `
|
||||
r += `Frei ${(maxVisitors-count) > 0 ? maxVisitors-count : 0}</option>`
|
||||
r += `${getWochentag(f.datum).substring(0, 2)} , ${moment(f.datum).format('DD.MM.YYYY')} ${await getFuhrzeit(f.datum, teilnehmer.typ)} `
|
||||
r += `Frei ${(maxVisitors - count) > 0 ? maxVisitors - count : 0}</option>`
|
||||
}
|
||||
r += '</select>'
|
||||
$('#umbuchung').html(r)
|
||||
// $('#umgebucht').html('Bitte wählen Sie ein freies Datum über den kleine Pfeil rechts.')
|
||||
// $('#umgebucht').html('Bitte wählen Sie ein freies Datum über den kleine Pfeil rechts.')
|
||||
$('#umgebucht').show()
|
||||
|
||||
// Eventhandler für Auswahl eines Datums über die SelectBox:
|
||||
// Anzeigen der Anmeldungen dazu
|
||||
$('#ftermin').change( async () => {
|
||||
$('#ftermin').change(async () => {
|
||||
const x = $('#ftermin').find(':selected');
|
||||
const date = x[0].value
|
||||
teilnehmer.fdatum = date
|
||||
teilnehmer.fid = x[0].id
|
||||
const update = {cmd: 'UPDATETLNFD', fdatum: date, fid: x[0].id, id: teilnehmer.id}
|
||||
const update = { cmd: 'UPDATETLNFD', fdatum: date, fid: x[0].id, id: teilnehmer.id }
|
||||
const erg = await putToDbase(update)
|
||||
if(erg) {
|
||||
if (erg) {
|
||||
$('#umbuchung').hide()
|
||||
$('#umgebucht').html(`Sie wurden erfolgreich umgebucht auf <br /><br />${await buildDatum(teilnehmer, false)}`)
|
||||
$('#umgebucht').show()
|
||||
@@ -137,11 +160,11 @@ $(document).ready(() => {
|
||||
}
|
||||
|
||||
const aendern = async (teilnehmer) => {
|
||||
console.log('Ändern von ',teilnehmer)
|
||||
let count = await fetchFromDbase({cmd: 'GET_COUNTS_DATE', date: teilnehmer.fdatum})
|
||||
console.log('Ändern von ', teilnehmer)
|
||||
let count = await fetchFromDbase({ cmd: 'GET_COUNTS_DATE', date: teilnehmer.fdatum })
|
||||
let anzahl = parseInt(teilnehmer.anzahl)
|
||||
let max = anzahl
|
||||
if(count < maxVisitors) {
|
||||
if (count < maxVisitors) {
|
||||
max = maxVisitors - (count - anzahl)
|
||||
}
|
||||
if (max > 10) {
|
||||
@@ -149,13 +172,13 @@ $(document).ready(() => {
|
||||
}
|
||||
console.log("Max: ", max)
|
||||
$('#maxanzahl').text(`max. ${max}`)
|
||||
$('#personen').attr({max: max, min: 1, step: 1, value: anzahl})
|
||||
$('#personen').attr({ max: max, min: 1, step: 1, value: anzahl })
|
||||
|
||||
// Evelthandler Übernehmen geklicked
|
||||
$('#bsave').click(async () => {
|
||||
$('#save').hide()
|
||||
teilnehmer.anzahl = $('#personen').val()
|
||||
let e = await putToDbase({cmd: 'UPDATE_TLN', data: teilnehmer, id: teilnehmer.id})
|
||||
let e = await putToDbase({ cmd: 'UPDATE_TLN', data: teilnehmer, id: teilnehmer.id })
|
||||
$('#newperson').html(`Sie sind nun mit <strong>${teilnehmer.anzahl} </strong> Personen angemeldet.`)
|
||||
$('#bsave').hide()
|
||||
$('#newperson').show()
|
||||
@@ -166,7 +189,7 @@ $(document).ready(() => {
|
||||
}
|
||||
|
||||
|
||||
// Emailadresse eingegeben
|
||||
// Emailadresse eingegeben
|
||||
const setEvent = (f) => {
|
||||
$(document).on("keypress", "input", async function (e) {
|
||||
if (e.which === 13) {
|
||||
@@ -196,15 +219,15 @@ $(document).ready(() => {
|
||||
$('#umbuchen').click(() => {
|
||||
umbuchen(f[idx])
|
||||
$('#anmeldid').hide()
|
||||
// $('#email').val("")
|
||||
// $('#email').val("")
|
||||
})
|
||||
// Ändern geklicked
|
||||
$('#aendern').click(() => {
|
||||
aendern(f[idx])
|
||||
$('#butgroup').hide()
|
||||
$('#aenderung').show()
|
||||
// $('#anmeldid').hide()
|
||||
// $('#email').val("")
|
||||
// $('#anmeldid').hide()
|
||||
// $('#email').val("")
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -214,20 +237,18 @@ $(document).ready(() => {
|
||||
sendEmail = async (tln, storno) => {
|
||||
let fdatum = await buildDatum(tln, false)
|
||||
let oldfdatum = await buildDatum(oldtndata, true)
|
||||
let subject = `${storno ? "Stornierung":"Umbuchung"} der Führung vom ${oldfdatum} auf der Sternwarte Welzheim`
|
||||
let subject = `${storno ? "Stornierung" : "Umbuchung"} der Führung vom ${oldfdatum} auf der Sternwarte Welzheim`
|
||||
let body_txt = `
|
||||
Sehr geehrte Dame, sehr geehrter Herr,
|
||||
|
||||
hiermit bestätigen wir die ${storno ? 'Stornierung' : 'Umbuchung'} Ihrer Führung auf der Sternwarte Welzheim vom`
|
||||
if(!storno) {
|
||||
if (!storno) {
|
||||
body_txt += ` ${oldfdatum}.
|
||||
|
||||
Sie wurden umgebucht auf:
|
||||
|
||||
${fdatum}
|
||||
|
||||
Bitte bringen Sie diese Bestätigung als Ausdruck oder digital zur Führung mit.
|
||||
|
||||
Die Führung findet NUR bei sternklarem Himmel statt. Falls der Himmel bedeckt ist
|
||||
und die Führung ausfällt, erhalten Sie bis spätestens eine Stunde vor Führungsbeginn
|
||||
eine Email. Sie können sich dann gerne zu einer anderen Führung neu anmelden.
|
||||
@@ -244,29 +265,12 @@ Mit freundlichen Grüßen
|
||||
Beobachterteam der Sternwarte Welzheim
|
||||
www.sternwarte-welzheim.de
|
||||
`
|
||||
/* let body_html = `Sehr geehrte Dame, sehr geehrter Herr,<br /><br />`
|
||||
if(!storno) {
|
||||
body_html += `hiermit bestätigen wir die <strong>Umbuchung</strong> Ihrer Führung auf der Sternwarte Welzheim.<br />
|
||||
<br />Sie wurden umgebucht auf:<br /><br />${fdatum}<br /><br />
|
||||
Bitte bringen Sie diese Bestätigung als Ausdruck oder digital zur Führung mit.<br /><br />
|
||||
Die Führung findet NUR bei sternklarem Himmel statt. Falls der Himmel bedeckt ist
|
||||
und die Führung ausfällt, erhalten Sie bis spätestens eine Stunde vor Führungsbeginn
|
||||
eine Email. Sie können sich dann gerne zu einer anderen Führung neu anmelden.<br /><br />
|
||||
Allen Teilnehmern/-innen wird dringend empfohlen, eine FFP2-Maske, die Mund und Nase
|
||||
bedeckt, zu tragen.<br />
|
||||
Sollten Sie Fragen haben senden Sie bitte eine Email an <a href="mailto:anmeldung@sternwarte-welzheim.de">anmeldung@sternwarte-welzheim.de</a>`
|
||||
} else {
|
||||
body_html += `hiermit bestätigen wir die <strong>Stornierung</strong> Ihrer Führung auf der Sternwarte Welzheim vom<br />`
|
||||
body_html += `${fdatum}.`
|
||||
}
|
||||
body_html += `<br /><br />Mit freundlichen Grüßen<br />Beobachterteam der Sternwarte Welzheim<br /><a href="https://www.sternwarte-welzheim.de">www.sternwarte-welzheim.de</a>`
|
||||
*/
|
||||
let erg = await putToDbase({cmd: 'SEND_MAIL_HTML', subject: subject, to: [tln.email], body_txt: body_txt, body_html: ""})
|
||||
let erg = await putToDbase({ cmd: 'SEND_MAIL_HTML', subject: subject, to: [tln.email], body_txt: body_txt, body_html: "" })
|
||||
console.log("Antwort von sendmail_1: ", erg)
|
||||
|
||||
body_txt = `Die Führung vom ${oldfdatum} wurde ${storno ? 'storniert' : 'umgebucht'}
|
||||
`
|
||||
if(!storno) {
|
||||
if (!storno) {
|
||||
body_txt += `
|
||||
auf ${fdatum}`
|
||||
}
|
||||
@@ -275,12 +279,12 @@ auf ${fdatum}`
|
||||
Besucher: ${tln.name} ${tln.vorname}`
|
||||
|
||||
body_html = `Die Führung vom ${oldfdatum} wurde ${storno ? 'storniert' : 'umgebucht'}`
|
||||
if(!storno) {
|
||||
if (!storno) {
|
||||
body_html += ` auf ${fdatum}<br />`
|
||||
}
|
||||
body_html += `<br />Besucher: ${tln.name} ${tln.vorname}`
|
||||
|
||||
erg = await putToDbase({cmd: 'SEND_MAIL_HTML', subject: subject, to: ['rexfue@gmail.com'], body_txt: body_txt, body_html: body_html})
|
||||
erg = await putToDbase({ cmd: 'SEND_MAIL_HTML', subject: subject, to: ['rexfue@gmail.com'], body_txt: body_txt, body_html: body_html })
|
||||
console.log("Antwort von sendmail_2: ", erg)
|
||||
}
|
||||
|
||||
@@ -294,7 +298,7 @@ Besucher: ${tln.name} ${tln.vorname}`
|
||||
// und anzeigen
|
||||
// Params:
|
||||
// n -> Anzahl der zu holnden Daten
|
||||
async function main(){
|
||||
async function main() {
|
||||
console.log("Running...")
|
||||
$('.lastchange').text(`Letzte Änderungen: ${VDATE} rxf`)
|
||||
$('#anmeldid').hide()
|
||||
@@ -307,30 +311,8 @@ Besucher: ${tln.name} ${tln.vorname}`
|
||||
$('#versn').html("Version: " + VERSION + ' vom ' + VDATE);
|
||||
let fdatum = moment().format('YYYYMMDD')
|
||||
// alle Anmeldungen ab fdatum in ein Array holen
|
||||
let fuehrungen = await fetchFromDbase({cmd: 'GET_ALLTEILN', fdatum: fdatum})
|
||||
let fuehrungen = await fetchFromDbase({ cmd: 'GET_ALLTEILN', fdatum: fdatum })
|
||||
setEvent(fuehrungen)
|
||||
// // Media Query einbauen:
|
||||
// let x = window.matchMedia("(max-width: 800px)");
|
||||
// switchText(x.matches);
|
||||
// x.addEventListener("change", async (e) => {
|
||||
// switchText(e.matches);
|
||||
// // await showAktAnmeldungen(actualdate);
|
||||
// });
|
||||
// let curtime = moment().subtract(14,'days').format("YYYYMMDD");
|
||||
// // let curtime = moment().format("YYYYMMDD");
|
||||
// console.log(curtime)
|
||||
|
||||
// const y = await fetchFromDbase({cmd:'GET_DATES', anzahl:n, date: curtime});
|
||||
// const last = await fetchFromDbase({cmd:'GET_LASTANMELDUNG', date: curtime});
|
||||
// const sel = await buildFuehrungsDates(y, last);
|
||||
// await showAktAnmeldungen(y[sel].datum);
|
||||
// console.log(y);
|
||||
// if(params.name != 'Null') {
|
||||
// await findName(params.name)
|
||||
// }
|
||||
// if(params.double == 'true') {
|
||||
// await showDoubles(curtime);
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
|
||||
147
sternwarte/tests/integration_api.test.cjs
Normal file
147
sternwarte/tests/integration_api.test.cjs
Normal file
@@ -0,0 +1,147 @@
|
||||
#!/usr/bin/env node
|
||||
/*
|
||||
Integration smoke tests for DB4js_all.php (CommonJS)
|
||||
Env:
|
||||
- API_URL (default: https://sternwarte-welzheim.de/DB4js_all.php)
|
||||
- API_USER / API_PASS (optional Basic Auth)
|
||||
*/
|
||||
|
||||
const https = require('https');
|
||||
const http = require('http');
|
||||
const { URL } = require('url');
|
||||
|
||||
const API_URL = process.env.API_URL || 'https://sternwarte-welzheim.de/DB4js_all.php';
|
||||
const API_USER = process.env.API_USER || '';
|
||||
const API_PASS = process.env.API_PASS || '';
|
||||
|
||||
function postJSON(urlStr, bodyObj) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const url = new URL(urlStr);
|
||||
const body = JSON.stringify(bodyObj || {});
|
||||
const isHttps = url.protocol === 'https:';
|
||||
const lib = isHttps ? https : http;
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': Buffer.byteLength(body)
|
||||
};
|
||||
if (API_USER && API_PASS) {
|
||||
const token = Buffer.from(`${API_USER}:${API_PASS}`).toString('base64');
|
||||
headers['Authorization'] = `Basic ${token}`;
|
||||
}
|
||||
|
||||
const req = lib.request({
|
||||
method: 'POST',
|
||||
hostname: url.hostname,
|
||||
port: url.port || (isHttps ? 443 : 80),
|
||||
path: url.pathname + url.search,
|
||||
headers,
|
||||
rejectUnauthorized: false,
|
||||
timeout: 15000,
|
||||
}, (res) => {
|
||||
let data = '';
|
||||
res.setEncoding('utf8');
|
||||
res.on('data', c => data += c);
|
||||
res.on('end', () => {
|
||||
let json = null;
|
||||
try { json = JSON.parse(data); } catch (e) {}
|
||||
if (res.statusCode >= 400) {
|
||||
return reject(new Error(`HTTP ${res.statusCode}: ${data}`));
|
||||
}
|
||||
if (!json) return reject(new Error(`Non-JSON response: ${data}`));
|
||||
resolve(json);
|
||||
});
|
||||
});
|
||||
req.on('error', reject);
|
||||
req.on('timeout', () => { req.destroy(new Error('Request timeout')); });
|
||||
req.write(body);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
function assert(cond, msg) {
|
||||
if (!cond) throw new Error(msg);
|
||||
}
|
||||
|
||||
(async () => {
|
||||
const failures = [];
|
||||
let step = 0;
|
||||
function record(name, fn) {
|
||||
return Promise.resolve()
|
||||
.then(fn)
|
||||
.then(() => console.log(`✔ ${++step}. ${name}`))
|
||||
.catch((err) => { failures.push({ name, err }); console.error(`✖ ${++step}. ${name} ->`, err.message); });
|
||||
}
|
||||
|
||||
console.log(`API_URL=${API_URL}`);
|
||||
|
||||
await record('PING returns pong + timestamp', async () => {
|
||||
const r = await postJSON(API_URL, { cmd: 'PING' });
|
||||
assert(r && r.pong === true, 'pong !== true');
|
||||
assert(typeof r.timestamp === 'string' && r.timestamp.length > 0, 'timestamp missing');
|
||||
});
|
||||
|
||||
await record('LIST_COMMANDS returns command map', async () => {
|
||||
const r = await postJSON(API_URL, { cmd: 'LIST_COMMANDS' });
|
||||
assert(r && r.commands && typeof r.count === 'number', 'invalid LIST_COMMANDS payload');
|
||||
});
|
||||
|
||||
await record('GET_BEOS with alias email works', async () => {
|
||||
const r = await postJSON(API_URL, { cmd: 'GET_BEOS', onlyguides: true, what: 'id,name,email' });
|
||||
assert(Array.isArray(r), 'GET_BEOS did not return array');
|
||||
if (r.length > 0) {
|
||||
const it = r[0];
|
||||
assert('id' in it, 'beo item missing id');
|
||||
assert('name' in it, 'beo item missing name');
|
||||
assert(('email' in it) || ('email_1' in it), 'beo item missing email/email_1');
|
||||
}
|
||||
});
|
||||
|
||||
await record('GET_BEOS with non-existing field falls back', async () => {
|
||||
const r = await postJSON(API_URL, { cmd: 'GET_BEOS', onlyguides: 'true', what: 'id,name,doesnotexist' });
|
||||
assert(Array.isArray(r), 'GET_BEOS did not return array on fallback');
|
||||
});
|
||||
|
||||
let sampleDate = null;
|
||||
await record('GET_TERMINE returns array', async () => {
|
||||
const r = await postJSON(API_URL, { cmd: 'GET_TERMINE' });
|
||||
assert(Array.isArray(r), 'GET_TERMINE did not return array');
|
||||
if (r.length > 0) {
|
||||
const d = r.find(x => typeof x.datum === 'string');
|
||||
if (d) sampleDate = d.datum;
|
||||
}
|
||||
});
|
||||
|
||||
if (sampleDate) {
|
||||
await record('GET_TIME for sample date', async () => {
|
||||
const r = await postJSON(API_URL, { cmd: 'GET_TIME', date: sampleDate });
|
||||
assert(r && typeof r.time === 'string', 'GET_TIME missing time');
|
||||
});
|
||||
|
||||
let sampleFid = null;
|
||||
await record('GET_FID for sample date', async () => {
|
||||
const r = await postJSON(API_URL, { cmd: 'GET_FID', datum: sampleDate });
|
||||
assert(r && ('fid' in r), 'GET_FID missing fid');
|
||||
sampleFid = r.fid;
|
||||
});
|
||||
|
||||
await record('GET_COUNTS_DATE for sample date', async () => {
|
||||
const r = await postJSON(API_URL, { cmd: 'GET_COUNTS_DATE', date: sampleDate });
|
||||
assert(r && typeof r.count === 'number', 'GET_COUNTS_DATE invalid');
|
||||
});
|
||||
|
||||
if (sampleFid) {
|
||||
await record('GET_COUNTS for sample fid', async () => {
|
||||
const r = await postJSON(API_URL, { cmd: 'GET_COUNTS', fid: sampleFid });
|
||||
assert(r && typeof r.count === 'number', 'GET_COUNTS invalid');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (failures.length) {
|
||||
console.error(`\n${failures.length} test(s) failed:`);
|
||||
failures.forEach((f, i) => console.error(` ${i + 1}) ${f.name}: ${f.err && f.err.stack || f.err}`));
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log('\nAll integration tests passed.');
|
||||
}
|
||||
})().catch((e) => { console.error('Fatal error:', e); process.exit(1); });
|
||||
151
sternwarte/tests/integration_api.test.js
Normal file
151
sternwarte/tests/integration_api.test.js
Normal file
@@ -0,0 +1,151 @@
|
||||
#!/usr/bin/env node
|
||||
/*
|
||||
Integration smoke tests for DB4js_all.php
|
||||
- Uses HTTPS POST JSON to remote API (or custom API_URL)
|
||||
- No external deps (Node core only)
|
||||
|
||||
Env:
|
||||
- API_URL (default: https://sternwarte-welzheim.de/DB4js_all.php)
|
||||
- API_USER / API_PASS (optional Basic Auth)
|
||||
*/
|
||||
|
||||
const https = require('https');
|
||||
const http = require('http');
|
||||
const { URL } = require('url');
|
||||
|
||||
const API_URL = process.env.API_URL || 'https://sternwarte-welzheim.de/DB4js_all.php';
|
||||
const API_USER = process.env.API_USER || '';
|
||||
const API_PASS = process.env.API_PASS || '';
|
||||
|
||||
function postJSON(urlStr, bodyObj) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const url = new URL(urlStr);
|
||||
const body = JSON.stringify(bodyObj || {});
|
||||
const isHttps = url.protocol === 'https:';
|
||||
const lib = isHttps ? https : http;
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': Buffer.byteLength(body)
|
||||
};
|
||||
if (API_USER && API_PASS) {
|
||||
const token = Buffer.from(`${API_USER}:${API_PASS}`).toString('base64');
|
||||
headers['Authorization'] = `Basic ${token}`;
|
||||
}
|
||||
|
||||
const req = lib.request({
|
||||
method: 'POST',
|
||||
hostname: url.hostname,
|
||||
port: url.port || (isHttps ? 443 : 80),
|
||||
path: url.pathname + url.search,
|
||||
headers,
|
||||
rejectUnauthorized: false, // allow self-signed (if any)
|
||||
timeout: 15000,
|
||||
}, (res) => {
|
||||
let data = '';
|
||||
res.setEncoding('utf8');
|
||||
res.on('data', c => data += c);
|
||||
res.on('end', () => {
|
||||
let json = null;
|
||||
try { json = JSON.parse(data); } catch (e) {}
|
||||
if (res.statusCode >= 400) {
|
||||
return reject(new Error(`HTTP ${res.statusCode}: ${data}`));
|
||||
}
|
||||
if (!json) return reject(new Error(`Non-JSON response: ${data}`));
|
||||
resolve(json);
|
||||
});
|
||||
});
|
||||
req.on('error', reject);
|
||||
req.on('timeout', () => { req.destroy(new Error('Request timeout')); });
|
||||
req.write(body);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
function assert(cond, msg) {
|
||||
if (!cond) throw new Error(msg);
|
||||
}
|
||||
|
||||
(async () => {
|
||||
const failures = [];
|
||||
let step = 0;
|
||||
function record(name, fn) {
|
||||
return Promise.resolve()
|
||||
.then(fn)
|
||||
.then(() => console.log(`✔ ${++step}. ${name}`))
|
||||
.catch((err) => { failures.push({ name, err }); console.error(`✖ ${++step}. ${name} ->`, err.message); });
|
||||
}
|
||||
|
||||
console.log(`API_URL=${API_URL}`);
|
||||
|
||||
await record('PING returns pong + timestamp', async () => {
|
||||
const r = await postJSON(API_URL, { cmd: 'PING' });
|
||||
assert(r && r.pong === true, 'pong !== true');
|
||||
assert(typeof r.timestamp === 'string' && r.timestamp.length > 0, 'timestamp missing');
|
||||
});
|
||||
|
||||
await record('LIST_COMMANDS returns command map', async () => {
|
||||
const r = await postJSON(API_URL, { cmd: 'LIST_COMMANDS' });
|
||||
assert(r && r.commands && typeof r.count === 'number', 'invalid LIST_COMMANDS payload');
|
||||
});
|
||||
|
||||
await record('GET_BEOS with alias email works', async () => {
|
||||
const r = await postJSON(API_URL, { cmd: 'GET_BEOS', onlyguides: true, what: 'id,name,email' });
|
||||
assert(Array.isArray(r), 'GET_BEOS did not return array');
|
||||
if (r.length > 0) {
|
||||
const it = r[0];
|
||||
assert('id' in it, 'beo item missing id');
|
||||
assert('name' in it, 'beo item missing name');
|
||||
assert(('email' in it) || ('email_1' in it), 'beo item missing email/email_1');
|
||||
}
|
||||
});
|
||||
|
||||
await record('GET_BEOS with non-existing field falls back', async () => {
|
||||
const r = await postJSON(API_URL, { cmd: 'GET_BEOS', onlyguides: 'true', what: 'id,name,doesnotexist' });
|
||||
assert(Array.isArray(r), 'GET_BEOS did not return array on fallback');
|
||||
});
|
||||
|
||||
let sampleDate = null;
|
||||
await record('GET_TERMINE returns array', async () => {
|
||||
const r = await postJSON(API_URL, { cmd: 'GET_TERMINE' });
|
||||
assert(Array.isArray(r), 'GET_TERMINE did not return array');
|
||||
if (r.length > 0) {
|
||||
// pick first with possible datum field
|
||||
const d = r.find(x => typeof x.datum === 'string');
|
||||
if (d) sampleDate = d.datum;
|
||||
}
|
||||
});
|
||||
|
||||
if (sampleDate) {
|
||||
await record('GET_TIME for sample date', async () => {
|
||||
const r = await postJSON(API_URL, { cmd: 'GET_TIME', date: sampleDate });
|
||||
assert(r && typeof r.time === 'string', 'GET_TIME missing time');
|
||||
});
|
||||
|
||||
let sampleFid = null;
|
||||
await record('GET_FID for sample date', async () => {
|
||||
const r = await postJSON(API_URL, { cmd: 'GET_FID', datum: sampleDate });
|
||||
assert(r && ('fid' in r), 'GET_FID missing fid');
|
||||
sampleFid = r.fid;
|
||||
});
|
||||
|
||||
await record('GET_COUNTS_DATE for sample date', async () => {
|
||||
const r = await postJSON(API_URL, { cmd: 'GET_COUNTS_DATE', date: sampleDate });
|
||||
assert(r && typeof r.count === 'number', 'GET_COUNTS_DATE invalid');
|
||||
});
|
||||
|
||||
if (sampleFid) {
|
||||
await record('GET_COUNTS for sample fid', async () => {
|
||||
const r = await postJSON(API_URL, { cmd: 'GET_COUNTS', fid: sampleFid });
|
||||
assert(r && typeof r.count === 'number', 'GET_COUNTS invalid');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (failures.length) {
|
||||
console.error(`\n${failures.length} test(s) failed:`);
|
||||
failures.forEach((f, i) => console.error(` ${i + 1}) ${f.name}: ${f.err && f.err.stack || f.err}`));
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log('\nAll integration tests passed.');
|
||||
}
|
||||
})().catch((e) => { console.error('Fatal error:', e); process.exit(1); });
|
||||
Reference in New Issue
Block a user