diff --git a/DeskHub-Webseite/staff/_footer.php b/DeskHub-Webseite/staff/_footer.php index 1586cc3d..f41501c6 100644 --- a/DeskHub-Webseite/staff/_footer.php +++ b/DeskHub-Webseite/staff/_footer.php @@ -2,5 +2,6 @@ + diff --git a/DeskHub-Webseite/staff/_header.php b/DeskHub-Webseite/staff/_header.php index 598ed8c0..4b3ed99e 100644 --- a/DeskHub-Webseite/staff/_header.php +++ b/DeskHub-Webseite/staff/_header.php @@ -56,6 +56,7 @@ if ('serviceWorker' in navigator) { Übersicht Bericht-Baukasten Tickets + Meine Woche Aufgaben Sperrliste Apps diff --git a/DeskHub-Webseite/staff/my_week.php b/DeskHub-Webseite/staff/my_week.php new file mode 100644 index 00000000..ebb6e290 --- /dev/null +++ b/DeskHub-Webseite/staff/my_week.php @@ -0,0 +1,97 @@ +prepare("SELECT ticket_number, subject, created_at, resolution_due_at, status FROM tickets WHERE assigned_staff_id = ? AND is_spam = 0 AND status NOT IN ('resolved','closed') ORDER BY created_at ASC LIMIT 5"); +$oldest->execute([$staffId]); +$oldest = $oldest->fetchAll(); + +$pageTitle = 'Meine Woche'; +$activePage = 'my_week'; +require __DIR__ . '/_header.php'; +?> +
+

Meine Woche

+
+ $l): ?> + + +
+
+

Nur für dich sichtbar: ( bis ), verglichen mit dem gleich langen Zeitraum davor.

+ +
+ +
+
+
+
+
+
+ +
+ +
+
+

Gelöst pro Tag

+
+ $n): ?> +
+
+
+
10 ? date('d.', strtotime($day)) : ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'][(int) date('w', strtotime($day))]) ?>
+
+ +
+
+ +
+

Gerade bei dir

+
+
offen
+
warten auf deine Antwort
+
überfällig
+
+ +
Deine ältesten offenen Tickets
+ +
+ # · + seit überfällig' : '' ?> +
+ + +

Keine offenen Tickets. 🎉

+ +
+
+

Die Zufriedenheitsabfrage ist ausgeschaltet; deshalb gibt es hier keine Bewertungen. Ein Administrator schaltet sie unter Einstellungen → Allgemein ein.

