Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6992ed5d9 | ||
|
|
d88db54635 | ||
|
|
24cc12374f | ||
|
|
9ec0465401 | ||
|
|
133b1d57ed | ||
|
|
1ad59a9881 | ||
|
|
c540d06939 | ||
|
|
52c11cc1b9 | ||
|
|
37fa819e97 |
No files matched your search
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
RewriteEngine On
|
RewriteEngine On
|
||||||
|
|
||||||
RewriteRule ^(Commands|config_backups|controller|modules)($|/.*) - [F,L,NC]
|
RewriteRule ^(Commands|config_backups|controller|modules|uploads)($|/.*) - [F,L,NC]
|
||||||
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
|
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
|
||||||
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
|
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
// reset_einkaufsliste.php
|
||||||
|
// Aufruf z.B. per Cron: php /pfad/zu/reset_einkaufsliste.php
|
||||||
|
|
||||||
|
include_once(__DIR__ . '/../controller/DatabaseController.php');
|
||||||
|
|
||||||
|
$result = DatabaseUpdate(
|
||||||
|
'Einkaufsliste',
|
||||||
|
'`STATUS` = ?',
|
||||||
|
'`TYP` = ? AND `STATUS` = ?',
|
||||||
|
'aktiv', 'vorlage', 'erledigt'
|
||||||
|
);
|
||||||
|
|
||||||
|
echo $result
|
||||||
|
? "Vorlagen-Artikel wurden zurückgesetzt.\n"
|
||||||
|
: "Fehler beim Zurücksetzen.\n";
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
<?php
|
||||||
|
session_set_cookie_params([
|
||||||
|
'lifetime' => 0,
|
||||||
|
'path' => '/',
|
||||||
|
'domain' => '',
|
||||||
|
'secure' => false,
|
||||||
|
'httponly' => true,
|
||||||
|
'samesite' => 'Lax'
|
||||||
|
]);
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
ini_set('display_errors', '0');
|
||||||
|
|
||||||
|
if (empty($_SESSION['user_logged_in'])) {
|
||||||
|
http_response_code(401);
|
||||||
|
echo json_encode(['error' => 'Nicht eingeloggt']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($_SESSION['is_admin'])) {
|
||||||
|
http_response_code(403);
|
||||||
|
echo json_encode(['error' => 'Keine Berechtigung']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
include_once(__DIR__ . '/../../../controller/DatabaseController.php');
|
||||||
|
include_once(__DIR__ . '/../../../controller/NotifyController.php');
|
||||||
|
|
||||||
|
$daten = json_decode(file_get_contents('php://input'), true) ?? [];
|
||||||
|
$action = $daten['action'] ?? '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
$notificationCtl = new Notification();
|
||||||
|
|
||||||
|
switch ($action) {
|
||||||
|
|
||||||
|
case 'get_entries': {
|
||||||
|
$entries = $notificationCtl->getAllMessages();
|
||||||
|
|
||||||
|
foreach ($entries as &$entry) {
|
||||||
|
$entry['GESEHEN_ANZAHL'] = $notificationCtl->countSeenBy((int)$entry['ID']);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'entries' => array_values($entries)]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'add_entry': {
|
||||||
|
$titel = trim($daten['titel'] ?? '');
|
||||||
|
$nachricht = trim($daten['nachricht'] ?? '');
|
||||||
|
|
||||||
|
if ($titel === '' || $nachricht === '') {
|
||||||
|
throw new InvalidArgumentException('Titel und Nachricht erforderlich');
|
||||||
|
}
|
||||||
|
|
||||||
|
$notificationCtl->createMessage($titel, $nachricht);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'edit_entry': {
|
||||||
|
$id = $daten['id'] ?? null;
|
||||||
|
$titel = trim($daten['titel'] ?? '');
|
||||||
|
$nachricht = trim($daten['nachricht'] ?? '');
|
||||||
|
|
||||||
|
if (!is_numeric($id)) {
|
||||||
|
throw new InvalidArgumentException('Ungültige ID');
|
||||||
|
}
|
||||||
|
if ($titel === '' || $nachricht === '') {
|
||||||
|
throw new InvalidArgumentException('Titel und Nachricht erforderlich');
|
||||||
|
}
|
||||||
|
|
||||||
|
$notificationCtl->updateMessage((int)$id, $titel, $nachricht);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'delete_entry': {
|
||||||
|
$id = $daten['id'] ?? null;
|
||||||
|
|
||||||
|
if (!is_numeric($id)) {
|
||||||
|
throw new InvalidArgumentException('Ungültige ID');
|
||||||
|
}
|
||||||
|
|
||||||
|
$notificationCtl->deleteMessage((int)$id);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new InvalidArgumentException("Unbekannte Aktion: $action");
|
||||||
|
}
|
||||||
|
} catch (InvalidArgumentException $e) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['error' => $e->getMessage()]);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
error_log($e->getMessage());
|
||||||
|
echo json_encode(['error' => 'Serverfehler']);
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
<?php
|
||||||
|
session_set_cookie_params([
|
||||||
|
'lifetime' => 0,
|
||||||
|
'path' => '/',
|
||||||
|
'domain' => '',
|
||||||
|
'secure' => false,
|
||||||
|
'httponly' => true,
|
||||||
|
'samesite' => 'Lax'
|
||||||
|
]);
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
ini_set('display_errors', '0');
|
||||||
|
|
||||||
|
if (empty($_SESSION['user_logged_in'])) {
|
||||||
|
http_response_code(401);
|
||||||
|
echo json_encode(['error' => 'Nicht eingeloggt']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
include_once(__DIR__ . '/../../../controller/DatabaseController.php');
|
||||||
|
|
||||||
|
$UPLOAD_DIR = __DIR__ . '/../../../uploads/';
|
||||||
|
|
||||||
|
$daten = json_decode(file_get_contents('php://input'), true) ?? [];
|
||||||
|
$action = $daten['action'] ?? '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
switch ($action) {
|
||||||
|
|
||||||
|
case 'get_entries': {
|
||||||
|
$entries = DatabaseShow(
|
||||||
|
"Dateien",
|
||||||
|
"ID, ORIGINALNAME, GROESSE, HOCHGELADEN_AM",
|
||||||
|
"HOCHGELADEN_VON = ?",
|
||||||
|
$_SESSION['username']
|
||||||
|
);
|
||||||
|
|
||||||
|
usort($entries, fn($a, $b) => strtotime($b['HOCHGELADEN_AM']) <=> strtotime($a['HOCHGELADEN_AM']));
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'entries' => array_values($entries)]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'delete_entry': {
|
||||||
|
$id = $daten['id'] ?? null;
|
||||||
|
|
||||||
|
if (!is_numeric($id)) {
|
||||||
|
throw new InvalidArgumentException('Ungültige ID');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nur eigene Dateien löschen dürfen
|
||||||
|
$rows = DatabaseShow(
|
||||||
|
"Dateien",
|
||||||
|
"SPEICHERNAME, HOCHGELADEN_VON",
|
||||||
|
"ID = ?",
|
||||||
|
(int)$id
|
||||||
|
);
|
||||||
|
|
||||||
|
if (empty($rows)) {
|
||||||
|
throw new InvalidArgumentException('Datei nicht gefunden');
|
||||||
|
}
|
||||||
|
if ($rows[0]['HOCHGELADEN_VON'] !== $_SESSION['username']) {
|
||||||
|
http_response_code(403);
|
||||||
|
echo json_encode(['error' => 'Keine Berechtigung']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pfad = $UPLOAD_DIR . $rows[0]['SPEICHERNAME'];
|
||||||
|
if (file_exists($pfad)) {
|
||||||
|
unlink($pfad);
|
||||||
|
}
|
||||||
|
|
||||||
|
DatabaseDelete("Dateien", "ID = ?", (int)$id);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new InvalidArgumentException("Unbekannte Aktion: $action");
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (InvalidArgumentException $e) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['error' => $e->getMessage()]);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
error_log($e->getMessage());
|
||||||
|
echo json_encode(['error' => 'Serverfehler']);
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
session_set_cookie_params([
|
||||||
|
'lifetime' => 0,
|
||||||
|
'path' => '/',
|
||||||
|
'domain' => '',
|
||||||
|
'secure' => false,
|
||||||
|
'httponly' => true,
|
||||||
|
'samesite' => 'Lax'
|
||||||
|
]);
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
if (empty($_SESSION['user_logged_in'])) {
|
||||||
|
http_response_code(401);
|
||||||
|
die('Nicht eingeloggt');
|
||||||
|
}
|
||||||
|
|
||||||
|
include_once(__DIR__ . '/../../../controller/DatabaseController.php');
|
||||||
|
|
||||||
|
$UPLOAD_DIR = __DIR__ . '/../../../uploads/';
|
||||||
|
|
||||||
|
$id = $_GET['id'] ?? null;
|
||||||
|
|
||||||
|
if (!is_numeric($id)) {
|
||||||
|
http_response_code(400);
|
||||||
|
die('Ungültige ID');
|
||||||
|
}
|
||||||
|
|
||||||
|
$rows = DatabaseShow(
|
||||||
|
"Dateien",
|
||||||
|
"ORIGINALNAME, SPEICHERNAME, MIME_TYPE, HOCHGELADEN_VON",
|
||||||
|
"ID = ?",
|
||||||
|
(int)$id
|
||||||
|
);
|
||||||
|
|
||||||
|
if (empty($rows)) {
|
||||||
|
http_response_code(404);
|
||||||
|
die('Datei nicht gefunden');
|
||||||
|
}
|
||||||
|
|
||||||
|
$datei = $rows[0];
|
||||||
|
|
||||||
|
// Nur eigene Dateien herunterladen dürfen (oder Admin, falls gewünscht - siehe Hinweis unten)
|
||||||
|
if ($datei['HOCHGELADEN_VON'] !== $_SESSION['username']) {
|
||||||
|
http_response_code(403);
|
||||||
|
die('Keine Berechtigung');
|
||||||
|
}
|
||||||
|
|
||||||
|
$pfad = $UPLOAD_DIR . $datei['SPEICHERNAME'];
|
||||||
|
|
||||||
|
if (!file_exists($pfad)) {
|
||||||
|
http_response_code(404);
|
||||||
|
die('Datei nicht mehr vorhanden');
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Content-Type: ' . $datei['MIME_TYPE']);
|
||||||
|
header('Content-Disposition: attachment; filename="' . basename($datei['ORIGINALNAME']) . '"');
|
||||||
|
header('Content-Length: ' . filesize($pfad));
|
||||||
|
readfile($pfad);
|
||||||
|
exit;
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
<?php
|
||||||
|
session_set_cookie_params([
|
||||||
|
'lifetime' => 0,
|
||||||
|
'path' => '/',
|
||||||
|
'domain' => '',
|
||||||
|
'secure' => false,
|
||||||
|
'httponly' => true,
|
||||||
|
'samesite' => 'Lax'
|
||||||
|
]);
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
ini_set('display_errors', '0');
|
||||||
|
|
||||||
|
if (empty($_SESSION['user_logged_in'])) {
|
||||||
|
http_response_code(401);
|
||||||
|
echo json_encode(['error' => 'Nicht eingeloggt']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
include_once(__DIR__ . '/../../../controller/DatabaseController.php');
|
||||||
|
|
||||||
|
$UPLOAD_DIR = __DIR__ . '/../../../uploads/';
|
||||||
|
$MAX_GROESSE = 20 * 1024 * 1024; // 20 MB
|
||||||
|
$VERBOTENE_ENDUNGEN = ['php', 'phtml', 'php3', 'php4', 'php5', 'phar', 'exe', 'sh', 'bat', 'cgi', 'htaccess'];
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (empty($_FILES['datei'])) {
|
||||||
|
throw new InvalidArgumentException('Keine Datei übermittelt');
|
||||||
|
}
|
||||||
|
|
||||||
|
$datei = $_FILES['datei'];
|
||||||
|
|
||||||
|
if ($datei['error'] !== UPLOAD_ERR_OK) {
|
||||||
|
throw new InvalidArgumentException('Fehler beim Upload (Code ' . $datei['error'] . ')');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($datei['size'] > $MAX_GROESSE) {
|
||||||
|
throw new InvalidArgumentException('Datei zu groß (max. 20 MB)');
|
||||||
|
}
|
||||||
|
|
||||||
|
$originalName = basename($datei['name']); // gegen Path Traversal im Namen
|
||||||
|
$endung = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
|
||||||
|
|
||||||
|
if (in_array($endung, $VERBOTENE_ENDUNGEN, true)) {
|
||||||
|
throw new InvalidArgumentException('Dieser Dateityp ist nicht erlaubt');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zufälliger, kollisionssicherer Speichername - Original bleibt nur in der DB erhalten
|
||||||
|
$speicherName = bin2hex(random_bytes(16)) . ($endung !== '' ? '.' . $endung : '');
|
||||||
|
$zielPfad = $UPLOAD_DIR . $speicherName;
|
||||||
|
|
||||||
|
if (!move_uploaded_file($datei['tmp_name'], $zielPfad)) {
|
||||||
|
throw new Exception('Datei konnte nicht gespeichert werden');
|
||||||
|
}
|
||||||
|
|
||||||
|
$mimeType = mime_content_type($zielPfad) ?: 'application/octet-stream';
|
||||||
|
|
||||||
|
DatabaseInsert(
|
||||||
|
"Dateien",
|
||||||
|
"ORIGINALNAME, SPEICHERNAME, GROESSE, MIME_TYPE, HOCHGELADEN_VON",
|
||||||
|
$originalName,
|
||||||
|
$speicherName,
|
||||||
|
$datei['size'],
|
||||||
|
$mimeType,
|
||||||
|
$_SESSION['username']
|
||||||
|
);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
|
||||||
|
} catch (InvalidArgumentException $e) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['error' => $e->getMessage()]);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
error_log($e->getMessage());
|
||||||
|
echo json_encode(['error' => 'Serverfehler']);
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
session_set_cookie_params([
|
||||||
|
'lifetime' => 0,
|
||||||
|
'path' => '/',
|
||||||
|
'domain' => '',
|
||||||
|
'secure' => false,
|
||||||
|
'httponly' => true,
|
||||||
|
'samesite' => 'Lax'
|
||||||
|
]);
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
ini_set('display_errors', '0');
|
||||||
|
|
||||||
|
if (empty($_SESSION['user_logged_in'])) {
|
||||||
|
http_response_code(401);
|
||||||
|
echo json_encode(['error' => 'Nicht eingeloggt']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
include_once(__DIR__ . '/../../../controller/DatabaseController.php');
|
||||||
|
include_once(__DIR__ . '/../../../controller/NotifyController.php');
|
||||||
|
|
||||||
|
$daten = json_decode(file_get_contents('php://input'), true) ?? [];
|
||||||
|
$action = $daten['action'] ?? '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
switch ($action) {
|
||||||
|
|
||||||
|
case 'mark_seen': {
|
||||||
|
$nachrichtId = $daten['nachricht_id'] ?? null;
|
||||||
|
|
||||||
|
if (!is_numeric($nachrichtId)) {
|
||||||
|
throw new InvalidArgumentException('Ungültige Nachrichten-ID');
|
||||||
|
}
|
||||||
|
|
||||||
|
$notificationCtl = new Notification();
|
||||||
|
$notificationCtl->markAsSeen((int)$nachrichtId);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new InvalidArgumentException("Unbekannte Aktion: $action");
|
||||||
|
}
|
||||||
|
} catch (InvalidArgumentException $e) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['error' => $e->getMessage()]);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
error_log($e->getMessage());
|
||||||
|
echo json_encode(['error' => 'Serverfehler']);
|
||||||
|
}
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
include_once(__DIR__ . '/DatabaseController.php');
|
||||||
|
include_once(__DIR__ . '/DataController.php');
|
||||||
|
|
||||||
|
class Notification
|
||||||
|
{
|
||||||
|
protected $table;
|
||||||
|
protected $seenTable;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->table = "Benachrichtigung";
|
||||||
|
$this->seenTable = "Benachrichtigung_Gesehen";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ermittelt die Benutzer-ID des aktuell eingeloggten Nutzers.
|
||||||
|
*/
|
||||||
|
private function getCurrentUserId(): ?int
|
||||||
|
{
|
||||||
|
$username = $_SESSION['username'] ?? null;
|
||||||
|
if (!$username) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$rows = DatabaseShow(
|
||||||
|
"Benutzer",
|
||||||
|
"ID",
|
||||||
|
"NAME = ?",
|
||||||
|
$username
|
||||||
|
);
|
||||||
|
|
||||||
|
return $rows[0]['ID'] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alle Nachrichten (z.B. für eine Verwaltungsansicht).
|
||||||
|
*/
|
||||||
|
public function getAllMessages(): array
|
||||||
|
{
|
||||||
|
return DatabaseShow(
|
||||||
|
$this->table,
|
||||||
|
"ID, TITEL, NACHRICHT"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nur die Nachrichten, die der aktuelle Nutzer noch NICHT gesehen hat.
|
||||||
|
* Genau das braucht das Modal, um zu entscheiden ob/was es anzeigt.
|
||||||
|
*/
|
||||||
|
public function getUnseenMessages(): array
|
||||||
|
{
|
||||||
|
$userId = $this->getCurrentUserId();
|
||||||
|
if ($userId === null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$alleNachrichten = DatabaseShow(
|
||||||
|
$this->table,
|
||||||
|
"ID, TITEL, NACHRICHT"
|
||||||
|
);
|
||||||
|
|
||||||
|
$gesehenRows = DatabaseShow(
|
||||||
|
$this->seenTable,
|
||||||
|
"NACHRICHTEN_ID",
|
||||||
|
"BENUTZER_ID = ?",
|
||||||
|
$userId
|
||||||
|
);
|
||||||
|
|
||||||
|
$gesehenIds = array_column($gesehenRows, 'NACHRICHTEN_ID');
|
||||||
|
|
||||||
|
return array_values(array_filter(
|
||||||
|
$alleNachrichten,
|
||||||
|
fn($nachricht) => !in_array($nachricht['ID'], $gesehenIds)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Legt eine neue Nachricht im Pool an (z.B. für eine Admin-Funktion).
|
||||||
|
*/
|
||||||
|
public function createMessage(string $titel, string $nachricht): bool
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
DatabaseInsert(
|
||||||
|
$this->table,
|
||||||
|
"TITEL, NACHRICHT",
|
||||||
|
$titel,
|
||||||
|
$nachricht
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
throw new Exception($e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Markiert eine bestimmte Nachricht als vom aktuellen Nutzer gesehen/bestätigt.
|
||||||
|
*/
|
||||||
|
public function markAsSeen(int $nachrichtId): bool
|
||||||
|
{
|
||||||
|
$userId = $this->getCurrentUserId();
|
||||||
|
if ($userId === null) {
|
||||||
|
throw new Exception('Kein eingeloggter Benutzer gefunden.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prüfen, ob schon als gesehen markiert, um Duplikate zu vermeiden
|
||||||
|
$bereitsGesehen = DatabaseShow(
|
||||||
|
$this->seenTable,
|
||||||
|
"ID",
|
||||||
|
"BENUTZER_ID = ? AND NACHRICHTEN_ID = ?",
|
||||||
|
$userId,
|
||||||
|
$nachrichtId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!empty($bereitsGesehen)) {
|
||||||
|
return true; // war schon markiert, kein Fehler
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
DatabaseInsert(
|
||||||
|
$this->seenTable,
|
||||||
|
"BENUTZER_ID, NACHRICHTEN_ID",
|
||||||
|
$userId,
|
||||||
|
$nachrichtId
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
throw new Exception($e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateMessage(int $id, string $titel, string $nachricht): bool
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
DatabaseUpdate(
|
||||||
|
$this->table,
|
||||||
|
"TITEL = ?, NACHRICHT = ?",
|
||||||
|
"ID = ?",
|
||||||
|
$titel,
|
||||||
|
$nachricht,
|
||||||
|
$id
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
throw new Exception($e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Löscht eine Nachricht komplett, inkl. aller "Gesehen"-Einträge dazu.
|
||||||
|
*/
|
||||||
|
public function deleteMessage(int $id): bool
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
DatabaseDelete($this->seenTable, "NACHRICHTEN_ID = ?", $id);
|
||||||
|
DatabaseDelete($this->table, "ID = ?", $id);
|
||||||
|
return true;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
throw new Exception($e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Anzahl Nutzer, die eine bestimmte Nachricht bereits bestätigt haben.
|
||||||
|
*/
|
||||||
|
public function countSeenBy(int $id): int
|
||||||
|
{
|
||||||
|
$rows = DatabaseShow(
|
||||||
|
$this->seenTable,
|
||||||
|
"ID",
|
||||||
|
"NACHRICHTEN_ID = ?",
|
||||||
|
$id
|
||||||
|
);
|
||||||
|
return count($rows);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
const BENACHRICHTIGUNGEN_HANDLER_URL = '/api/js/handler/BenachrichtigungenAjaxHandler';
|
||||||
|
|
||||||
|
async function benachrichtigungenAjaxRequest(payload) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(BENACHRICHTIGUNGEN_HANDLER_URL, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
});
|
||||||
|
const data = await parseJsonResponse(response);
|
||||||
|
|
||||||
|
if (data.error) {
|
||||||
|
throw new Error(data.error);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Benachrichtigungen-Ajax-Fehler:', error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNotificationEntries() {
|
||||||
|
return benachrichtigungenAjaxRequest({ action: 'get_entries' });
|
||||||
|
}
|
||||||
|
|
||||||
|
function addNotificationEntry(titel, nachricht) {
|
||||||
|
return benachrichtigungenAjaxRequest({ action: 'add_entry', titel, nachricht });
|
||||||
|
}
|
||||||
|
|
||||||
|
function editNotificationEntry(id, titel, nachricht) {
|
||||||
|
return benachrichtigungenAjaxRequest({ action: 'edit_entry', id, titel, nachricht });
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteNotificationEntry(id) {
|
||||||
|
return benachrichtigungenAjaxRequest({ action: 'delete_entry', id });
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
const DATEIEN_HANDLER_URL = '/api/js/handler/DateienAjaxHandler';
|
||||||
|
|
||||||
|
async function dateienAjaxRequest(payload) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(DATEIEN_HANDLER_URL, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
});
|
||||||
|
const data = await parseJsonResponse(response);
|
||||||
|
|
||||||
|
if (data.error) {
|
||||||
|
throw new Error(data.error);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Dateien-Ajax-Fehler:', error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDateienEntries() {
|
||||||
|
return dateienAjaxRequest({ action: 'get_entries' });
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteDateiEntry(id) {
|
||||||
|
return dateienAjaxRequest({ action: 'delete_entry', id });
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
const NOTIFICATION_HANDLER_URL = '/api/js/handler/NotificationAjaxHandler';
|
||||||
|
|
||||||
|
async function notificationAjaxRequest(payload) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(NOTIFICATION_HANDLER_URL, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
});
|
||||||
|
const data = await parseJsonResponse(response);
|
||||||
|
|
||||||
|
if (data.error) {
|
||||||
|
throw new Error(data.error);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Notification-Ajax-Fehler:', error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function markNotificationAsSeen(nachrichtId) {
|
||||||
|
return notificationAjaxRequest({ action: 'mark_seen', nachricht_id: nachrichtId });
|
||||||
|
}
|
||||||
+22
-22
@@ -150,18 +150,18 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
<h2>Login</h2>
|
<h2>Login</h2>
|
||||||
|
|
||||||
<?php if ($error): ?>
|
<?php if ($error): ?>
|
||||||
<p class="login-error"><?= htmlspecialchars($error) ?></p>
|
<p class="login-error"><?= htmlspecialchars($error) ?></p>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<form method="post">
|
<form method="post">
|
||||||
<input type="text" name="username" placeholder="Benutzername" required>
|
<input type="text" name="username" placeholder="Benutzername" required>
|
||||||
<input type="password" name="password" placeholder="Passwort" required>
|
<input type="password" name="password" placeholder="Passwort" required>
|
||||||
<button type="submit">Login</button>
|
<button type="submit">Einloggen</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<button style="margin-top: 9px" type="button"
|
<button style="margin-top: 9px" type="button"
|
||||||
onclick="document.querySelector('input[name=username]').value='gast'; document.querySelector('form').submit();">
|
onclick="document.querySelector('input[name=username]').value='gast'; document.querySelector('form').submit();">
|
||||||
Als Gast einloggen
|
Gast Login
|
||||||
</button>
|
</button>
|
||||||
<button style="margin-top: 9px" type="button" id="registryButton">
|
<button style="margin-top: 9px" type="button" id="registryButton">
|
||||||
Registrieren
|
Registrieren
|
||||||
@@ -174,27 +174,27 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.getElementById('registryButton').addEventListener('click', () => {
|
document.getElementById('registryButton').addEventListener('click', () => {
|
||||||
window.location.href = 'register.php';
|
window.location.href = 'register.php';
|
||||||
});
|
});
|
||||||
document.getElementById('resetpassword').addEventListener('click', () => {
|
document.getElementById('resetpassword').addEventListener('click', () => {
|
||||||
window.location.href = 'forgot_password.php';
|
window.location.href = 'forgot_password.php';
|
||||||
});
|
});
|
||||||
const toggle = document.getElementById('darkModeToggle');
|
const toggle = document.getElementById('darkModeToggle');
|
||||||
|
|
||||||
if (localStorage.getItem('darkMode') === 'enabled') {
|
if (localStorage.getItem('darkMode') === 'enabled') {
|
||||||
document.body.classList.add('dark-mode');
|
document.body.classList.add('dark-mode');
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle.addEventListener('click', () => {
|
||||||
|
document.body.classList.toggle('dark-mode');
|
||||||
|
|
||||||
|
if (document.body.classList.contains('dark-mode')) {
|
||||||
|
localStorage.setItem('darkMode', 'enabled');
|
||||||
|
} else {
|
||||||
|
localStorage.setItem('darkMode', 'disabled');
|
||||||
}
|
}
|
||||||
|
});
|
||||||
toggle.addEventListener('click', () => {
|
|
||||||
document.body.classList.toggle('dark-mode');
|
|
||||||
|
|
||||||
if (document.body.classList.contains('dark-mode')) {
|
|
||||||
localStorage.setItem('darkMode', 'enabled');
|
|
||||||
} else {
|
|
||||||
localStorage.setItem('darkMode', 'disabled');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
<?php
|
||||||
|
include_once(__DIR__ . '/../controller/NotifyController.php');
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="arbeitsberichte-wrapper">
|
||||||
|
<div class="vergabe-form">
|
||||||
|
<h3>Neue Nachricht anlegen</h3>
|
||||||
|
<form id="addNotificationForm" class="vergabe-grid">
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Titel</label>
|
||||||
|
<input type="text" name="titel" id="in-titel" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Nachricht</label>
|
||||||
|
<input type="text" name="nachricht" id="in-nachricht" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn-submit-vergabe">💾 Speichern</button>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-container" style="margin-top: 30px;">
|
||||||
|
<h3>Nachrichten Verwaltung</h3>
|
||||||
|
<div id="tableWrapper">
|
||||||
|
<p>Lade Daten...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/js/AjaxMethods.js"></script>
|
||||||
|
<script src="/js/ajaxApi/BenachrichtigungenAjaxMethods.js"></script>
|
||||||
|
<script src="/js/AjaxRenderTables.js"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const tableWrapper = document.getElementById('tableWrapper');
|
||||||
|
|
||||||
|
function renderNotificationsTabelle(entries) {
|
||||||
|
if (!entries || entries.length === 0) {
|
||||||
|
tableWrapper.innerHTML = '<p>Keine Nachrichten vorhanden.</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let html = `
|
||||||
|
<table class="db-table mobile-responsive-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Titel</th><th>Nachricht</th><th>Gesehen von</th>
|
||||||
|
<th style="width: 220px;">Aktion</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
`;
|
||||||
|
for (const e of entries) {
|
||||||
|
html += `
|
||||||
|
<tr data-id="${escapeHtml(e.ID)}">
|
||||||
|
<td data-label="Titel">
|
||||||
|
<input type="text" class="edit-titel" value="${escapeHtml(e.TITEL)}">
|
||||||
|
</td>
|
||||||
|
<td data-label="Nachricht">
|
||||||
|
<input type="text" class="edit-nachricht" value="${escapeHtml(e.NACHRICHT)}">
|
||||||
|
</td>
|
||||||
|
<td data-label="Gesehen von">${escapeHtml(e.GESEHEN_ANZAHL)} Nutzer</td>
|
||||||
|
<td data-label="Aktion">
|
||||||
|
<div style="display:flex; flex-direction:column; gap:6px; align-items:stretch;">
|
||||||
|
<button type="button" class="btn-edit" data-id="${escapeHtml(e.ID)}">Speichern</button>
|
||||||
|
<button type="button" class="btn-delete" data-id="${escapeHtml(e.ID)}">Löschen</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>`;
|
||||||
|
}
|
||||||
|
html += `</tbody></table>`;
|
||||||
|
tableWrapper.innerHTML = html;
|
||||||
|
|
||||||
|
tableWrapper.querySelectorAll('.btn-edit').forEach(btn => {
|
||||||
|
btn.addEventListener('click', async () => {
|
||||||
|
const row = tableWrapper.querySelector(`tr[data-id="${btn.dataset.id}"]`);
|
||||||
|
const titel = row.querySelector('.edit-titel').value.trim();
|
||||||
|
const nachricht = row.querySelector('.edit-nachricht').value.trim();
|
||||||
|
|
||||||
|
if (!titel || !nachricht) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await editNotificationEntry(btn.dataset.id, titel, nachricht);
|
||||||
|
await ladeListe();
|
||||||
|
} catch (err) {
|
||||||
|
alert('Fehler beim Speichern: ' + err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
tableWrapper.querySelectorAll('.btn-delete').forEach(btn => {
|
||||||
|
btn.addEventListener('click', async () => {
|
||||||
|
if (!confirm('Möchtest du diese Nachricht wirklich löschen? Dies entfernt auch alle "Gesehen"-Markierungen dazu.')) return;
|
||||||
|
try {
|
||||||
|
await deleteNotificationEntry(btn.dataset.id);
|
||||||
|
await ladeListe();
|
||||||
|
} catch (err) {
|
||||||
|
alert('Fehler beim Löschen: ' + err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ladeListe() {
|
||||||
|
try {
|
||||||
|
const data = await getNotificationEntries();
|
||||||
|
renderNotificationsTabelle(data.entries);
|
||||||
|
} catch (err) {
|
||||||
|
tableWrapper.innerHTML = `<p>❌ ${escapeHtml(err.message)}</p>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('addNotificationForm').addEventListener('submit', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const titel = document.getElementById('in-titel').value.trim();
|
||||||
|
const nachricht = document.getElementById('in-nachricht').value.trim();
|
||||||
|
|
||||||
|
if (!titel || !nachricht) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await addNotificationEntry(titel, nachricht);
|
||||||
|
e.target.reset();
|
||||||
|
await ladeListe();
|
||||||
|
} catch (err) {
|
||||||
|
alert('Fehler beim Speichern: ' + err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ladeListe();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
<div class="arbeitsberichte-wrapper">
|
||||||
|
<div class="vergabe-form">
|
||||||
|
<h3>Datei hochladen</h3>
|
||||||
|
<form id="uploadForm" enctype="multipart/form-data">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Datei auswählen</label>
|
||||||
|
<input type="file" name="datei" id="in-datei" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn-submit-vergabe">⬆️ Hochladen</button>
|
||||||
|
</form>
|
||||||
|
<p id="uploadStatus" style="margin-top: 10px;"></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-container" style="margin-top: 30px;">
|
||||||
|
<h3>Meine Dateien</h3>
|
||||||
|
<div id="tableWrapper">
|
||||||
|
<p>Lade Daten...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/js/AjaxMethods.js"></script>
|
||||||
|
<script src="/js/ajaxApi/DateienAjaxMethods.js"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const tableWrapper = document.getElementById('tableWrapper');
|
||||||
|
const uploadStatus = document.getElementById('uploadStatus');
|
||||||
|
|
||||||
|
function formatBytes(bytes) {
|
||||||
|
if (bytes < 1024) return bytes + ' B';
|
||||||
|
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
|
||||||
|
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderDateienTabelle(entries) {
|
||||||
|
if (!entries || entries.length === 0) {
|
||||||
|
tableWrapper.innerHTML = '<p>Keine Dateien vorhanden.</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let html = `
|
||||||
|
<table class="db-table mobile-responsive-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Dateiname</th><th>Größe</th><th>Hochgeladen am</th>
|
||||||
|
<th style="width: 180px;">Aktion</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
`;
|
||||||
|
for (const e of entries) {
|
||||||
|
html += `
|
||||||
|
<tr data-id="${escapeHtml(e.ID)}">
|
||||||
|
<td data-label="Dateiname">${escapeHtml(e.ORIGINALNAME)}</td>
|
||||||
|
<td data-label="Größe">${formatBytes(e.GROESSE)}</td>
|
||||||
|
<td data-label="Hochgeladen am">${escapeHtml(e.HOCHGELADEN_AM)}</td>
|
||||||
|
<td data-label="Aktion">
|
||||||
|
<div style="display:flex; gap:6px;">
|
||||||
|
<a href="/api/js/handler/DateienDownloadHandler.php?id=${escapeHtml(e.ID)}" class="btn-submit-vergabe" style="text-decoration:none; padding:6px 12px;">⬇️ Download</a>
|
||||||
|
<button type="button" class="btn-delete" data-id="${escapeHtml(e.ID)}">🗑️</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>`;
|
||||||
|
}
|
||||||
|
html += `</tbody></table>`;
|
||||||
|
tableWrapper.innerHTML = html;
|
||||||
|
|
||||||
|
tableWrapper.querySelectorAll('.btn-delete').forEach(btn => {
|
||||||
|
btn.addEventListener('click', async () => {
|
||||||
|
if (!confirm('Diese Datei wirklich löschen?')) return;
|
||||||
|
try {
|
||||||
|
await deleteDateiEntry(btn.dataset.id);
|
||||||
|
await ladeListe();
|
||||||
|
} catch (err) {
|
||||||
|
alert('Fehler beim Löschen: ' + err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ladeListe() {
|
||||||
|
try {
|
||||||
|
const data = await getDateienEntries();
|
||||||
|
renderDateienTabelle(data.entries);
|
||||||
|
} catch (err) {
|
||||||
|
tableWrapper.innerHTML = `<p>❌ ${escapeHtml(err.message)}</p>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('uploadForm').addEventListener('submit', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const fileInput = document.getElementById('in-datei');
|
||||||
|
if (!fileInput.files.length) return;
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('datei', fileInput.files[0]);
|
||||||
|
|
||||||
|
uploadStatus.textContent = 'Lade hoch...';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/js/handler/DateienUploadHandler.php', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'X-Requested-With': 'XMLHttpRequest'
|
||||||
|
},
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
const data = await parseJsonResponse(response);
|
||||||
|
|
||||||
|
if (data.error) throw new Error(data.error);
|
||||||
|
|
||||||
|
uploadStatus.textContent = '✅ Erfolgreich hochgeladen';
|
||||||
|
fileInput.value = '';
|
||||||
|
await ladeListe();
|
||||||
|
} catch (err) {
|
||||||
|
uploadStatus.textContent = '❌ ' + err.message;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ladeListe();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -11,6 +11,14 @@
|
|||||||
<input type="text" name="menge" id="menge" required>
|
<input type="text" name="menge" id="menge" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Typ</label>
|
||||||
|
<select name="typ" id="typ">
|
||||||
|
<option value="einmalig">Einmalig</option>
|
||||||
|
<option value="vorlage">Vorlage (wiederkehrend)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="btn-submit-vergabe">
|
<button type="submit" class="btn-submit-vergabe">
|
||||||
Speichern
|
Speichern
|
||||||
</button>
|
</button>
|
||||||
@@ -26,7 +34,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/js/AjaxMethods.js"></script>
|
<script src="/js/AjaxMethods.js"></script>
|
||||||
<script src="/js/AjaxRenderTables.js"></script>
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const tableWrapper = document.getElementById('tableWrapper');
|
const tableWrapper = document.getElementById('tableWrapper');
|
||||||
@@ -40,21 +47,109 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
key: 'MENGE',
|
key: 'MENGE',
|
||||||
label: 'Menge'
|
label: 'Menge'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'TYP',
|
||||||
|
label: 'Typ'
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Eigene Render-Funktion, da hier je nach TYP unterschiedlich
|
||||||
|
// gehandelt werden muss (Löschen vs. Erledigt-Markieren).
|
||||||
|
function renderEinkaufsliste(entries) {
|
||||||
|
if (entries && !Array.isArray(entries)) {
|
||||||
|
entries = Object.values(entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!entries || entries.length === 0) {
|
||||||
|
tableWrapper.innerHTML = '<p>Keine Einträge vorhanden.</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let html = `
|
||||||
|
<table class="db-table mobile-responsive-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
${spaltenKonfig.map(s => `<th>${escapeHtml(s.label)}</th>`).join('')}
|
||||||
|
<th style="width: 100px;">Aktion</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
`;
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
const istVorlage = entry.TYP === 'vorlage';
|
||||||
|
const label = istVorlage ? 'Erledigt' : 'Löschen';
|
||||||
|
|
||||||
|
html += `<tr>`;
|
||||||
|
for (const s of spaltenKonfig) {
|
||||||
|
let wert = entry[s.key];
|
||||||
|
if (s.key === 'TYP') {
|
||||||
|
wert = istVorlage ? 'Vorlage' : 'Einmalig';
|
||||||
|
}
|
||||||
|
html += `<td data-label="${escapeHtml(s.label)}">${escapeHtml(wert)}</td>`;
|
||||||
|
}
|
||||||
|
html += `
|
||||||
|
<td data-label="Aktion">
|
||||||
|
<button type="button" class="btn-delete" data-id="${escapeHtml(entry.ID)}" data-typ="${escapeHtml(entry.TYP)}">
|
||||||
|
${label}
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
html += `</tbody></table>`;
|
||||||
|
tableWrapper.innerHTML = html;
|
||||||
|
|
||||||
|
tableWrapper.querySelectorAll('.btn-delete').forEach(btn => {
|
||||||
|
btn.addEventListener('click', async () => {
|
||||||
|
const id = btn.dataset.id;
|
||||||
|
const istVorlage = btn.dataset.typ === 'vorlage';
|
||||||
|
|
||||||
|
const frage = istVorlage ?
|
||||||
|
'Diesen Vorlagen-Artikel als erledigt markieren?' :
|
||||||
|
'Diesen Artikel wirklich endgültig löschen?';
|
||||||
|
if (!confirm(frage)) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (istVorlage) {
|
||||||
|
await DatabaseUpdate(
|
||||||
|
table, {
|
||||||
|
STATUS: 'erledigt'
|
||||||
|
},
|
||||||
|
[{
|
||||||
|
column: 'ID',
|
||||||
|
operator: '=',
|
||||||
|
value: id
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await DatabaseDelete(
|
||||||
|
table,
|
||||||
|
[{
|
||||||
|
column: 'ID',
|
||||||
|
operator: '=',
|
||||||
|
value: id
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await reloadTable();
|
||||||
|
} catch (error) {
|
||||||
|
alert('Fehler: ' + error.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function reloadTable() {
|
async function reloadTable() {
|
||||||
try {
|
try {
|
||||||
const data = await DatabaseShow(table);
|
const data = await DatabaseShow(table, [], [{
|
||||||
renderTableWithDelete(tableWrapper, data.entries, spaltenKonfig, 'ID', async (id) => {
|
column: 'STATUS',
|
||||||
await DatabaseDelete(table, [{
|
operator: '=',
|
||||||
column: 'ID',
|
value: 'aktiv'
|
||||||
operator: '=',
|
}]);
|
||||||
value: id
|
renderEinkaufsliste(data.entries);
|
||||||
}]);
|
|
||||||
await reloadTable();
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
tableWrapper.innerHTML = `<p> ${escapeHtml(error.message)}</p>`;
|
tableWrapper.innerHTML = `<p>${escapeHtml(error.message)}</p>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,10 +157,15 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const artikel = document.getElementById('artikel').value.trim();
|
const artikel = document.getElementById('artikel').value.trim();
|
||||||
const menge = document.getElementById('menge').value.trim();
|
const menge = document.getElementById('menge').value.trim();
|
||||||
|
const typ = document.getElementById('typ').value;
|
||||||
if (!artikel || !menge) return;
|
if (!artikel || !menge) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await DatabaseInsert(table, ['PRODUKT', 'MENGE'], [artikel, menge]);
|
await DatabaseInsert(
|
||||||
|
table,
|
||||||
|
['PRODUKT', 'MENGE', 'TYP', 'STATUS'],
|
||||||
|
[artikel, menge, typ, 'aktiv']
|
||||||
|
);
|
||||||
e.target.reset();
|
e.target.reset();
|
||||||
await reloadTable();
|
await reloadTable();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
include_once(__DIR__ . '/../controller/NotifyController.php');
|
||||||
|
|
||||||
$username = $_SESSION['username'] ?? 'Gast';
|
$username = $_SESSION['username'] ?? 'Gast';
|
||||||
$is_superuser = $_SESSION['is_superuser'] ?? false;
|
$is_superuser = $_SESSION['is_superuser'] ?? false;
|
||||||
$is_admin = $_SESSION['is_admin'] ?? false;
|
$is_admin = $_SESSION['is_admin'] ?? false;
|
||||||
@@ -10,8 +12,24 @@ $db_res = DatabaseShow(
|
|||||||
$username
|
$username
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// --- Ungesehene Benachrichtigung ermitteln ---
|
||||||
|
$notificationCtl = new Notification();
|
||||||
|
$offeneNachrichten = $notificationCtl->getUnseenMessages();
|
||||||
|
$aktuelleNachricht = $offeneNachrichten[0] ?? null; // immer nur die erste anzeigen
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|
||||||
|
<?php if ($aktuelleNachricht): ?>
|
||||||
|
<div class="notification-overlay open" id="notificationModal" data-nachricht-id="<?= htmlspecialchars($aktuelleNachricht['ID']) ?>">
|
||||||
|
<div class="notification-box">
|
||||||
|
<div class="notification-icon">📢</div>
|
||||||
|
<h3><?= htmlspecialchars($aktuelleNachricht['TITEL']) ?></h3>
|
||||||
|
<p><?= nl2br(htmlspecialchars($aktuelleNachricht['NACHRICHT'])) ?></p>
|
||||||
|
<button type="button" id="notificationConfirm">Verstanden</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<div class="home-dashboard">
|
<div class="home-dashboard">
|
||||||
<div class="links-container">
|
<div class="links-container">
|
||||||
|
|
||||||
@@ -20,18 +38,18 @@ $db_res = DatabaseShow(
|
|||||||
<div class="group-links" style="padding: 10px; color: inherit;">
|
<div class="group-links" style="padding: 10px; color: inherit;">
|
||||||
<p style="margin: 5px 0;"><strong>Name:</strong> <code><?= htmlspecialchars($username) ?></code></p>
|
<p style="margin: 5px 0;"><strong>Name:</strong> <code><?= htmlspecialchars($username) ?></code></p>
|
||||||
<?php if ($username != 'gast'): ?>
|
<?php if ($username != 'gast'): ?>
|
||||||
<p style="margin: 5px 0;"><strong>Nachname:</strong>
|
<p style="margin: 5px 0;"><strong>Nachname:</strong>
|
||||||
<code><?= htmlspecialchars($db_res[0]['NACHNAME']) ?></code>
|
<code><?= htmlspecialchars($db_res[0]['NACHNAME']) ?></code>
|
||||||
</p>
|
</p>
|
||||||
<p style="margin: 5px 0;"><strong>E-Mail:</strong>
|
<p style="margin: 5px 0;"><strong>E-Mail:</strong>
|
||||||
<code><?= htmlspecialchars($db_res[0]['EMAIL']) ?></code>
|
<code><?= htmlspecialchars($db_res[0]['EMAIL']) ?></code>
|
||||||
</p>
|
</p>
|
||||||
<p style="margin: 5px 0;"><strong>Benutzerrolle:</strong>
|
<p style="margin: 5px 0;"><strong>Benutzerrolle:</strong>
|
||||||
<code><?= htmlspecialchars($db_res[0]['ROLLE']) ?></code>
|
<code><?= htmlspecialchars($db_res[0]['ROLLE']) ?></code>
|
||||||
</p>
|
</p>
|
||||||
<p style="margin: 5px 0;"><strong>Home IP:</strong>
|
<p style="margin: 5px 0;"><strong>Home IP:</strong>
|
||||||
<code><?= htmlspecialchars($db_res[0]['HOME_IP']) ?></code>
|
<code><?= htmlspecialchars($db_res[0]['HOME_IP']) ?></code>
|
||||||
</p>
|
</p>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -41,18 +59,18 @@ $db_res = DatabaseShow(
|
|||||||
<div class="group-links"
|
<div class="group-links"
|
||||||
style="padding: 10px; display: flex; flex-direction: row; gap: 10px; flex-wrap: wrap;">
|
style="padding: 10px; display: flex; flex-direction: row; gap: 10px; flex-wrap: wrap;">
|
||||||
<?php if ((new getSpecificData())->getIsAdmin()): ?>
|
<?php if ((new getSpecificData())->getIsAdmin()): ?>
|
||||||
<span
|
<span
|
||||||
style="background: #dc3545; color: white; padding: 4px 10px; border-radius: 15px; font-size: 0.8rem; font-weight: bold;">Admin</span>
|
style="background: #dc3545; color: white; padding: 4px 10px; border-radius: 15px; font-size: 0.8rem; font-weight: bold;">Admin</span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if ((new getSpecificData())->getIsSuperuser()): ?>
|
<?php if ((new getSpecificData())->getIsSuperuser()): ?>
|
||||||
<span
|
<span
|
||||||
style="background: #ffc107; color: #000; padding: 4px 10px; border-radius: 15px; font-size: 0.8rem; font-weight: bold;">Superuser</span>
|
style="background: #ffc107; color: #000; padding: 4px 10px; border-radius: 15px; font-size: 0.8rem; font-weight: bold;">Superuser</span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if (!(new getSpecificData())->getIsSuperuser() && !(new getSpecificData())->getIsAdmin()): ?>
|
<?php if (!(new getSpecificData())->getIsSuperuser() && !(new getSpecificData())->getIsAdmin()): ?>
|
||||||
<span
|
<span
|
||||||
style="background: #6c757d; color: white; padding: 4px 10px; border-radius: 15px; font-size: 0.8rem; font-weight: bold;">Standard-Nutzer</span>
|
style="background: #6c757d; color: white; padding: 4px 10px; border-radius: 15px; font-size: 0.8rem; font-weight: bold;">Standard-Nutzer</span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -67,21 +85,21 @@ $db_res = DatabaseShow(
|
|||||||
<code>Aktiv</code>
|
<code>Aktiv</code>
|
||||||
</p>
|
</p>
|
||||||
<?php if ($is_admin): ?>
|
<?php if ($is_admin): ?>
|
||||||
<p style="margin: 5px 0;"><strong>Client IP:</strong>
|
<p style="margin: 5px 0;"><strong>Client IP:</strong>
|
||||||
<code><?= htmlspecialchars((new getSpecificData())->getClientIP()) ?></code>
|
<code><?= htmlspecialchars((new getSpecificData())->getClientIP()) ?></code>
|
||||||
</p>
|
</p>
|
||||||
<p style="margin: 5px 0;"><strong>Server IP:</strong>
|
<p style="margin: 5px 0;"><strong>Server IP:</strong>
|
||||||
<code><?= htmlspecialchars((new getSpecificData())->getServerIP()) ?></code>
|
<code><?= htmlspecialchars((new getSpecificData())->getServerIP()) ?></code>
|
||||||
</p>
|
</p>
|
||||||
<p style="margin: 5px 0;"><strong>Geo IP:</strong>
|
<p style="margin: 5px 0;"><strong>Geo IP:</strong>
|
||||||
<code><?= htmlspecialchars((new getSpecificData())->getGeoIP((new getSpecificData())->getClientIP())) ?></code>
|
<code><?= htmlspecialchars((new getSpecificData())->getGeoIP((new getSpecificData())->getClientIP())) ?></code>
|
||||||
</p>
|
</p>
|
||||||
<p style="margin: 5px 0;"><strong>Database IP:</strong>
|
<p style="margin: 5px 0;"><strong>Database IP:</strong>
|
||||||
<code><?= htmlspecialchars((new getSpecificData())->getDatabase()) ?></code>
|
<code><?= htmlspecialchars((new getSpecificData())->getDatabase()) ?></code>
|
||||||
</p>
|
</p>
|
||||||
<p style="margin: 5px 0;"><strong>Wetter:</strong>
|
<p style="margin: 5px 0;"><strong>Wetter:</strong>
|
||||||
<code><?= htmlspecialchars((new getSpecificData())->getWeather()['aktuell']) ?></code>
|
<code><?= htmlspecialchars((new getSpecificData())->getWeather()['aktuell']) ?></code>
|
||||||
</p>
|
</p>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -89,32 +107,55 @@ $db_res = DatabaseShow(
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script src="/js/ajaxApi/NotificationAjaxMethods.js"></script>
|
||||||
|
<script src="/js/AjaxMethods.js"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const modal = document.getElementById('notificationModal');
|
||||||
|
const confirmBtn = document.getElementById('notificationConfirm');
|
||||||
|
|
||||||
|
if (!modal || !confirmBtn) return; // keine offene Nachricht vorhanden
|
||||||
|
|
||||||
|
confirmBtn.addEventListener('click', async () => {
|
||||||
|
const nachrichtId = modal.dataset.nachrichtId;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await markNotificationAsSeen(nachrichtId);
|
||||||
|
modal.classList.remove('open');
|
||||||
|
setTimeout(() => modal.remove(), 200); // nach dem Fade-out entfernen
|
||||||
|
} catch (err) {
|
||||||
|
alert('Fehler beim Bestätigen: ' + err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* Zusätzliche Inline-Styles für die Feinabstimmung in der home_info */
|
/* Zusätzliche Inline-Styles für die Feinabstimmung in der home_info */
|
||||||
.home-dashboard {
|
.home-dashboard {
|
||||||
animation: fadeIn 0.5s ease-in-out;
|
animation: fadeIn 0.5s ease-in-out;
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
background: rgba(0, 0, 0, 0.1);
|
|
||||||
padding: 2px 5px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.dark-mode code {
|
|
||||||
background: rgba(255, 255, 255, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadeIn {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(10px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
to {
|
code {
|
||||||
opacity: 1;
|
background: rgba(0, 0, 0, 0.1);
|
||||||
transform: translateY(0);
|
padding: 2px 5px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode code {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
@@ -1460,4 +1460,88 @@ body.dark-mode .autocomplete-suggestions div.active {
|
|||||||
.register-form input {
|
.register-form input {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
/* gap übernimmt jetzt den Abstand, kein doppeltes Spacing mehr */
|
/* gap übernimmt jetzt den Abstand, kein doppeltes Spacing mehr */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== Benachrichtigungs-Modal ===== */
|
||||||
|
.notification-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 2000;
|
||||||
|
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-overlay.open {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-box {
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
border-top: 4px solid #007bff;
|
||||||
|
width: 420px;
|
||||||
|
max-width: 90%;
|
||||||
|
padding: 28px 32px;
|
||||||
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
transform: translateY(-10px);
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-overlay.open .notification-box {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .notification-box {
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-box .notification-icon {
|
||||||
|
font-size: 2.2rem;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-box h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
font-size: 1.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .notification-box h3 {
|
||||||
|
color: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-box p {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: #555;
|
||||||
|
white-space: pre-line;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .notification-box p {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-box button {
|
||||||
|
padding: 10px 28px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-box button:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user