Compare commits
39
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a9a08421f | ||
|
|
ffcc8821ed | ||
|
|
72f188e239 | ||
|
|
6ec159f75c | ||
|
|
d6e70a08eb | ||
|
|
52446cc019 | ||
|
|
10472843a7 | ||
|
|
771f9e7bf6 | ||
|
|
a066c59576 | ||
|
|
388f9b8f0d | ||
|
|
b08857a4cd | ||
|
|
6d68fbc25d | ||
|
|
dceb02a3de | ||
|
|
cd325270f0 | ||
|
|
efa9584baf | ||
|
|
ac202be4a7 | ||
|
|
24e11e14a3 | ||
|
|
5305fcabb6 | ||
|
|
3f7c840c39 | ||
|
|
378c462b1c | ||
|
|
5f1aa1c6fc | ||
|
|
5ac4fa4ac6 | ||
|
|
3eb451069b | ||
|
|
caf62ea701 | ||
|
|
82fe6e85d8 | ||
|
|
40c26e625b | ||
|
|
2901a5e362 | ||
|
|
2231d65465 | ||
|
|
4558a717fa | ||
|
|
b2b4ac2124 | ||
|
|
f7fd4ed183 | ||
|
|
a73b2ec13f | ||
|
|
dce0a0caed | ||
|
|
3d8c4291be | ||
|
|
a0afeabb27 | ||
|
|
96c27a8722 | ||
|
|
4478f7e82f | ||
|
|
c1f3c3ea4e | ||
|
|
2a8564d3bf |
No files matched your search
@@ -3,3 +3,17 @@ RewriteEngine On
|
||||
RewriteRule ^(Commands|config_backups|controller|modules)($|/.*) - [F,L,NC]
|
||||
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
|
||||
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
|
||||
|
||||
# -----------------------------------
|
||||
# .php entfernen
|
||||
# -----------------------------------
|
||||
|
||||
# browser requestet datei.php -> redirect zu /datei
|
||||
RewriteCond %{THE_REQUEST} \s/+(.+?)\.php[\s?] [NC]
|
||||
RewriteRule ^ /%1 [R=301,L]
|
||||
|
||||
# /datei -> datei.php
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteCond %{DOCUMENT_ROOT}/$1.php -f
|
||||
RewriteRule ^(.+)$ $1.php [L]
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
include_once(__DIR__ . "/../controller/DatabaseController.php");
|
||||
include_once(__DIR__ . "/../modules/Mail.php");
|
||||
|
||||
date_default_timezone_set("Europe/Berlin");
|
||||
|
||||
$jetzt = new DateTime();
|
||||
$heute = $jetzt->format("Y-m-d");
|
||||
|
||||
// Alle heutigen Termine laden
|
||||
$termine = DatabaseShow(
|
||||
"Termine",
|
||||
"*",
|
||||
"DATUM = ? AND ERINNERUNG_AM IS NULL",
|
||||
$heute
|
||||
);
|
||||
|
||||
foreach ($termine as $termin) {
|
||||
|
||||
$terminZeit = new DateTime($termin["DATUM"] . " " . $termin["ZEIT"]);
|
||||
$sekundenBisTermin = $terminZeit->getTimestamp() - $jetzt->getTimestamp();
|
||||
|
||||
// Nur Termine innerhalb der nächsten 30 Minuten
|
||||
if ($sekundenBisTermin > 0 && $sekundenBisTermin <= 1800) {
|
||||
|
||||
// Benutzer laden
|
||||
$benutzer = DatabaseShow(
|
||||
"Benutzer",
|
||||
"*",
|
||||
"NAME = ?",
|
||||
$termin["ERSTELLT_VON"]
|
||||
);
|
||||
|
||||
if (empty($benutzer)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$benutzer = $benutzer[0];
|
||||
|
||||
if (empty($benutzer["EMAIL"])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$subject = "Terminerinnerung";
|
||||
|
||||
$message = "Hallo {$benutzer["NAME"]},\n\n";
|
||||
$message .= "Du hast in weniger als 30 Minuten einen Termin.\n\n";
|
||||
$message .= "Termin: {$termin["NAME"]}\n";
|
||||
$message .= "Datum: {$termin["DATUM"]}\n";
|
||||
$message .= "Uhrzeit: {$termin["ZEIT"]}\n";
|
||||
|
||||
if (!empty($termin["NOTIZ"])) {
|
||||
$notizClean = mb_convert_encoding($termin["NOTIZ"], "UTF-8", "auto");
|
||||
$message .= "Notiz: {$notizClean}\n";
|
||||
}
|
||||
|
||||
sendMail(
|
||||
$subject,
|
||||
$message,
|
||||
$_SERVER['SCRIPT_NAME'] ?? 'CLI/Unbekannt',
|
||||
true,
|
||||
$benutzer["EMAIL"],
|
||||
);
|
||||
|
||||
print_r("{$benutzer["EMAIL"]}\n{$message}");
|
||||
|
||||
DatabaseUpdate(
|
||||
"Termine",
|
||||
"ERINNERUNG_AM = ?",
|
||||
"ID = ?",
|
||||
date('d-m-Y H:i:s'),
|
||||
$termin["ID"]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
<?php
|
||||
include_once '../controller/DatabaseController.php';
|
||||
include_once '../controller/DataController.php';
|
||||
include_once __DIR__ . '/../modules/Mail.php';
|
||||
include_once( __DIR__ . '/../../controller/DatabaseController.php');
|
||||
include_once( __DIR__ . '/../../controller/DataController.php');
|
||||
include_once( __DIR__ . '/../../controller/ApiController.php');
|
||||
include_once(__DIR__ . '/../../modules/Mail.php');
|
||||
|
||||
header("Content-Type: application/json");
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
@@ -13,9 +14,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
exit();
|
||||
}
|
||||
|
||||
$validTokens = [
|
||||
"15a437d128c918633e57dc7d18118a073b807644f21731ed5a7ebcf3eca9e242",
|
||||
];
|
||||
$validTokens = DatabaseShow(
|
||||
"API_Credentials",
|
||||
"TOKEN"
|
||||
);
|
||||
|
||||
$validTokens = array_column($validTokens, 'TOKEN');
|
||||
|
||||
$headers = getallheaders();
|
||||
$token = $headers['Authorization'] ?? $headers['authorization'] ?? $_SERVER['HTTP_AUTHORIZATION'] ?? '';
|
||||
@@ -28,18 +32,37 @@ if (!in_array($token, $validTokens)) {
|
||||
"debug_received_token" => $token,
|
||||
"debug_all_headers" => $headers
|
||||
]);
|
||||
|
||||
DatabaseInsert(
|
||||
"API_LOGS",
|
||||
"CLIENT_IP, TOKEN, API, FUNKTION, JSON",
|
||||
(new getSpecificData())->getClientIP(),
|
||||
$token,
|
||||
(new getSpecificData())->getRequestURL(),
|
||||
$function,
|
||||
$error
|
||||
);
|
||||
|
||||
errorMail(
|
||||
"API Login failed",
|
||||
$error,
|
||||
true,
|
||||
);
|
||||
echo $error;
|
||||
|
||||
echo json_encode(
|
||||
[
|
||||
"error" => "Unauthorized",
|
||||
"Contact" => "bruno.eckhardt07@gmail.com",
|
||||
]
|
||||
);
|
||||
exit;
|
||||
}
|
||||
|
||||
$data = null;
|
||||
|
||||
if (in_array($token, $validTokens)) {
|
||||
DatabaseInsert(
|
||||
"API",
|
||||
"API_LOGS",
|
||||
"CLIENT_IP, TOKEN, API, FUNKTION",
|
||||
(new getSpecificData())->getClientIP(),
|
||||
$token,
|
||||
@@ -60,6 +83,13 @@ if (in_array($token, $validTokens)) {
|
||||
"MINUTEN"
|
||||
);
|
||||
}
|
||||
|
||||
if ($function === "Berichte") {
|
||||
$data = DatabaseShowWithFK(
|
||||
"Berufsschulberichte",
|
||||
$Berufsschulberichte,
|
||||
);
|
||||
}
|
||||
|
||||
echo json_encode(
|
||||
[
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
include_once '../controller/DatabaseController.php';
|
||||
include_once '../controller/DataController.php';
|
||||
include_once( __DIR__ . '/../../controller/DatabaseController.php' );
|
||||
include_once( __DIR__ . '/../../controller/DataController.php' );
|
||||
|
||||
header("Content-Type: application/json");
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
@@ -110,4 +110,4 @@ DatabaseInsert(
|
||||
$json
|
||||
);
|
||||
|
||||
echo $json;
|
||||
echo $json;
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
include_once(__DIR__ . '/../../controller/DatabaseController.php');
|
||||
include_once(__DIR__ . '/../../controller/DataController.php');
|
||||
include_once(__DIR__ . '/../../controller/ApiController.php');
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$api = (new ApiController());
|
||||
|
||||
if ($api->urlAuth($_GET['token']) == true ) {
|
||||
|
||||
if (isset($_GET['day'])) {
|
||||
|
||||
$day = $_GET['day'];
|
||||
|
||||
$data = DatabaseShowWithFK(
|
||||
"Stundenplan",
|
||||
$Stundenplan,
|
||||
"ZEITEN, $day",
|
||||
"$day IS NOT NULL AND $day != '' AND $day != '(NULL)' ORDER BY ZEITEN ASC"
|
||||
);
|
||||
} else {
|
||||
$data = DatabaseShowWithFK(
|
||||
"Stundenplan",
|
||||
$Stundenplan,
|
||||
"ZEITEN, MO, DI, MO, DO, FR",
|
||||
);
|
||||
}
|
||||
echo $api->jsonSuccess($data);
|
||||
$api->requestSave();
|
||||
}
|
||||
|
||||
/**
|
||||
* http://localhost:9000/api/v2/lessons.php?token=f3a50684d7f0cc2d9b21601798ea48242fab7481a0d0407a2b8683b0b74a79e3
|
||||
* http://localhost:9000/api/v2/lessons.php?token=f3a50684d7f0cc2d9b21601798ea48242fab7481a0d0407a2b8683b0b74a79e3&day=FR
|
||||
*/
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
include_once(__DIR__ . '/../../controller/DatabaseController.php');
|
||||
include_once(__DIR__ . '/../../controller/DataController.php');
|
||||
include_once(__DIR__ . '/../../controller/ApiController.php');
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$api = (new ApiController());
|
||||
|
||||
if ($api->urlAuth($_GET['token']) == true ) {
|
||||
|
||||
if (isset($_GET['date'])) {
|
||||
|
||||
$date = $_GET['date'];
|
||||
|
||||
$data = DatabaseShowWithFK(
|
||||
"Berufsschulberichte",
|
||||
$Berufsschulberichte,
|
||||
"*",
|
||||
"DATUM = ?",
|
||||
$date
|
||||
);
|
||||
} else {
|
||||
$data = DatabaseShowWithFK(
|
||||
"Berufsschulberichte",
|
||||
$Berufsschulberichte,
|
||||
);
|
||||
}
|
||||
echo $api->jsonSuccess($data);
|
||||
$api->requestSave();
|
||||
}
|
||||
|
||||
/**
|
||||
* http://localhost:9000/api/v2/schoolReports.php?token=f3a50684d7f0cc2d9b21601798ea48242fab7481a0d0407a2b8683b0b74a79e3
|
||||
* http://localhost:9000/api/v2/schoolReports.php?token=f3a50684d7f0cc2d9b21601798ea48242fab7481a0d0407a2b8683b0b74a79e3&date=2025-09-09
|
||||
*/
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
include_once(__DIR__ . '/../../controller/DatabaseController.php');
|
||||
include_once(__DIR__ . '/../../controller/DataController.php');
|
||||
include_once(__DIR__ . '/../../controller/ApiController.php');
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$api = (new ApiController());
|
||||
|
||||
if ($api->urlAuth($_GET['token']) == true ) {
|
||||
|
||||
if (isset($_GET['date'])) {
|
||||
|
||||
$date = $_GET['date'];
|
||||
|
||||
$data = DatabaseShow(
|
||||
"Firmenberichte",
|
||||
"DATUM, PROZESS_1, PROZESS_2, PROZESS_3, PROZESS_4, BEGINN, ENDE_PUBLIC",
|
||||
"DATUM = ?",
|
||||
$date
|
||||
);
|
||||
} else {
|
||||
$data = DatabaseShow(
|
||||
"Firmenberichte",
|
||||
"DATUM, PROZESS_1, PROZESS_2, PROZESS_3, PROZESS_4, BEGINN, ENDE_PUBLIC",
|
||||
);
|
||||
}
|
||||
echo $api->jsonSuccess($data);
|
||||
$api->requestSave();
|
||||
}
|
||||
|
||||
/**
|
||||
* http://localhost:9000/api/v2/workReports.php?token=f3a50684d7f0cc2d9b21601798ea48242fab7481a0d0407a2b8683b0b74a79e3
|
||||
* http://localhost:9000/api/v2/workReports.php?token=f3a50684d7f0cc2d9b21601798ea48242fab7481a0d0407a2b8683b0b74a79e3&date=2025-09-09
|
||||
*/
|
||||
+38
-3
@@ -16,7 +16,10 @@ $serviceGroups = array (
|
||||
'services' =>
|
||||
array (
|
||||
0 => 'home_info',
|
||||
1 => 'links',
|
||||
1 => 'vergabe',
|
||||
2 => 'links',
|
||||
3 => 'einkaufsliste',
|
||||
4 => 'termine',
|
||||
),
|
||||
),
|
||||
'Ausbildung' =>
|
||||
@@ -29,12 +32,14 @@ $serviceGroups = array (
|
||||
1 => 'zeiterfassung',
|
||||
2 => 'berichte_arbeit_gen',
|
||||
3 => 'berichte',
|
||||
4 => 'firmenberichte',
|
||||
5 => 'berufsschulberichte',
|
||||
),
|
||||
),
|
||||
'Logs' =>
|
||||
array (
|
||||
'label' => 'Webserver',
|
||||
'defaultOpen' => true,
|
||||
'defaultOpen' => false,
|
||||
'services' =>
|
||||
array (
|
||||
0 => 'public_request_logs',
|
||||
@@ -77,6 +82,12 @@ $services = array (
|
||||
'active' => true,
|
||||
'adminOnly' => false,
|
||||
),
|
||||
'vergabe' =>
|
||||
array (
|
||||
'name' => 'Vergabe',
|
||||
'active' => true,
|
||||
'adminOnly' => false,
|
||||
),
|
||||
'print_static' =>
|
||||
array (
|
||||
'name' => 'Berichte Drucken',
|
||||
@@ -185,6 +196,30 @@ $services = array (
|
||||
'active' => true,
|
||||
'adminOnly' => false,
|
||||
),
|
||||
'einkaufsliste' =>
|
||||
array (
|
||||
'name' => 'Einkaufsliste',
|
||||
'active' => true,
|
||||
'adminOnly' => false,
|
||||
),
|
||||
'termine' =>
|
||||
array (
|
||||
'name' => 'Termine',
|
||||
'active' => true,
|
||||
'adminOnly' => false,
|
||||
),
|
||||
'firmenberichte' =>
|
||||
array (
|
||||
'name' => 'Firmenberichte',
|
||||
'active' => true,
|
||||
'adminOnly' => false,
|
||||
),
|
||||
'berufsschulberichte' =>
|
||||
array (
|
||||
'name' => 'Berufsschulberichte',
|
||||
'active' => true,
|
||||
'adminOnly' => false,
|
||||
),
|
||||
);
|
||||
|
||||
$linkGroups = array (
|
||||
@@ -375,4 +410,4 @@ $linkButtons = array (
|
||||
'adminOnly' => false,
|
||||
'group' => 'tools',
|
||||
),
|
||||
);
|
||||
);
|
||||
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
require(__DIR__ . '/DatabaseController.php');
|
||||
|
||||
class ApiController {
|
||||
|
||||
/**
|
||||
* method for pretty jsonsuccess output
|
||||
*
|
||||
* @param array $data
|
||||
* @return string
|
||||
*/
|
||||
public function jsonSuccess($data) {
|
||||
return json_encode(
|
||||
[
|
||||
'status' => 'success',
|
||||
'data' => $data,
|
||||
],
|
||||
JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* method for pretty jsonerror output
|
||||
*
|
||||
* @param array $error
|
||||
* @return string
|
||||
*/
|
||||
public function jsonError ($error) {
|
||||
echo json_encode(
|
||||
[
|
||||
'status' => 'failed',
|
||||
'data' => 'none',
|
||||
'error' => $error
|
||||
],
|
||||
JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* get authentication key out database
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getAuthKey () {
|
||||
$validTokens = DatabaseShow(
|
||||
"API_Credentials",
|
||||
"TOKEN"
|
||||
);
|
||||
|
||||
return array_column($validTokens, 'TOKEN');
|
||||
}
|
||||
|
||||
/**
|
||||
* url authentication method via token and optional parameter function
|
||||
*
|
||||
* @param string $token
|
||||
* @param string $function
|
||||
* @return void
|
||||
*/
|
||||
public function urlAuth ($token, $function = '') {
|
||||
|
||||
$validTokens = $this->getAuthKey();
|
||||
if (!in_array($token, $validTokens)) {
|
||||
http_response_code(401);
|
||||
$error = json_encode([
|
||||
"error" => "Unauthorized",
|
||||
"debug_received_token" => $token,
|
||||
]);
|
||||
|
||||
echo $this->jsonError($error);
|
||||
|
||||
DatabaseInsert(
|
||||
"API_LOGS",
|
||||
"CLIENT_IP, TOKEN, API, FUNKTION, JSON",
|
||||
(new getSpecificData())->getClientIP(),
|
||||
$token,
|
||||
(new getSpecificData())->getRequestURL(),
|
||||
$function,
|
||||
$error
|
||||
);
|
||||
|
||||
errorMail(
|
||||
"API Login failed",
|
||||
$error,
|
||||
true,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function requestSave($function = '') {
|
||||
DatabaseInsert(
|
||||
"API_LOGS",
|
||||
"CLIENT_IP, TOKEN, API, FUNKTION, JSON",
|
||||
(new getSpecificData())->getClientIP(),
|
||||
$_GET['token'],
|
||||
(new getSpecificData())->getRequestURL(),
|
||||
$function,
|
||||
'{"status": "success"}',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* create new api token
|
||||
*
|
||||
*/
|
||||
private function createToken() {
|
||||
return bin2hex(random_bytes(32));
|
||||
}
|
||||
|
||||
/**
|
||||
* save created api token
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function saveToken() {
|
||||
try {
|
||||
DatabaseInsert(
|
||||
"API_Credentials",
|
||||
"TOKEN, BENUTZER",
|
||||
$this->createToken(),
|
||||
"API-User"
|
||||
);
|
||||
} catch (Exception $error) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -117,16 +117,20 @@ class getSpecificData {
|
||||
}
|
||||
}
|
||||
|
||||
public function getClientIP(): string {
|
||||
public function getClientIP(): string
|
||||
{
|
||||
if (PHP_SAPI === 'cli') {
|
||||
return 'CLI';
|
||||
}
|
||||
|
||||
$xff = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? null;
|
||||
|
||||
if ($xff) {
|
||||
if (!empty($xff)) {
|
||||
$ips = array_map('trim', explode(',', $xff));
|
||||
$clientIp = $ips[0];
|
||||
return $clientIp;
|
||||
} else {
|
||||
return $_SERVER['REMOTE_ADDR'];
|
||||
return $ips[0];
|
||||
}
|
||||
|
||||
return $_SERVER['REMOTE_ADDR'] ?? 'UNKNOWN';
|
||||
}
|
||||
|
||||
public function getRequestTime(): string {
|
||||
|
||||
@@ -147,10 +147,37 @@ function DatabaseUpdate($table, $set, $where, ...$values) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function getFKValue($conn, $table, $key, $value, $id, &$cache) {
|
||||
if ($id === null) return null;
|
||||
|
||||
$cacheKey = $table . "_" . $id;
|
||||
|
||||
if (isset($cache[$cacheKey])) {
|
||||
return $cache[$cacheKey];
|
||||
}
|
||||
|
||||
$stmt = $conn->prepare("SELECT `$value` FROM `$table` WHERE `$key` = ? LIMIT 1");
|
||||
if (!$stmt) {
|
||||
die("FK Prepare Fehler: " . $conn->error);
|
||||
}
|
||||
|
||||
$stmt->bind_param("i", $id);
|
||||
$stmt->execute();
|
||||
|
||||
$result = $stmt->get_result()->fetch_assoc();
|
||||
$valueResult = $result[$value] ?? null;
|
||||
|
||||
$cache[$cacheKey] = $valueResult;
|
||||
|
||||
return $valueResult;
|
||||
}
|
||||
|
||||
function DatabaseShowWithFK($table, $fkMap = [], $columns = "*", $where = "", ...$values) {
|
||||
include_once 'DatabaseConnect.php';
|
||||
$conn = db_connect();
|
||||
|
||||
$cache = [];
|
||||
|
||||
$select = is_array($columns) ? implode(", ", $columns) : $columns;
|
||||
|
||||
$sql = "SELECT $select FROM `$table`";
|
||||
@@ -161,33 +188,32 @@ function DatabaseShowWithFK($table, $fkMap = [], $columns = "*", $where = "", ..
|
||||
$stmt = $conn->prepare($sql);
|
||||
if (!$stmt) die("Fehler beim Prepare: " . $conn->error);
|
||||
|
||||
if (!$stmt->execute($values)) die("Fehler beim Execute: " . $stmt->error);
|
||||
if (!empty($values)) {
|
||||
$stmt->execute($values);
|
||||
} else {
|
||||
$stmt->execute();
|
||||
}
|
||||
|
||||
$result = $stmt->get_result();
|
||||
if (!$result) die("Fehler beim get_result: " . $stmt->error);
|
||||
|
||||
$columnsResult = [];
|
||||
while ($fieldinfo = $result->fetch_field()) {
|
||||
$columnsResult[] = $fieldinfo->name;
|
||||
}
|
||||
if (!$result) die("Fehler beim get_result: " . $conn->error);
|
||||
|
||||
$rows = $result->fetch_all(MYSQLI_ASSOC);
|
||||
|
||||
foreach ($rows as &$row) {
|
||||
foreach ($fkMap as $fkColumn => $fkInfo) {
|
||||
if (isset($row[$fkColumn]) && $row[$fkColumn] !== null) {
|
||||
$fkStmt = $conn->prepare(
|
||||
"SELECT {$fkInfo['value']} FROM `{$fkInfo['table']}` WHERE {$fkInfo['key']} = ?"
|
||||
);
|
||||
if (!$fkStmt) die("Fehler beim Prepare FK: " . $conn->error);
|
||||
|
||||
$fkStmt->bind_param("i", $row[$fkColumn]);
|
||||
$fkStmt->execute();
|
||||
$fkResult = $fkStmt->get_result();
|
||||
$fkRow = $fkResult->fetch_assoc();
|
||||
|
||||
$row[$fkColumn] = $fkRow ? $fkRow[$fkInfo['value']] : null;
|
||||
if (!isset($row[$fkColumn]) || $row[$fkColumn] === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$row[$fkColumn] = getFKValue(
|
||||
$conn,
|
||||
$fkInfo['table'],
|
||||
$fkInfo['key'],
|
||||
$fkInfo['value'],
|
||||
$row[$fkColumn],
|
||||
$cache
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
require_once(__DIR__ . '/DatabaseController.php');
|
||||
|
||||
class RightsController {
|
||||
|
||||
/**
|
||||
* save new site with sitename
|
||||
*
|
||||
* @param string $sitename
|
||||
*/
|
||||
private function saveSite($sitename) {
|
||||
DatabaseInsert(
|
||||
"Backend_Seiten",
|
||||
"DATEINAME",
|
||||
$sitename
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* get site id by sitename
|
||||
*
|
||||
* @param string $sitename
|
||||
* @return int
|
||||
*/
|
||||
private function getSiteID($sitename) {
|
||||
$id = DatabaseShow(
|
||||
"Backend_Seiten",
|
||||
"ID",
|
||||
"DATEINAME = ?",
|
||||
$sitename
|
||||
);
|
||||
return $id[0]['ID'];
|
||||
}
|
||||
|
||||
/**
|
||||
* get user id
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $lastname
|
||||
* @return int
|
||||
*/
|
||||
private function getUserID($name, $lastname) {
|
||||
$id = DatabaseShow(
|
||||
"Benutzer",
|
||||
"ID",
|
||||
"NAME = ? AND NACHNAME = ?",
|
||||
$name,
|
||||
$lastname
|
||||
);
|
||||
return $id[0]['ID'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Rights Handler for saving default user is Bruno
|
||||
*
|
||||
* @param string $sitename
|
||||
* @param string $name
|
||||
* @param string $lastname
|
||||
* @return string
|
||||
*/
|
||||
public function RightsHandler($sitename, $name = 'Bruno', $lastname = 'Eckhardt') {
|
||||
try {
|
||||
$sitename = strtolower($sitename);
|
||||
$this->saveSite($sitename);
|
||||
|
||||
DatabaseInsert(
|
||||
"Benutzer_Berechtigungen",
|
||||
"BENUTZER_ID, SEITEN_ID",
|
||||
$this->getUserID($name, $lastname),
|
||||
$this->getSiteID($sitename)
|
||||
);
|
||||
} catch (Exception $error) {
|
||||
return json_encode(
|
||||
[
|
||||
"status" => "failed",
|
||||
"error" => $error->getMessage()
|
||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE
|
||||
);
|
||||
}
|
||||
return json_encode(
|
||||
[
|
||||
"status" => "success",
|
||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
include_once(__DIR__ . '/modules/Mail.php');
|
||||
include_once(__DIR__ . '/controller/DataController.php');
|
||||
|
||||
try {
|
||||
session_start();
|
||||
|
||||
$_SESSION = array();
|
||||
|
||||
session_destroy();
|
||||
|
||||
http_response_code(200);
|
||||
|
||||
exit();
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'An error occurred while logging out.']);
|
||||
sendMail(
|
||||
"[ERROR] Logout",
|
||||
"$e",
|
||||
(new getSpecificData())->getRequestURL(),
|
||||
true,
|
||||
);
|
||||
exit();
|
||||
}
|
||||
+1
-1
@@ -27,7 +27,7 @@ document.querySelectorAll('.group-title').forEach(title => {
|
||||
|
||||
const logoutButton = document.getElementById('logoutButton');
|
||||
logoutButton.addEventListener('click', () => {
|
||||
fetch('services/logout.php', { method: 'POST' })
|
||||
fetch('logout', { method: 'POST' })
|
||||
.then(() => {
|
||||
window.location.href = 'login.php';
|
||||
});
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
include_once __DIR__ . '/../controller/DatabaseController.php';
|
||||
include_once __DIR__ . '/../controller/DataController.php';
|
||||
|
||||
function sendMail($subject, $message, $url, bool $log) {
|
||||
function sendMail($subject, $message, $url, bool $log, $to = "bruno.eckhardt07@gmail.com")
|
||||
{
|
||||
$headers = "From: bruno.eckhardt07@gmail.com\r\n";
|
||||
mail(
|
||||
"bruno.eckhardt07@gmail.com",
|
||||
$to,
|
||||
$subject,
|
||||
$message,
|
||||
$headers
|
||||
@@ -14,17 +15,19 @@ function sendMail($subject, $message, $url, bool $log) {
|
||||
if ($log == true) {
|
||||
DatabaseInsert(
|
||||
"Mailing",
|
||||
"DATUM, SUBJECT, MESSAGE, URL, CLIENT_IP",
|
||||
"DATUM, SUBJECT, MESSAGE, MAIL_AN, URL, CLIENT_IP",
|
||||
(new getSpecificData())->getRequestTime(),
|
||||
$subject,
|
||||
$message,
|
||||
$to,
|
||||
$url,
|
||||
(new getSpecificData())->getClientIP(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function errorMail($title, $error, bool $log) {
|
||||
function errorMail($title, $error, bool $log)
|
||||
{
|
||||
$headers = "From: bruno.eckhardt07@gmail.com\r\n";
|
||||
mail(
|
||||
"bruno.eckhardt07@gmail.com",
|
||||
@@ -42,4 +45,4 @@ function errorMail($title, $error, bool $log) {
|
||||
(new getSpecificData())->getClientIP(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+437
-112
@@ -1,29 +1,69 @@
|
||||
<?php
|
||||
// API Tester Service - Als Modul für index.php
|
||||
// Dieser Service dient zum manuellen Testen der API-Endpunkte.
|
||||
// ======================================================
|
||||
// API TESTER
|
||||
// Modul für index.php
|
||||
// ======================================================
|
||||
|
||||
// Basis-Verzeichnisse scannen (Pfad relativ zur index.php)
|
||||
$apiDir = __DIR__ . '/../api/';
|
||||
$apiFiles = glob($apiDir . '*.php');
|
||||
// API Basis-Verzeichnis
|
||||
$apiDir = realpath(__DIR__ . '/../api');
|
||||
|
||||
// Alle PHP APIs rekursiv finden
|
||||
$apiOptions = [];
|
||||
foreach ($apiFiles as $file) {
|
||||
$apiOptions[] = 'api/' . basename($file);
|
||||
|
||||
if ($apiDir !== false) {
|
||||
|
||||
$iterator = new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator(
|
||||
$apiDir,
|
||||
RecursiveDirectoryIterator::SKIP_DOTS
|
||||
)
|
||||
);
|
||||
|
||||
foreach ($iterator as $file) {
|
||||
|
||||
if (!$file->isFile()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($file->getExtension() !== 'php') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// relativen Pfad erzeugen
|
||||
$relativePath = str_replace(
|
||||
$apiDir . DIRECTORY_SEPARATOR,
|
||||
'',
|
||||
$file->getPathname()
|
||||
);
|
||||
|
||||
// Windows kompatibel
|
||||
$relativePath = str_replace('\\', '/', $relativePath);
|
||||
|
||||
$apiOptions[] = 'api/' . $relativePath;
|
||||
}
|
||||
}
|
||||
|
||||
// Token aus deiner get.php entnommen
|
||||
// sortieren
|
||||
sort($apiOptions);
|
||||
|
||||
// Standard Token
|
||||
$defaultToken = "15a437d128c918633e57dc7d18118a073b807644f21731ed5a7ebcf3eca9e242";
|
||||
?>
|
||||
|
||||
<style>
|
||||
/* Spezifische Styles für den API Tester innerhalb des Content-Bereichs */
|
||||
/* ======================================================
|
||||
API TESTER STYLES
|
||||
====================================================== */
|
||||
|
||||
.api-tester-container {
|
||||
width: 100%;
|
||||
max-width: 900px;
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
background-color: var(--card-bg);
|
||||
padding: 25px;
|
||||
border-radius: 10px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--card-border);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.api-tester-container h2 {
|
||||
@@ -31,83 +71,8 @@ $defaultToken = "15a437d128c918633e57dc7d18118a073b807644f21731ed5a7ebcf3eca9e24
|
||||
margin-bottom: 20px;
|
||||
color: var(--btn-bg);
|
||||
border-bottom: 1px solid var(--card-border);
|
||||
padding-bottom: 10px;
|
||||
padding-bottom: 12px;
|
||||
display: block;
|
||||
/* style.css h2 hat flex, wir wollen hier einfaches block */
|
||||
}
|
||||
|
||||
.tester-form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.tester-form-group label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.tester-input {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
background-color: var(--input-bg);
|
||||
border: 1px solid var(--input-border);
|
||||
color: var(--input-text);
|
||||
border-radius: 5px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tester-btn-send {
|
||||
background-color: var(--btn-bg);
|
||||
color: white;
|
||||
padding: 12px 20px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.tester-btn-send:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
#tester-response-container {
|
||||
margin-top: 25px;
|
||||
background-color: var(--input-bg);
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--card-border);
|
||||
min-height: 80px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
#tester-output-content {
|
||||
white-space: pre-wrap;
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
font-size: 0.85rem;
|
||||
margin: 0;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.tester-badge {
|
||||
display: inline-block;
|
||||
padding: 3px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.tester-status-200 {
|
||||
background-color: var(--status-bg);
|
||||
color: var(--status-text);
|
||||
}
|
||||
|
||||
.tester-status-error {
|
||||
background-color: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.tester-grid {
|
||||
@@ -116,140 +81,500 @@ $defaultToken = "15a437d128c918633e57dc7d18118a073b807644f21731ed5a7ebcf3eca9e24
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.tester-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.tester-form-group {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.tester-form-group label {
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
font-weight: bold;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.tester-input,
|
||||
.tester-textarea,
|
||||
select {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
background-color: var(--input-bg);
|
||||
border: 1px solid var(--input-border);
|
||||
color: var(--input-text);
|
||||
border-radius: 6px;
|
||||
box-sizing: border-box;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.tester-textarea {
|
||||
min-height: 120px;
|
||||
resize: vertical;
|
||||
font-family: Consolas, monospace;
|
||||
}
|
||||
|
||||
.tester-btn-send {
|
||||
width: 100%;
|
||||
background-color: var(--btn-bg);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 13px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
transition: 0.2s;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.tester-btn-send:hover {
|
||||
opacity: 0.9;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
#tester-response-container {
|
||||
margin-top: 25px;
|
||||
background-color: var(--input-bg);
|
||||
border: 1px solid var(--card-border);
|
||||
border-radius: 10px;
|
||||
padding: 15px;
|
||||
min-height: 100px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
#tester-output-content {
|
||||
white-space: pre-wrap;
|
||||
font-family: Consolas, monospace;
|
||||
font-size: 0.85rem;
|
||||
margin: 0;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.tester-badge {
|
||||
display: inline-block;
|
||||
padding: 5px 10px;
|
||||
border-radius: 5px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: bold;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.tester-status-200 {
|
||||
background-color: #198754;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.tester-status-error {
|
||||
background-color: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.tester-status-warning {
|
||||
background-color: #ffc107;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.tester-row {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tester-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
white-space: nowrap;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.tester-small-btn {
|
||||
background: transparent;
|
||||
border: 1px solid var(--input-border);
|
||||
color: var(--text);
|
||||
padding: 6px 10px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tester-small-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.tester-response-info {
|
||||
margin-bottom: 12px;
|
||||
font-size: 0.85rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
|
||||
.tester-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.tester-row {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="api-tester-container">
|
||||
|
||||
<h2>🛠️ API Tester</h2>
|
||||
|
||||
<!-- API Auswahl -->
|
||||
<div class="tester-form-group">
|
||||
<label for="endpoint">API Endpunkt</label>
|
||||
|
||||
<select id="endpoint" class="tester-input" onchange="checkEndpoint()">
|
||||
|
||||
<?php foreach ($apiOptions as $opt): ?>
|
||||
<option value="<?= $opt ?>"><?= $opt ?></option>
|
||||
|
||||
<option value="<?= htmlspecialchars($opt) ?>">
|
||||
<?= htmlspecialchars($opt) ?>
|
||||
</option>
|
||||
|
||||
<?php endforeach; ?>
|
||||
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Methode + Function -->
|
||||
<div class="tester-grid">
|
||||
|
||||
<div class="tester-form-group">
|
||||
<label for="method">HTTP Methode</label>
|
||||
|
||||
<select id="method" class="tester-input">
|
||||
<option value="GET">GET</option>
|
||||
<option value="POST">POST</option>
|
||||
<option value="PUT">PUT</option>
|
||||
<option value="DELETE">DELETE</option>
|
||||
<option value="PATCH">PATCH</option>
|
||||
<option value="OPTIONS">OPTIONS</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="tester-form-group" id="function-group">
|
||||
<label for="function-header">Function Header</label>
|
||||
|
||||
<input type="text" id="function-header" class="tester-input" placeholder="z.B. IPs, Ueberstunden">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Token -->
|
||||
<div class="tester-form-group">
|
||||
|
||||
<label for="token">Authorization Token</label>
|
||||
<input type="text" id="token" class="tester-input" value="<?= $defaultToken ?>">
|
||||
|
||||
<div class="tester-row">
|
||||
|
||||
<input type="text" id="token" class="tester-input" value="<?= htmlspecialchars($defaultToken) ?>">
|
||||
|
||||
<label class="tester-checkbox">
|
||||
<input type="checkbox" id="send-token" checked>
|
||||
Mitsenden
|
||||
</label>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Eigene Header -->
|
||||
<div class="tester-form-group">
|
||||
|
||||
<label for="custom-headers">
|
||||
Zusätzliche Header (JSON)
|
||||
</label>
|
||||
|
||||
<textarea id="custom-headers" class="tester-textarea" placeholder='{
|
||||
"X-Test": "123",
|
||||
"Client": "Frontend"
|
||||
}'></textarea>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- JSON Body -->
|
||||
<div class="tester-form-group" id="json-body-group">
|
||||
|
||||
<label for="json-body">
|
||||
JSON Body
|
||||
</label>
|
||||
|
||||
<textarea id="json-body" class="tester-textarea" placeholder='{
|
||||
"test": true
|
||||
}'></textarea>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Datei Upload -->
|
||||
<div class="tester-form-group hidden" id="file-group">
|
||||
<label for="files">Dateien (files)</label>
|
||||
|
||||
<label for="files">Dateien Upload</label>
|
||||
|
||||
<input type="file" id="files" class="tester-input" multiple>
|
||||
|
||||
</div>
|
||||
|
||||
<button class="tester-btn-send" onclick="sendApiRequest()">Anfrage senden 🚀</button>
|
||||
<!-- Buttons -->
|
||||
<div class="tester-grid">
|
||||
|
||||
<button class="tester-btn-send" onclick="sendApiRequest()">
|
||||
Anfrage senden 🚀
|
||||
</button>
|
||||
|
||||
<button class="tester-btn-send" onclick="clearResponse()">
|
||||
Ausgabe leeren 🧹
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Ausgabe -->
|
||||
<div id="tester-response-container">
|
||||
<div style="text-align: center; color: #888; padding: 10px;">Bereit für Anfragen.</div>
|
||||
|
||||
<div style="text-align:center; opacity:0.7; padding:15px;">
|
||||
Bereit für Anfragen.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// ======================================================
|
||||
// CHECK ENDPOINT
|
||||
// ======================================================
|
||||
|
||||
function checkEndpoint() {
|
||||
|
||||
const endpoint = document.getElementById('endpoint').value;
|
||||
|
||||
const fileGroup = document.getElementById('file-group');
|
||||
const functionGroup = document.getElementById('function-group');
|
||||
const jsonBodyGroup = document.getElementById('json-body-group');
|
||||
const methodSelect = document.getElementById('method');
|
||||
|
||||
// Standard: alles ausblenden
|
||||
// reset
|
||||
fileGroup.classList.add('hidden');
|
||||
functionGroup.classList.remove('hidden');
|
||||
jsonBodyGroup.classList.remove('hidden');
|
||||
|
||||
// upload api
|
||||
if (endpoint.includes('upload.php')) {
|
||||
|
||||
if (endpoint === 'api/upload.php') {
|
||||
// Nur bei exakt api/upload.php Dateien anzeigen
|
||||
fileGroup.classList.remove('hidden');
|
||||
functionGroup.classList.add('hidden');
|
||||
jsonBodyGroup.classList.add('hidden');
|
||||
|
||||
methodSelect.value = 'POST';
|
||||
}
|
||||
|
||||
if (endpoint === 'api/get.php') {
|
||||
// get api
|
||||
if (endpoint.includes('get.php')) {
|
||||
methodSelect.value = 'GET';
|
||||
}
|
||||
}
|
||||
|
||||
// ======================================================
|
||||
// CLEAR RESPONSE
|
||||
// ======================================================
|
||||
|
||||
function clearResponse() {
|
||||
|
||||
document.getElementById('tester-response-container').innerHTML = `
|
||||
<div style="text-align:center; opacity:0.7; padding:15px;">
|
||||
Ausgabe geleert.
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// ======================================================
|
||||
// SEND REQUEST
|
||||
// ======================================================
|
||||
|
||||
async function sendApiRequest() {
|
||||
|
||||
const endpoint = document.getElementById('endpoint').value;
|
||||
const method = document.getElementById('method').value;
|
||||
const token = document.getElementById('token').value;
|
||||
const functionHeader = document.getElementById('function-header').value;
|
||||
const customHeadersInput = document.getElementById('custom-headers').value;
|
||||
const jsonBodyInput = document.getElementById('json-body').value;
|
||||
const fileInput = document.getElementById('files');
|
||||
const sendToken = document.getElementById('send-token').checked;
|
||||
|
||||
const responseContainer = document.getElementById('tester-response-container');
|
||||
|
||||
responseContainer.innerHTML = '<div style="text-align: center; padding: 10px;">Lade...</div>';
|
||||
responseContainer.innerHTML = `
|
||||
<div style="text-align:center; padding:15px;">
|
||||
Anfrage läuft...
|
||||
</div>
|
||||
`;
|
||||
|
||||
let headers = {
|
||||
'Authorization': token
|
||||
};
|
||||
let headers = {};
|
||||
|
||||
if (endpoint.includes('get.php') && functionHeader) {
|
||||
// Token
|
||||
if (sendToken && token.trim() !== '') {
|
||||
headers['Authorization'] = token;
|
||||
}
|
||||
|
||||
// Function Header
|
||||
if (functionHeader.trim() !== '') {
|
||||
headers['Function'] = functionHeader;
|
||||
}
|
||||
|
||||
// Custom Headers
|
||||
if (customHeadersInput.trim() !== '') {
|
||||
|
||||
try {
|
||||
|
||||
const customHeaders = JSON.parse(customHeadersInput);
|
||||
|
||||
headers = {
|
||||
...headers,
|
||||
...customHeaders
|
||||
};
|
||||
|
||||
} catch (e) {
|
||||
|
||||
responseContainer.innerHTML = `
|
||||
<div style="color:#dc3545; padding:15px;">
|
||||
Fehlerhafte Header JSON Struktur.
|
||||
</div>
|
||||
`;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let options = {
|
||||
method: method,
|
||||
headers: headers
|
||||
};
|
||||
|
||||
// Upload
|
||||
if (endpoint.includes('upload.php') && fileInput.files.length > 0) {
|
||||
|
||||
const formData = new FormData();
|
||||
|
||||
for (let i = 0; i < fileInput.files.length; i++) {
|
||||
formData.append('files[]', fileInput.files[i]);
|
||||
}
|
||||
|
||||
options.body = formData;
|
||||
|
||||
} else {
|
||||
|
||||
// JSON Body
|
||||
if (
|
||||
['POST', 'PUT', 'PATCH', 'DELETE']
|
||||
.includes(method) &&
|
||||
jsonBodyInput.trim() !== ''
|
||||
) {
|
||||
|
||||
try {
|
||||
|
||||
JSON.parse(jsonBodyInput);
|
||||
|
||||
headers['Content-Type'] = 'application/json';
|
||||
|
||||
options.body = jsonBodyInput;
|
||||
|
||||
} catch (e) {
|
||||
|
||||
responseContainer.innerHTML = `
|
||||
<div style="color:#dc3545; padding:15px;">
|
||||
Fehlerhafte JSON Body Struktur.
|
||||
</div>
|
||||
`;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const startedAt = performance.now();
|
||||
|
||||
try {
|
||||
|
||||
const response = await fetch(endpoint, options);
|
||||
|
||||
const duration = (performance.now() - startedAt).toFixed(0);
|
||||
|
||||
const status = response.status;
|
||||
|
||||
const rawText = await response.text();
|
||||
|
||||
let resultText = rawText;
|
||||
|
||||
// JSON hübsch formatieren
|
||||
try {
|
||||
|
||||
const json = JSON.parse(rawText);
|
||||
|
||||
resultText = JSON.stringify(json, null, 2);
|
||||
|
||||
} catch (e) {}
|
||||
|
||||
const badgeClass = status >= 200 && status < 300 ? 'tester-status-200' : 'tester-status-error';
|
||||
let badgeClass = 'tester-status-error';
|
||||
|
||||
if (status >= 200 && status < 300) {
|
||||
badgeClass = 'tester-status-200';
|
||||
} else if (status >= 300 && status < 400) {
|
||||
badgeClass = 'tester-status-warning';
|
||||
}
|
||||
|
||||
responseContainer.innerHTML = `
|
||||
<div class="tester-badge ${badgeClass}">Status: ${status}</div>
|
||||
<pre id="tester-output-content"></pre>
|
||||
`;
|
||||
<div class="tester-badge ${badgeClass}">
|
||||
Status: ${status}
|
||||
</div>
|
||||
|
||||
<div class="tester-response-info">
|
||||
⏱️ ${duration} ms
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="tester-small-btn"
|
||||
onclick="copyResponse()"
|
||||
>
|
||||
Antwort kopieren
|
||||
</button>
|
||||
|
||||
<pre id="tester-output-content"></pre>
|
||||
`;
|
||||
|
||||
document.getElementById('tester-output-content').textContent = resultText;
|
||||
|
||||
} catch (error) {
|
||||
responseContainer.innerHTML = `<div style="color:#dc3545; padding: 10px;">Fehler: ${error.message}</div>`;
|
||||
|
||||
responseContainer.innerHTML = `
|
||||
<div style="color:#dc3545; padding:15px;">
|
||||
Fehler: ${error.message}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
// Initial check
|
||||
// ======================================================
|
||||
// COPY RESPONSE
|
||||
// ======================================================
|
||||
|
||||
function copyResponse() {
|
||||
|
||||
const content = document.getElementById('tester-output-content');
|
||||
|
||||
if (!content) {
|
||||
return;
|
||||
}
|
||||
|
||||
navigator.clipboard.writeText(content.textContent);
|
||||
|
||||
alert('Antwort kopiert.');
|
||||
}
|
||||
|
||||
// Initial
|
||||
checkEndpoint();
|
||||
</script>
|
||||
@@ -120,7 +120,7 @@ $count = is_array($displayData) ? count($displayData) : 0;
|
||||
<div class="main">
|
||||
<div class="content">
|
||||
<h2 style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<span>📊 Berichtte-Vorschau:</span>
|
||||
<span>📊 Berichte-Vorschau:</span>
|
||||
|
||||
<div>
|
||||
<a href="?service=<?= $selectedService ?>&mode=fk">
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
<?php
|
||||
include_once(__DIR__ . '/../controller/DataController.php');
|
||||
include_once(__DIR__ . '/../controller/DatabaseController.php');
|
||||
|
||||
$alleFaecher = DatabaseShow("Fach", "ID, FACH");
|
||||
$alleRaeume = DatabaseShow("Raum", "ID, RAUM_NR");
|
||||
$alleLehrer = DatabaseShow("Lehrer", "ID, NAME");
|
||||
|
||||
/*
|
||||
1 => ["fach_id" => 1, "raum_id" => 5, "lehrer_id" => 8], // LF1
|
||||
1 => ["fach_id" => 2, "raum_id" => 4, "lehrer_id" => 11], // LF2
|
||||
1 => ["fach_id" => 3, "raum_id" => 1, "lehrer_id" => 2], // LF3
|
||||
1 => ["fach_id" => 4, "raum_id" => 1, "lehrer_id" => 5], // LF4
|
||||
6 => ["fach_id" => 5, "raum_id" => 3, "lehrer_id" => 1], // LF5
|
||||
1 => ["fach_id" => 6, "raum_id" => 5, "lehrer_id" => 4], // Sozi
|
||||
1 => ["fach_id" => 8, "raum_id" => 5, "lehrer_id" => 7], // VWL
|
||||
1 => ["fach_id" => 9, "raum_id" => 5, "lehrer_id" => 3], // DE
|
||||
1 => ["fach_id" => 10, "raum_id" => 5, "lehrer_id" => 3], // ENG
|
||||
*/
|
||||
|
||||
$Stundenplan = [
|
||||
"MO" => [
|
||||
1 => ["fach_id" => 3, "raum_id" => 1, "lehrer_id" => 2], // LF3
|
||||
2 => ["fach_id" => 3, "raum_id" => 1, "lehrer_id" => 2], // LF3
|
||||
3 => ["fach_id" => 3, "raum_id" => 1, "lehrer_id" => 2], // LF3
|
||||
4 => ["fach_id" => 6, "raum_id" => 5, "lehrer_id" => 4], // Sozi
|
||||
5 => ["fach_id" => 10, "raum_id" => 5, "lehrer_id" => 3], // Eng
|
||||
6 => ["fach_id" => 10, "raum_id" => 5, "lehrer_id" => 3], // Eng
|
||||
7 => ["fach_id" => 2, "raum_id" => 3, "lehrer_id" => 11], // LF2
|
||||
8 => ["fach_id" => 2, "raum_id" => 3, "lehrer_id" => 11], // LF2
|
||||
],
|
||||
"DI" => [
|
||||
1 => ["fach_id" => 5, "raum_id" => 3, "lehrer_id" => 1], // LF5
|
||||
2 => ["fach_id" => 5, "raum_id" => 3, "lehrer_id" => 1], // LF5
|
||||
3 => ["fach_id" => 5, "raum_id" => 1, "lehrer_id" => 5], // LF5
|
||||
4 => ["fach_id" => 2, "raum_id" => 3, "lehrer_id" => 6], // LF2
|
||||
5 => ["fach_id" => 2, "raum_id" => 3, "lehrer_id" => 6], // LF2
|
||||
],
|
||||
"MI" => [
|
||||
1 => ["fach_id" => 8, "raum_id" => 5, "lehrer_id" => 7], // VWL
|
||||
2 => ["fach_id" => 8, "raum_id" => 5, "lehrer_id" => 7], // VWL
|
||||
3 => ["fach_id" => 9, "raum_id" => 5, "lehrer_id" => 3], // DE
|
||||
4 => ["fach_id" => 9, "raum_id" => 5, "lehrer_id" => 3], // DE
|
||||
5 => ["fach_id" => 2, "raum_id" => 4, "lehrer_id" => 6], // LF2
|
||||
6 => ["fach_id" => 2, "raum_id" => 4, "lehrer_id" => 6], // LF2
|
||||
7 => ["fach_id" => 8, "raum_id" => 5, "lehrer_id" => 7], // VWL
|
||||
],
|
||||
"DO" => [
|
||||
1 => ["fach_id" => 3, "raum_id" => 1, "lehrer_id" => 2], // LF3
|
||||
2 => ["fach_id" => 3, "raum_id" => 1, "lehrer_id" => 2], // LF3
|
||||
3 => ["fach_id" => 3, "raum_id" => 1, "lehrer_id" => 2], // LF3
|
||||
4 => ["fach_id" => 6, "raum_id" => 5, "lehrer_id" => 4], // Sozi
|
||||
5 => ["fach_id" => 6, "raum_id" => 5, "lehrer_id" => 4], // Sozi
|
||||
6 => ["fach_id" => 4, "raum_id" => 1, "lehrer_id" => 5], // LF4
|
||||
7 => ["fach_id" => 4, "raum_id" => 1, "lehrer_id" => 5], // LF4
|
||||
8 => ["fach_id" => 4, "raum_id" => 1, "lehrer_id" => 5], // LF4
|
||||
],
|
||||
"FR" => [
|
||||
1 => ["fach_id" => 1, "raum_id" => 5, "lehrer_id" => 8], // LF1
|
||||
2 => ["fach_id" => 1, "raum_id" => 5, "lehrer_id" => 8], // LF1
|
||||
3 => ["fach_id" => 1, "raum_id" => 5, "lehrer_id" => 8], // LF1
|
||||
4 => ["fach_id" => 5, "raum_id" => 3, "lehrer_id" => 1], // LF5
|
||||
5 => ["fach_id" => 5, "raum_id" => 3, "lehrer_id" => 1], // LF5
|
||||
6 => ["fach_id" => 5, "raum_id" => 3, "lehrer_id" => 1], // LF5
|
||||
]
|
||||
];
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Berufsschulbericht</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="main">
|
||||
<div class="content" style="max-width: 1100px; margin: 0 auto; padding: 20px;">
|
||||
|
||||
<div class="form-container">
|
||||
<h3 style="margin-top: 0;">Berufsschulbericht erfassen</h3>
|
||||
|
||||
<form method="post">
|
||||
<div class="form-group" style="max-width: 220px; margin-bottom: 25px;">
|
||||
<label for="datum">Datum auswählen</label>
|
||||
<input type="date" name="datum" id="datum" required>
|
||||
</div>
|
||||
|
||||
<div class="vergabe-grid"
|
||||
style="font-weight: bold; padding-bottom: 8px; margin-bottom: 12px; border-bottom: 1px solid var(--card-border);">
|
||||
<div>Stunde</div>
|
||||
<div>Fach</div>
|
||||
<div>Raum</div>
|
||||
<div>Lehrer</div>
|
||||
<div>Inhalt / Thema</div>
|
||||
</div>
|
||||
|
||||
<?php for ($i = 1; $i <= 8; $i++): ?>
|
||||
<div class="vergabe-grid" style="margin-bottom: 10px; align-items: center;">
|
||||
|
||||
<div>
|
||||
<span class="badge"
|
||||
style="display: inline-block; min-width: 90px; text-align: center; padding: 8px 12px; font-weight: bold;">Stunde
|
||||
<?= $i ?></span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<select name="stunden[<?= $i ?>][fach_id]" data-stunde="<?= $i ?>" data-type="fach_id">
|
||||
<option value="">-- Fach --</option>
|
||||
<?php foreach ($alleFaecher as $row): ?>
|
||||
<option value="<?= $row['ID'] ?>"><?= htmlspecialchars($row['FACH']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<select name="stunden[<?= $i ?>][raum_id]" data-stunde="<?= $i ?>" data-type="raum_id">
|
||||
<option value="">-- Raum --</option>
|
||||
<?php foreach ($alleRaeume as $row): ?>
|
||||
<option value="<?= $row['ID'] ?>"><?= htmlspecialchars($row['RAUM_NR']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<select name="stunden[<?= $i ?>][lehrer_id]" data-stunde="<?= $i ?>" data-type="lehrer_id">
|
||||
<option value="">-- Lehrer --</option>
|
||||
<?php foreach ($alleLehrer as $row): ?>
|
||||
<option value="<?= $row['ID'] ?>"><?= htmlspecialchars($row['NAME']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<input type="text" name="stunden[<?= $i ?>][inhalt_text]" placeholder="Inhalt der Stunde..."
|
||||
style="margin: 0; padding: 10px;">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<?php endfor; ?>
|
||||
|
||||
<div style="margin-top: 25px; text-align: right;">
|
||||
<button type="submit" class="btn-submit-vergabe"
|
||||
style="width: 220px; display: inline-flex; font-weight: bold;">
|
||||
Bericht speichern
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const stundenplan = <?= json_encode($Stundenplan) ?>;
|
||||
const wochentage = ["SO", "MO", "DI", "MI", "DO", "FR", "SA"];
|
||||
|
||||
document.getElementById('datum').addEventListener('change', function() {
|
||||
const gewaehltesDatum = new Date(this.value);
|
||||
if (isNaN(gewaehltesDatum)) return;
|
||||
|
||||
let tagKey = wochentage[gewaehltesDatum.getDay()];
|
||||
|
||||
if (tagKey === "SO" || tagKey === "SA") {
|
||||
tagKey = "MO";
|
||||
}
|
||||
|
||||
const tagesPlan = stundenplan[tagKey] || {};
|
||||
|
||||
document.querySelectorAll('.vergabe-grid select').forEach(select => {
|
||||
const stunde = select.getAttribute('data-stunde');
|
||||
const type = select.getAttribute('data-type');
|
||||
|
||||
const vordefinierteId = (tagesPlan[stunde] && tagesPlan[stunde][type]) ? tagesPlan[stunde][
|
||||
type
|
||||
] : "";
|
||||
|
||||
select.value = vordefinierteId;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$datum = $_POST['datum'] ?? null;
|
||||
$werte = [$datum];
|
||||
|
||||
for ($i = 1; $i <= 8; $i++) {
|
||||
$stunde = $_POST['stunden'][$i] ?? [];
|
||||
$werte[] = !empty($stunde['fach_id']) ? $stunde['fach_id'] : null;
|
||||
$werte[] = !empty($stunde['lehrer_id']) ? $stunde['lehrer_id'] : null;
|
||||
$werte[] = !empty($stunde['raum_id']) ? $stunde['raum_id'] : null;
|
||||
$werte[] = !empty($stunde['inhalt_text']) ? $stunde['inhalt_text'] : null;
|
||||
}
|
||||
|
||||
DatabaseInsert(
|
||||
"Berufsschulberichte",
|
||||
"DATUM, FACH_S1, LEHRER_S1, RAUM_S1, INHALT_S1, FACH_S2, LEHRER_S2, RAUM_S2, INHALT_S2, FACH_S3, LEHRER_S3, RAUM_S3, INHALT_S3, FACH_S4, LEHRER_S4, RAUM_S4, INHALT_S4, FACH_S5, LEHRER_S5, RAUM_S5, INHALT_S5, FACH_S6, LEHRER_S6, RAUM_S6, INHALT_S6, FACH_S7, LEHRER_S7, RAUM_S7, INHALT_S7, FACH_S8, LEHRER_S8, RAUM_S8, INHALT_S8",
|
||||
...$werte
|
||||
);
|
||||
|
||||
echo "<div class='status-msg' style='position: fixed; bottom: 20px; right: 20px; z-index: 10000;'>Bericht gespeichert!</div>";
|
||||
}
|
||||
?>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
include_once __DIR__ . '/../controller/DatabaseController.php';
|
||||
include_once __DIR__ . '/../controller/RoutesController.php';
|
||||
|
||||
$view = $_GET['view'] ?? 'show';
|
||||
$selectedService = $_GET['service'] ?? null;
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_entry'])) {
|
||||
$artikel = $_POST['artikel'];
|
||||
$menge = $_POST['menge'];
|
||||
|
||||
DatabaseInsert(
|
||||
"Einkaufsliste",
|
||||
"PRODUKT, MENGE",
|
||||
$artikel,
|
||||
$menge
|
||||
);
|
||||
|
||||
redirectToWebsite((new getSpecificData())->getReferer());
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_entry'])) {
|
||||
$id = $_POST['id'];
|
||||
|
||||
DatabaseDelete("Einkaufsliste", "ID = ?", $id);
|
||||
|
||||
redirectToWebsite((new getSpecificData())->getReferer());
|
||||
exit;
|
||||
}
|
||||
|
||||
$entries = DatabaseShow(
|
||||
"Einkaufsliste"
|
||||
);
|
||||
|
||||
?>
|
||||
|
||||
<div>
|
||||
<div class="vergabe-form">
|
||||
<form method="POST" class="vergabe-grid">
|
||||
|
||||
<div class="form-group">
|
||||
<label>Artikel</label>
|
||||
<input type="text" name="artikel" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Menge</label>
|
||||
<input type="text" name="menge" required>
|
||||
</div>
|
||||
|
||||
<button type="submit" name="add_entry" class="btn-submit-vergabe">
|
||||
💾 Speichern
|
||||
</button>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<div class="table-container" style="margin-top: 30px;">
|
||||
<h3>Einkaufsliste</h3>
|
||||
|
||||
<?php if (!empty($entries)): ?>
|
||||
<table class="db-table mobile-responsive-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Artikel</th>
|
||||
<th>Menge</th>
|
||||
<th style="width: 100px;">Aktion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($entries as $entry): ?>
|
||||
<tr>
|
||||
<td data-label="Artikel"><?= htmlspecialchars($entry['PRODUKT'] ?? '') ?></td>
|
||||
<td data-label="Menge"><?= htmlspecialchars($entry['MENGE'] ?? '') ?></td>
|
||||
<td data-label="Aktion">
|
||||
<form method="POST" onsubmit="return confirm('Möchtest du diesen Eintrag wirklich löschen?');" style="margin: 0;">
|
||||
<input type="hidden" name="id" value="<?= $entry['ID'] ?? '' ?>">
|
||||
<button type="submit" name="delete_entry" class="btn-delete">
|
||||
🗑️ Löschen
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php else: ?>
|
||||
<p>Keine Einträge vorhanden.</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
include_once(__DIR__ . '/../controller/DataController.php');
|
||||
include_once(__DIR__ . '/../controller/DatabaseController.php');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_bericht'])) {
|
||||
$prozess1 = $_POST['prozess1'];
|
||||
$prozess2 = $_POST['prozess2'];
|
||||
$prozess3 = $_POST['prozess3'];
|
||||
$prozess4 = $_POST['prozess4'];
|
||||
$startzeit = $_POST['startzeit'];
|
||||
$endzeit = $_POST['endzeit'];
|
||||
$datum = $_POST['datum'];
|
||||
|
||||
// IHK konforme zeiten berechnen und Überstunden berechnen
|
||||
$soll = new DateTime($startzeit);
|
||||
$soll->modify("+8 hours 30 minutes");
|
||||
|
||||
$randomMin = rand(1, 15);
|
||||
$endePublicDt = clone $soll;
|
||||
$endePublicDt->modify("+{$randomMin} minutes");
|
||||
$endePublic = $endePublicDt->format("H:i");
|
||||
|
||||
$real = DateTime::createFromFormat("H:i", $endzeit);
|
||||
|
||||
$beginnDt = new DateTime($startzeit);
|
||||
$arbeitsDiff = $beginnDt->diff($real);
|
||||
$arbeitsMinuten = ($arbeitsDiff->h * 60) + $arbeitsDiff->i;
|
||||
$sollMinuten = 8 * 60 + 30;
|
||||
|
||||
if ($arbeitsMinuten <= $sollMinuten) {
|
||||
$overtime = 0;
|
||||
} else {
|
||||
$diff = $soll->diff($real);
|
||||
$overtime = ($diff->h * 60) + $diff->i;
|
||||
}
|
||||
|
||||
DatabaseInsert(
|
||||
"Firmenberichte",
|
||||
"DATUM, PROZESS_1, PROZESS_2, PROZESS_3, PROZESS_4, BEGINN, ENDE_REAL, ENDE_PUBLIC, OVERTIME",
|
||||
$datum,
|
||||
$prozess1,
|
||||
$prozess2,
|
||||
$prozess3,
|
||||
$prozess4,
|
||||
$startzeit,
|
||||
$endzeit,
|
||||
$endePublic,
|
||||
$overtime
|
||||
);
|
||||
|
||||
redirectToWebsite((new getSpecificData())->getReferer());
|
||||
exit;
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
<div>
|
||||
<div class="berichte-container">
|
||||
<form method="POST" class="berichte-form">
|
||||
|
||||
<div class="form-group">
|
||||
<label>Prozess 1</label>
|
||||
<input type="text" name="prozess1" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Prozess 2</label>
|
||||
<input type="text" name="prozess2" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Prozess 3</label>
|
||||
<input type="text" name="prozess3" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Prozess 4</label>
|
||||
<input type="text" name="prozess4" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Startzeit</label>
|
||||
<input type="time" name="startzeit" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Endzeit</label>
|
||||
<input type="time" name="endzeit" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Datum</label>
|
||||
<input type="date" name="datum" required>
|
||||
</div>
|
||||
|
||||
<button type="submit" name="add_bericht" class="btn-submit-bericht">
|
||||
Bericht Speichern
|
||||
</button>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,5 +1,8 @@
|
||||
<?php
|
||||
|
||||
include_once(__DIR__ . '/../controller/RightsController.php');
|
||||
include_once(__DIR__ . '/../config.php');
|
||||
|
||||
$configFile = 'config.php';
|
||||
$backupDir = 'config_backups';
|
||||
|
||||
@@ -47,6 +50,7 @@ if (isset($_POST['save']) && ($_SESSION['logged_in'] ?? false)) {
|
||||
$serviceToGroupMapping[$s['targetGroup']][] = $finalId;
|
||||
}
|
||||
}
|
||||
(new RightsController())->RightsHandler($s['name']);
|
||||
}
|
||||
|
||||
$newServiceGroups = [];
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
<?php
|
||||
include_once(__DIR__ . '/../controller/DatabaseController.php');
|
||||
include_once(__DIR__ . '/../controller/DataController.php');
|
||||
|
||||
$msg = "";
|
||||
$username = $_SESSION['username'] ?? 'Gast';
|
||||
|
||||
if (isset($_GET['action']) && $_GET['action'] === 'delete') {
|
||||
if (isset($_GET['serien_id']) && !empty($_GET['serien_id'])) {
|
||||
$serienId = $_GET['serien_id'];
|
||||
DatabaseDelete("Termine", "SERIEN_ID = ?", $serienId);
|
||||
$msg = "Komplette Terminserie erfolgreich gelöscht!";
|
||||
} elseif (isset($_GET['id'])) {
|
||||
$deleteId = (int)$_GET['id'];
|
||||
DatabaseDelete("Termine", "ID = ?", $deleteId);
|
||||
$msg = "Termin erfolgreich gelöscht!";
|
||||
}
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_termin'])) {
|
||||
$datum = $_POST['datum'] ?? '';
|
||||
$zeit = $_POST['zeit'] ?? '';
|
||||
$name = $_POST['name'] ?? '';
|
||||
$notiz = $_POST['notiz'] ?? '';
|
||||
$wiederholung = $_POST['wiederholung'] ?? 'none';
|
||||
$enddatum = $_POST['enddatum'] ?? '';
|
||||
|
||||
if (!empty($datum) && !empty($zeit) && !empty($name)) {
|
||||
|
||||
if ($wiederholung !== 'none' && !empty($enddatum)) {
|
||||
$currentDate = new DateTime($datum);
|
||||
$endDate = new DateTime($enddatum);
|
||||
|
||||
if ($endDate < $currentDate) {
|
||||
$msg = "Das Enddatum darf nicht vor dem Startdatum liegen.";
|
||||
} else {
|
||||
$insertedCount = 0;
|
||||
$serienId = uniqid('serie_', true);
|
||||
|
||||
$intervalMapping = [
|
||||
'daily' => '+1 day',
|
||||
'weekly' => '+1 week',
|
||||
'monthly' => '+1 month'
|
||||
];
|
||||
|
||||
$modifier = $intervalMapping[$wiederholung] ?? null;
|
||||
|
||||
if ($modifier) {
|
||||
while ($currentDate <= $endDate) {
|
||||
DatabaseInsert(
|
||||
"Termine",
|
||||
"DATUM, ZEIT, NAME, NOTIZ, ERSTELLT_VON, SERIEN_ID, INTERVALL, SERIEN_ENDE",
|
||||
$currentDate->format('Y-m-d'),
|
||||
$zeit,
|
||||
$name,
|
||||
$notiz,
|
||||
$username,
|
||||
$serienId,
|
||||
$wiederholung,
|
||||
$enddatum
|
||||
);
|
||||
$insertedCount++;
|
||||
$currentDate->modify($modifier);
|
||||
}
|
||||
$msg = "Serientermin ($insertedCount Termine) erfolgreich eingetragen!";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
DatabaseInsert(
|
||||
"Termine",
|
||||
"DATUM, ZEIT, NAME, NOTIZ, ERSTELLT_VON",
|
||||
$datum,
|
||||
$zeit,
|
||||
$name,
|
||||
$notiz,
|
||||
$username
|
||||
);
|
||||
$msg = "Termin erfolgreich eingetragen!";
|
||||
}
|
||||
} else {
|
||||
$msg = "Bitte alle Pflichtfelder ausfüllen.";
|
||||
}
|
||||
}
|
||||
|
||||
$termine = DatabaseShow(
|
||||
"Termine",
|
||||
"MAX(ID) as ID, MIN(DATUM) as DATUM, ZEIT, NAME, NOTIZ, ERSTELLT_VON, SERIEN_ID, INTERVALL, SERIEN_ENDE",
|
||||
"ERSTELLT_VON = ? GROUP BY IFNULL(SERIEN_ID, ID) ORDER BY DATUM ASC, ZEIT ASC",
|
||||
$username
|
||||
);
|
||||
|
||||
$intervallNamen = [
|
||||
'daily' => 'Täglich',
|
||||
'weekly' => 'Wöchentlich',
|
||||
'monthly' => 'Monatlich'
|
||||
];
|
||||
?>
|
||||
|
||||
<?php if (!empty($msg)): ?>
|
||||
<div class="status-msg"><?= htmlspecialchars($msg) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="form-container">
|
||||
<h3>📅 Neuen Termin vergeben</h3>
|
||||
<form method="POST" class="vergabe-form">
|
||||
<div class="vergabe-grid">
|
||||
<div class="form-group">
|
||||
<label for="datum">Start-Datum *</label>
|
||||
<input type="date" id="datum" name="datum" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="zeit">Uhrzeit *</label>
|
||||
<input type="time" id="zeit" name="zeit" required
|
||||
style="width:100%; padding:10px; border-radius:5px; border:1px solid var(--input-border); background:var(--input-bg); color:var(--input-text);">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="name">Name / Zweck *</label>
|
||||
<input type="text" id="name" name="name" placeholder="z.B. Kundentermin" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="notiz">Notiz (Optional)</label>
|
||||
<input type="text" id="notiz" name="notiz" placeholder="Details zum Termin...">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="wiederholung">Wiederholung</label>
|
||||
<select id="wiederholung" name="wiederholung" onchange="toggleEnddatum()"
|
||||
style="width:100%; padding:10px; border-radius:5px; border:1px solid var(--input-border); background:var(--input-bg); color:var(--input-text);">
|
||||
<option value="none">Keine Wiederholung</option>
|
||||
<option value="daily">Täglich</option>
|
||||
<option value="weekly">Wöchentlich</option>
|
||||
<option value="monthly">Monatlich</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" id="enddatum-group" style="display: none;">
|
||||
<label for="enddatum">Wiederholen bis *</label>
|
||||
<input type="date" id="enddatum" name="enddatum">
|
||||
</div>
|
||||
<button type="submit" name="add_termin" class="btn-submit-vergabe">Speichern</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="form-container">
|
||||
<h3>📋 Aktuelle Terminbelegungen</h3>
|
||||
<div class="table-container">
|
||||
<table class="db-table mobile-responsive-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Datum / Turnus</th>
|
||||
<th>Uhrzeit</th>
|
||||
<th>Termin / Name</th>
|
||||
<th>Notiz</th>
|
||||
<th>Eingetragen von</th>
|
||||
<th>Aktion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($termine)): ?>
|
||||
<tr>
|
||||
<td colspan="6" style="text-align: center; padding: 20px;">Keine Termine eingetragen.</td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($termine as $termin): ?>
|
||||
<?php
|
||||
$dateFormatted = date('d.m.Y', strtotime($termin['DATUM']));
|
||||
$timeFormatted = date('H:i', strtotime($termin['ZEIT']));
|
||||
$istSerie = !empty($termin['SERIEN_ID']);
|
||||
?>
|
||||
<tr>
|
||||
<td data-label="Datum:">
|
||||
<strong><?= htmlspecialchars($dateFormatted) ?></strong>
|
||||
<?php if ($istSerie): ?>
|
||||
<br>
|
||||
<small style="color: var(--primary-color, #007bff); font-style: italic;">
|
||||
(<?= $intervallNamen[$termin['INTERVALL']] ?? '' ?>
|
||||
bis <?= date('d.m.Y', strtotime($termin['SERIEN_ENDE'])) ?>)
|
||||
</small>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td data-label="Uhrzeit:"><?= htmlspecialchars($timeFormatted) ?></td>
|
||||
<td data-label="Termin / Name:">
|
||||
<strong><?= htmlspecialchars($termin['NAME']) ?></strong>
|
||||
<?php if ($istSerie): ?>
|
||||
<span
|
||||
style="font-size: 0.8em; background: #e1f5fe; color: #0288d1; padding: 2px 6px; border-radius: 10px; margin-left: 5px;">Wiederholend</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td data-label="Notiz:"><?= htmlspecialchars($termin['NOTIZ'] ?: '-') ?></td>
|
||||
<td data-label="Von:"><span class="badge"><?= htmlspecialchars($termin['ERSTELLT_VON']) ?></span>
|
||||
</td>
|
||||
<td data-label="Aktion:">
|
||||
<?php if ($istSerie): ?>
|
||||
<a href="?service=termine&action=delete&serien_id=<?= urlencode($termin['SERIEN_ID']) ?>"
|
||||
onclick="return confirm('Möchtest du die GESAMTE Terminserie löschen?');">
|
||||
<button class="btn-delete" style="background-color: #d32f2f;">löschen</button>
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<a href="?service=termine&action=delete&id=<?= $termin['ID'] ?>"
|
||||
onclick="return confirm('Möchtest du diesen Termin wirklich löschen?');">
|
||||
<button class="btn-delete">Löschen</button>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function toggleEnddatum() {
|
||||
var wiederholung = document.getElementById('wiederholung').value;
|
||||
var enddatumGroup = document.getElementById('enddatum-group');
|
||||
var enddatumInput = document.getElementById('enddatum');
|
||||
|
||||
if (wiederholung !== 'none') {
|
||||
enddatumGroup.style.display = 'block';
|
||||
enddatumInput.required = true;
|
||||
} else {
|
||||
enddatumGroup.style.display = 'none';
|
||||
enddatumInput.required = false;
|
||||
enddatumInput.value = '';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
include_once __DIR__ . '/../controller/DatabaseController.php';
|
||||
include_once __DIR__ . '/../controller/RoutesController.php';
|
||||
|
||||
$conn = db_connect();
|
||||
$view = $_GET['view'] ?? 'show';
|
||||
$selectedService = $_GET['service'] ?? null;
|
||||
|
||||
// --- LOGIK: Überstunden-Eintrag (Abbau) speichern ---
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_entry'])) {
|
||||
$was = $_POST['was'];
|
||||
$wer = $_POST['wer'];
|
||||
$wann = $_POST['wann'];
|
||||
$rueckgabe = $_POST['rueckgabe'];
|
||||
|
||||
DatabaseInsert(
|
||||
"Vergabe",
|
||||
"WAS, WER, WANN, RUECKGABE",
|
||||
$was,
|
||||
$wer,
|
||||
$wann,
|
||||
$rueckgabe
|
||||
);
|
||||
|
||||
redirectToWebsite((new getSpecificData())->getReferer());
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_entry'])) {
|
||||
$id = $_POST['id'];
|
||||
|
||||
DatabaseDelete("Vergabe", "ID = ?", $id);
|
||||
|
||||
redirectToWebsite((new getSpecificData())->getReferer());
|
||||
exit;
|
||||
}
|
||||
|
||||
$entries = DatabaseShow(
|
||||
"Vergabe"
|
||||
);
|
||||
|
||||
?>
|
||||
|
||||
<div class="arbeitsberichte-wrapper">
|
||||
<div class="vergabe-form">
|
||||
<form method="POST" class="vergabe-grid">
|
||||
|
||||
<div class="form-group">
|
||||
<label>Wann ausgeliehen</label>
|
||||
<input type="date" name="wann" value="<?= date('Y-m-d') ?>" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Rückgabe</label>
|
||||
<input type="date" name="rueckgabe" value="<?= date('Y-m-d') ?>" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>An wen</label>
|
||||
<input type="text" name="wer" placeholder="Name eingeben" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Was</label>
|
||||
<input type="text" name="was" placeholder="Gegenstand eingeben" required>
|
||||
</div>
|
||||
|
||||
<button type="submit" name="add_entry" class="btn-submit-vergabe">
|
||||
💾 Speichern
|
||||
</button>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<div class="table-container" style="margin-top: 30px;">
|
||||
<h3>Aktuelle Vergaben</h3>
|
||||
|
||||
<?php if (!empty($entries)): ?>
|
||||
<table class="db-table mobile-responsive-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Was</th>
|
||||
<th>Wer</th>
|
||||
<th>Wann ausgeliehen</th>
|
||||
<th>Rückgabe</th>
|
||||
<th style="width: 100px;">Aktion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($entries as $entry): ?>
|
||||
<tr>
|
||||
<td data-label="Was"><?= htmlspecialchars($entry['WAS'] ?? '') ?></td>
|
||||
<td data-label="Wer"><?= htmlspecialchars($entry['WER'] ?? '') ?></td>
|
||||
<td data-label="Wann"><?= htmlspecialchars($entry['WANN'] ?? '') ?></td>
|
||||
<td data-label="Rückgabe"><?= htmlspecialchars($entry['RUECKGABE'] ?? '') ?></td>
|
||||
<td data-label="Aktion">
|
||||
<form method="POST" onsubmit="return confirm('Möchtest du diesen Eintrag wirklich löschen?');" style="margin: 0;">
|
||||
<input type="hidden" name="id" value="<?= $entry['ID'] ?? '' ?>">
|
||||
<button type="submit" name="delete_entry" class="btn-delete">
|
||||
🗑️ Löschen
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php else: ?>
|
||||
<p>Keine Einträge vorhanden.</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
+334
-3
@@ -981,7 +981,7 @@ body.dark-mode .info-value {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 20px;
|
||||
table-layout: fixed;
|
||||
/* table-layout: fixed; */
|
||||
/* sorgt für feste Spaltenbreite */
|
||||
}
|
||||
|
||||
@@ -997,9 +997,9 @@ body.dark-mode .info-value {
|
||||
border-bottom: 1px solid var(--card-border);
|
||||
|
||||
/* Wichtig für gleiche Größe */
|
||||
overflow: hidden;
|
||||
/* overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
white-space: nowrap; */
|
||||
}
|
||||
|
||||
.db-table tr:hover {
|
||||
@@ -1070,4 +1070,335 @@ select {
|
||||
/* Dark Mode Pfeil */
|
||||
body.dark-mode select {
|
||||
background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg fill='%23ccc' height='20' viewBox='0 0 20 20' width='20' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M5 7l5 5 5-5z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* ===== Vergabe Formular ===== */
|
||||
.vergabe-form {
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--card-border);
|
||||
border-left: 5px solid var(--btn-bg);
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
margin-bottom: 25px;
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
/* Desktop-First Grid Layout */
|
||||
.vergabe-grid {
|
||||
display: grid;
|
||||
/* Nutzt minmax, damit die Datumsfelder auf kleineren Desktop-Monitoren nicht zerquetschen */
|
||||
grid-template-columns: minmax(140px, 1fr) minmax(140px, 1fr) 1.3fr 2fr auto;
|
||||
gap: 15px;
|
||||
align-items: end;
|
||||
}
|
||||
|
||||
.vergabe-grid .form-group {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.vergabe-grid input,
|
||||
.btn-submit-vergabe {
|
||||
width: 100%;
|
||||
/* Wichtig für Responsiveness */
|
||||
padding: 10px 16px;
|
||||
font-size: 15px;
|
||||
border-radius: 5px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body.dark-mode input[type="date"] {
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
/* Dedizierter Speicher-Button Style */
|
||||
.btn-submit-vergabe {
|
||||
background: var(--btn-add-bg);
|
||||
color: white;
|
||||
border: none;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
height: 43px;
|
||||
/* Gleicht die Höhe der Inputs inklusive Padding/Border perfekt aus */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background-color 0.2s ease, transform 0.1s ease;
|
||||
}
|
||||
|
||||
.btn-submit-vergabe:hover {
|
||||
opacity: 0.9;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.btn-submit-vergabe:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
/* ===== Mobile Optimierung (Tablets & Smartphones) ===== */
|
||||
@media (max-width: 1024px) {
|
||||
.vergabe-grid {
|
||||
/* Schaltet auf Mobilgeräten in eine saubere 1-spaltige Liste um */
|
||||
grid-template-columns: 1fr;
|
||||
gap: 12px;
|
||||
/* Schafft vertikalen Abstand zwischen den Feldern */
|
||||
}
|
||||
|
||||
.btn-submit-vergabe {
|
||||
margin-top: 5px;
|
||||
height: 48px;
|
||||
/* Größere Touch-Fläche für Smartphones */
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== Globale Mobile Anpassungen ===== */
|
||||
@media (max-width: 768px) {
|
||||
|
||||
/* 1. Haupt-Layout untereinander brechen lassen */
|
||||
.main {
|
||||
flex-direction: column;
|
||||
height: calc(100vh - 60px);
|
||||
/* Behält die feste Gesamthöhe bei */
|
||||
overflow-y: auto;
|
||||
/* Erlaubt das Scrollen der gesamten Seite falls nötig */
|
||||
}
|
||||
|
||||
/* 2. Sidebar nimmt volle Breite ein und passt sich dem Inhalt an */
|
||||
.sidebar {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
/* Keine feste Höhe mehr auf Mobile */
|
||||
border-right: none;
|
||||
border-bottom: 1px solid #333;
|
||||
/* Trennlinie nach unten statt nach rechts */
|
||||
overflow-y: visible;
|
||||
/* Scrollbar hier deaktivieren */
|
||||
}
|
||||
|
||||
/* 3. Inhaltsbereich nimmt den Rest des Platzes ein */
|
||||
.content {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
overflow-y: visible;
|
||||
/* Verhindert doppelte Scrollbalken */
|
||||
padding: 15px 10px;
|
||||
}
|
||||
|
||||
/* 4. Das Vergabe-Formular bricht sauber in eine Spalte um */
|
||||
.vergabe-grid {
|
||||
grid-template-columns: 1fr !important;
|
||||
/* Erzwingt eine Spalte */
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* 5. Inputs und Buttons nehmen die volle Breite der Box ein */
|
||||
.vergabe-grid input,
|
||||
.btn-submit-vergabe,
|
||||
.vergabe-grid button {
|
||||
width: 100% !important;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Optional: Header-Buttons (Logout / Light Mode) für Mobile optimieren, falls sie überlappen */
|
||||
#darkModeToggle,
|
||||
#logoutButton {
|
||||
position: static;
|
||||
/* Entfernt absolute Positionierung im mobilen Header */
|
||||
margin: 5px;
|
||||
padding: 6px 12px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== Mobile Optimierung für die Vergabe-Tabelle ===== */
|
||||
@media (max-width: 768px) {
|
||||
|
||||
/* Verstecke die normale Tabellen-Überschrift */
|
||||
.mobile-responsive-table thead {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Mache aus der Tabelle ein Block-Layout */
|
||||
.mobile-responsive-table,
|
||||
.mobile-responsive-table tbody,
|
||||
.mobile-responsive-table tr,
|
||||
.mobile-responsive-table td {
|
||||
display: block;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
/* Jede Zeile wird zu einer schicken Card */
|
||||
.mobile-responsive-table tr {
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--card-border);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 15px;
|
||||
padding: 10px;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
/* Einzelne Zellen nebeneinander/untereinander stylen */
|
||||
.mobile-responsive-table td {
|
||||
text-align: right !important;
|
||||
padding: 8px 10px !important;
|
||||
border-bottom: 1px solid var(--card-border);
|
||||
position: relative;
|
||||
white-space: normal !important;
|
||||
/* Erlaubt Textumbruch auf Handys */
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.mobile-responsive-table td:last-child {
|
||||
border-bottom: none;
|
||||
padding-top: 12px !important;
|
||||
}
|
||||
|
||||
/* Die Beschriftung (z.B. "Was:") links einblenden */
|
||||
.mobile-responsive-table td::before {
|
||||
content: attr(data-label);
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-weight: bold;
|
||||
font-size: 0.9rem;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
/* Lösch-Button auf Mobile über die gesamte Breite strecken für leichtere Bedienung */
|
||||
.mobile-responsive-table .btn-delete {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== Optimiertes Firmenberichte Erfassungsformular ===== */
|
||||
|
||||
.berichte-container {
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--card-border);
|
||||
border-top: 4px solid #007bff;
|
||||
/* Eleganter blauer Akzentstreifen oben statt links */
|
||||
border-radius: 12px;
|
||||
padding: 30px;
|
||||
max-width: 500px;
|
||||
/* Kompakt und perfekt lesbar auf Monitoren */
|
||||
margin: 40px auto;
|
||||
/* Schön zentriert mit Luft nach oben/unten */
|
||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.08);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
/* Tiefenwirkung für den Dark Mode */
|
||||
body.dark-mode .berichte-container {
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
|
||||
background: #161616;
|
||||
/* Passt sich perfekt deiner CSS-Variable an */
|
||||
}
|
||||
|
||||
.berichte-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.berichte-form .form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.berichte-form .form-group label {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 700;
|
||||
color: var(--text);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.8px;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
/* Einheitlicher, moderner Look für ALLE Input-Typen */
|
||||
.berichte-form input[type="text"],
|
||||
.berichte-form input[type="time"],
|
||||
.berichte-form input[type="date"] {
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
background: var(--input-bg);
|
||||
border: 1px solid var(--input-border);
|
||||
color: var(--input-text);
|
||||
border-radius: 8px;
|
||||
font-size: 0.95rem;
|
||||
box-sizing: border-box;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
/* Behebt das Problem mit den grellweißen Zeit/Datumsfeldern im Dark Mode */
|
||||
body.dark-mode .berichte-form input[type="time"],
|
||||
body.dark-mode .berichte-form input[type="date"] {
|
||||
color-scheme: dark;
|
||||
/* Zwingt den Browser, das native Dropdown dunkel zu machen */
|
||||
background: #222;
|
||||
border-color: #444;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Fokus-Effekt (weicher blauer Schein) */
|
||||
.berichte-form input:focus {
|
||||
outline: none;
|
||||
border-color: #007bff;
|
||||
box-shadow: 0 0 0 4px rgba(0, 123, 255, 0.15);
|
||||
}
|
||||
|
||||
body.dark-mode .berichte-form input:focus {
|
||||
box-shadow: 0 0 0 4px rgba(0, 123, 255, 0.3);
|
||||
}
|
||||
|
||||
/* Vollbreiter, moderner Absende-Button */
|
||||
.btn-submit-bericht {
|
||||
width: 100%;
|
||||
background: #28a745;
|
||||
/* Kräftiges Success-Grün */
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 14px;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
margin-top: 10px;
|
||||
box-shadow: 0 4px 12px rgba(40, 167, 69, 0.2);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-submit-bericht:hover {
|
||||
background: #218838;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 6px 15px rgba(40, 167, 69, 0.3);
|
||||
}
|
||||
|
||||
.btn-submit-bericht:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
/* Kleine Bildschirme (Smartphones) */
|
||||
@media (max-width: 600px) {
|
||||
.berichte-container {
|
||||
margin: 15px;
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user