+ + diff --git a/DeskHub-Webseite/staff/report_builder.php b/DeskHub-Webseite/staff/report_builder.php index 55e7029d..31c9834a 100644 --- a/DeskHub-Webseite/staff/report_builder.php +++ b/DeskHub-Webseite/staff/report_builder.php @@ -35,16 +35,54 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $row->execute([(int) $_POST['id']]); if (($r = $row->fetch()) && ((int) $r['staff_id'] === (int) $staff['id'] || $staff['role'] === 'admin')) { $pdo->prepare('DELETE FROM saved_reports WHERE id = ?')->execute([$r['id']]); + $pdo->prepare('DELETE FROM report_schedules WHERE report_id = ?')->execute([$r['id']]); flash('success', 'Bericht gelöscht.'); } redirect('/staff/report_builder.php'); } + if (in_array($action, ['schedule_add', 'schedule_delete', 'schedule_toggle', 'schedule_test', 'schedule_flag'], true)) { + $sid = (int) ($_POST['schedule_id'] ?? 0); + $own = $pdo->prepare('SELECT * FROM report_schedules WHERE id = ?'); + $own->execute([$sid]); + $sch = $own->fetch(); + $mine = $sch && ((int) $sch['staff_id'] === (int) $staff['id'] || $staff['role'] === 'admin'); + if ($action === 'schedule_add') { + $err = report_schedule_create($pdo, $staff, $_POST); + $err ? flash('error', $err) : flash('success', 'Zeitplan angelegt. Der Bericht wird zum nächsten Termin per E-Mail gesendet.'); + } elseif (!$mine) { + flash('error', 'Zeitplan nicht gefunden.'); + } elseif ($action === 'schedule_delete') { + $pdo->prepare('DELETE FROM report_schedules WHERE id = ?')->execute([$sid]); + flash('success', 'Zeitplan gelöscht.'); + } elseif ($action === 'schedule_flag') { + $flag = (string) ($_POST['flag'] ?? ''); + if (!in_array($flag, ['only_if_data', 'skip_unchanged', 'weekdays_only'], true)) { flash('error', 'Unbekannte Option.'); } + else { + $new = (int) $sch[$flag] === 1 ? 0 : 1; + $upd = $sch; $upd[$flag] = $new; + $pdo->prepare("UPDATE report_schedules SET {$flag} = ?, next_run_at = ? WHERE id = ?")->execute([$new, (int) $sch['enabled'] === 1 ? report_schedule_next($upd) : $sch['next_run_at'], $sid]); + flash('success', 'Option gespeichert.'); + } + } elseif ($action === 'schedule_toggle') { + $on = (int) $sch['enabled'] === 1 ? 0 : 1; + $pdo->prepare('UPDATE report_schedules SET enabled = ?, next_run_at = ? WHERE id = ?')->execute([$on, $on ? report_schedule_next($sch) : $sch['next_run_at'], $sid]); + flash('success', $on ? 'Zeitplan aktiviert.' : 'Zeitplan pausiert.'); + } else { + $r = report_schedule_send($pdo, $sch); + $r['error'] ? flash('error', $r['error']) : flash('success', 'Testversand an ' . $r['sent'] . ' Empfänger gesendet.'); + } + redirect('/staff/report_builder.php#versand'); + } } // ── Konfiguration: aus gespeichertem Bericht oder aus der URL ─────────────── $saved = $pdo->prepare('SELECT r.*, s.name AS owner FROM saved_reports r LEFT JOIN staff s ON s.id = r.staff_id WHERE r.staff_id = ? OR r.is_shared = 1 ORDER BY r.name'); $saved->execute([$staff['id']]); $savedReports = $saved->fetchAll(); +$schedStmt = $pdo->prepare("SELECT sc.*, r.name AS report_name FROM report_schedules sc JOIN saved_reports r ON r.id = sc.report_id WHERE sc.staff_id = ? OR ? = 'admin' ORDER BY r.name, sc.id"); +$schedStmt->execute([$staff['id'], $staff['role']]); +$schedules = $schedStmt->fetchAll(); +$ownReports = array_values(array_filter($savedReports, fn($r) => (int) $r['staff_id'] === (int) $staff['id'] || $staff['role'] === 'admin')); $cfgIn = $_GET; if (isset($_GET['saved'])) { foreach ($savedReports as $sr) { @@ -175,4 +213,57 @@ require __DIR__ . '/_header.php'; +
> + Versand per E-Mail (Zeitplan) +

Ein gespeicherter Bericht wird regelmäßig neu berechnet und als Tabelle an die Empfänger gemailt. Der Zeitraum ist relativ (z. B. „Letzte Kalenderwoche“), die Datumsangaben im gespeicherten Bericht werden dafür ersetzt. Berechnet wird mit Ihren Abteilungsrechten.

+ + + + + + + + + + + + + + + + + +
BerichtRhythmusZeitraumEmpfängerWeniger MailsNächster VersandZuletzt
, + 'Nur bei Daten', 'skip_unchanged' => 'Nur bei Änderung', 'weekdays_only' => 'Nur Mo–Fr'] as $flag => $flagLabel): + if ($flag === 'weekdays_only' && $sc['frequency'] !== 'daily') continue; ?> +
+ +
' . e($sc['last_error']) . '' : ($sc['last_sent_at'] ? e(format_dt((string) $sc['last_sent_at'])) : '–') ?>' . e($sc['last_note']) . '' : '' ?> + 'Test senden', 'schedule_toggle' => (int) $sc['enabled'] ? 'Pausieren' : 'Aktivieren', 'schedule_delete' => 'Löschen'] as $act => $lbl): ?> +
>
+ +
+ + +
+ + +
+
+
+
+
+
+ + + + +
+
+
Nur Adressen aktiver Mitarbeiter; externe Empfänger kann ein Administrator eintragen.
+
+
+

Speichern Sie zuerst einen Bericht (unten), um einen Versand einzurichten.

+
+ diff --git a/DeskHub-Webseite/staff/settings.php b/DeskHub-Webseite/staff/settings.php index ae914717..09e68ccc 100644 --- a/DeskHub-Webseite/staff/settings.php +++ b/DeskHub-Webseite/staff/settings.php @@ -23,6 +23,8 @@ $cronJobs = [ 'remind_customers' => ['label' => 'Kundenerinnerung', 'file' => 'remind_customers.php', 'fn' => 'run_remind_customers'], 'send_csat' => ['label' => 'Zufriedenheitsabfrage', 'file' => 'send_csat.php', 'fn' => 'run_send_csat'], 'status_checks' => ['label' => 'Statusprüfungen', 'file' => 'status_checks.php', 'fn' => 'run_status_checks'], + 'send_scheduled_replies' => ['label' => 'Geplante Antworten', 'file' => 'send_scheduled_replies.php', 'fn' => 'run_send_scheduled_replies'], + 'send_report_schedules' => ['label' => 'Berichte per E-Mail', 'file' => 'send_report_schedules.php', 'fn' => 'run_send_report_schedules'], 'run_plugins' => ['label' => 'Plugins (Hintergrundaufgaben)', 'file' => 'run_plugins.php', 'fn' => 'run_plugins_cron'], ]; @@ -53,6 +55,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { set_setting('auto_close_enabled', !empty($_POST['auto_close_enabled']) ? '1' : '0'); $autoCloseDays = max(1, min(90, (int) ($_POST['auto_close_days'] ?? 7))); set_setting('auto_close_days', (string) $autoCloseDays); + set_setting('wait_reminder_enabled', !empty($_POST['wait_reminder_enabled']) ? '1' : '0'); + set_setting('wait_reminder_hours', (string) max(1, min(72, (int) ($_POST['wait_reminder_hours'] ?? 4)))); + set_setting('wait_reminder_email', !empty($_POST['wait_reminder_email']) ? '1' : '0'); set_setting('remind_enabled', !empty($_POST['remind_enabled']) ? '1' : '0'); set_setting('remind_after_days', (string) max(1, min(60, (int) ($_POST['remind_after_days'] ?? 3)))); csat_set_enabled(!empty($_POST['csat_enabled']), (int) ($_POST['csat_delay_minutes'] ?? 30)); @@ -71,6 +76,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { set_setting('retention_enabled', $enabled); set_setting('retention_months', (string)$months); set_setting('ip_anonymize', $ipAnon); + set_setting('gdpr_tools_enabled', !empty($_POST['gdpr_tools_enabled']) ? '1' : '0'); audit_log($staff['id'], 'Datenschutzeinstellungen geändert'); flash('success', 'Datenschutzeinstellungen gespeichert.'); redirect('/staff/settings.php?tab=datenschutz'); @@ -337,6 +343,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $extList = array_filter(array_map('trim', explode(',', strtolower($_POST['upload_allowed_extensions'] ?? '')))); $extList = array_diff($extList, $dangerous); set_setting('upload_allowed_extensions', implode(', ', $extList)); + $mailExt = array_diff(array_filter(array_map(fn($e) => ltrim(trim($e), '.'), explode(',', strtolower($_POST['mail_attachment_extra_extensions'] ?? '')))), $dangerous); + set_setting('mail_attachment_extra_extensions', implode(', ', $mailExt)); set_setting('upload_max_size_mb', max(1, min(100, (int)($_POST['upload_max_size_mb'] ?? 10)))); audit_log($staff['id'], 'Upload-Einstellungen geändert'); flash('success', 'Upload-Einstellungen gespeichert.'); @@ -467,6 +475,7 @@ $footerImprintLabel = get_setting('footer_imprint_label', 'Impressum'); $footerImprintUrl = get_setting('footer_imprint_url', ''); $footerImprintHtml = get_setting('footer_imprint_html', ''); $uploadExtensions = get_setting('upload_allowed_extensions', implode(', ', config()['uploads']['allowed_extensions'])); +$mailExtraExtensions = get_setting('mail_attachment_extra_extensions', MAIL_ATTACHMENT_EXTRA_DEFAULT); $uploadMaxSize = get_setting('upload_max_size_mb', '10'); $customThemes = list_custom_themes(); $activeCustomTheme = get_setting('active_custom_theme', ''); @@ -573,6 +582,17 @@ require __DIR__ . '/_header.php'; Tage + + +
+
+ + Stunden (zählt nur die Geschäftszeiten, wenn sie in den SLA-Einstellungen eingerichtet sind) + +