Beide Module wurden aus einem anderen Kundenprojekt kopiert und bereinigt: Knowledgecenter: - Bild-Cache (1.985 Dateien) geleert, Ordnerstruktur mit .gitkeep behalten - Files-Gallery-Lizenzschlüssel entfernt (config.php) - Kundenspezifische Kategorien gelöscht: Teil I/II/III QM-Struktur, nummerierte QM-Kapitel, Gremien (Präsidium/Finanzausschuss/Landesausschuss), Sitzungsservice-Serie, Protokoll-Abteilungen, Testeinträge - 56 generische Kategorien bleiben (Downloads, Onboarding, Personalthemen …) Tasks: - Alle Betriebsdaten gelöscht (114 Tasks, 914 Submissions, 579 Assignments) - Task-Definitionen und Verlinkungen geleert - Kategoriestruktur bleibt (Allgemein, IT, Personal, Buchhaltung …) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
553 lines
26 KiB
PHP
553 lines
26 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
if (!isset($task) && !isset($error)) {
|
|
require_once dirname(dirname(__DIR__)) . '/actions/admin/task_detail_action.php';
|
|
$viewData = task_cert_admin_task_detail_action();
|
|
if (is_array($viewData)) {
|
|
extract($viewData, EXTR_SKIP);
|
|
}
|
|
}
|
|
|
|
$error = $error ?? null;
|
|
$task = $task ?? null;
|
|
$dashboard = is_array($dashboard ?? null) ? $dashboard : [];
|
|
|
|
if (!function_exists('task_cert_render_styles')) {
|
|
require_once dirname(dirname(__DIR__)) . '/bootstrap.php';
|
|
}
|
|
|
|
task_cert_render_styles(['admin'], ['vendor/gridjs/mermaid.min.css']);
|
|
task_cert_render_scripts([
|
|
'vendor/gridjs/gridjs.umd.js',
|
|
'js/task_cert_tabs.js',
|
|
'js/task_cert_admin_overview_grid.js',
|
|
'js/task_cert_admin_actions.js',
|
|
'js/task_cert_admin_mark_completed.js',
|
|
]);
|
|
|
|
$taskId = (int) ($task['id'] ?? 0);
|
|
$taskTitle = (string) ($task['title'] ?? 'Übersicht');
|
|
$taskListUrl = task_cert_view_url('admin.task_list');
|
|
$taskEditUrl = $taskId > 0
|
|
? task_cert_view_url('admin.task_form', ['id' => $taskId])
|
|
: task_cert_view_url('admin.task_form');
|
|
$taskDetailUrl = task_cert_view_url('admin.task_detail');
|
|
$approvalDecisionUrl = task_cert_base_url() . '/endpoints/admin/approval_decision.php';
|
|
$resetAssignmentUrl = task_cert_base_url() . '/endpoints/admin/reset_assignment_progress.php';
|
|
$resetCycleUrl = task_cert_base_url() . '/endpoints/admin/reset_cycle_progress.php';
|
|
$markCompletedMethodsUrl = task_cert_base_url() . '/endpoints/admin/mark_completed_methods.php';
|
|
$markCompletedUrl = task_cert_base_url() . '/endpoints/admin/mark_completed.php';
|
|
$csrfToken = (string) ($csrf_token ?? '');
|
|
|
|
$filters = is_array($dashboard['filters'] ?? null) ? $dashboard['filters'] : [];
|
|
$cycleOptions = is_array($dashboard['cycle_options'] ?? null) ? $dashboard['cycle_options'] : [];
|
|
$selectedCycleKey = (string) ($dashboard['selected_cycle_key'] ?? '');
|
|
$cycleSelectionWarning = trim((string) ($dashboard['cycle_selection_warning'] ?? ''));
|
|
$activeTabKey = (string) ($dashboard['active_tab_key'] ?? 'overview');
|
|
$showCycleSelector = (bool) ($dashboard['show_cycle_selector'] ?? true);
|
|
$exportMode = (string) ($dashboard['export_mode'] ?? 'cycle');
|
|
$canExportAnswers = (bool) ($dashboard['can_export_answers'] ?? false);
|
|
|
|
$tabIndexMap = [
|
|
'overview' => '0',
|
|
'approvals' => '1',
|
|
'answers' => '2',
|
|
// Backward-Kompatibilität: alte Tab-Keys zeigen auf Übersicht
|
|
'todo' => '0',
|
|
'completed' => '0',
|
|
'overdue' => '0',
|
|
];
|
|
|
|
// Status-Vorfilter aus altem Tab-Key ableiten
|
|
$initialStatusFilter = '';
|
|
if ($activeTabKey === 'todo') {
|
|
$initialStatusFilter = 'open';
|
|
} elseif ($activeTabKey === 'completed') {
|
|
$initialStatusFilter = 'completed';
|
|
} elseif ($activeTabKey === 'overdue') {
|
|
$initialStatusFilter = 'overdue';
|
|
}
|
|
$activeTabIndex = $tabIndexMap[$activeTabKey] ?? '0';
|
|
|
|
$kpis = is_array($dashboard['kpis'] ?? null) ? $dashboard['kpis'] : [];
|
|
$methodStats = is_array($dashboard['method_stats'] ?? null) ? $dashboard['method_stats'] : [];
|
|
$todoRows = is_array($dashboard['todo_rows'] ?? null) ? $dashboard['todo_rows'] : [];
|
|
$completedRows = is_array($dashboard['completed_rows'] ?? null) ? $dashboard['completed_rows'] : [];
|
|
$approvalRows = is_array($dashboard['approval_rows'] ?? null) ? $dashboard['approval_rows'] : [];
|
|
$overdueRows = is_array($dashboard['overdue_rows'] ?? null) ? $dashboard['overdue_rows'] : [];
|
|
$submissionRows = is_array($dashboard['submission_rows'] ?? null) ? $dashboard['submission_rows'] : [];
|
|
|
|
// Zusammengeführte Zeilen für die Übersicht-Grid-Tabelle
|
|
$mergedRows = [];
|
|
$seenAssignmentIds = [];
|
|
$todoAssignmentIds = [];
|
|
|
|
foreach ($todoRows as $row) {
|
|
$assignmentId = (int) ($row['id'] ?? 0);
|
|
if ($assignmentId > 0 && isset($seenAssignmentIds[$assignmentId])) {
|
|
continue;
|
|
}
|
|
$seenAssignmentIds[$assignmentId] = true;
|
|
$todoAssignmentIds[$assignmentId] = true;
|
|
|
|
$graceUntilRaw = trim((string) ($row['grace_until'] ?? ''));
|
|
$graceBadgeHtml = '';
|
|
if ($graceUntilRaw !== '') {
|
|
try {
|
|
$graceUntilDt = new DateTimeImmutable($graceUntilRaw);
|
|
if ((new DateTimeImmutable()) < $graceUntilDt) {
|
|
$graceBadgeHtml = '<span class="task-cert-grace-badge">Karenz bis ' . TaskCertView::e($graceUntilDt->format('d.m.Y')) . '</span>';
|
|
}
|
|
} catch (Throwable $e) {
|
|
// ignore
|
|
}
|
|
}
|
|
|
|
$todoActionHtml = '';
|
|
if ($assignmentId > 0) {
|
|
$todoActionHtml = '<button type="button" class="btn task-cert-btn-success task-cert-btn-sm task-cert-btn-mark-completed" data-assignment-id="' . $assignmentId . '">Erledigt markieren</button>';
|
|
}
|
|
|
|
$mergedRows[] = [
|
|
'contact_name' => (string) ($row['contact_name'] ?? '-'),
|
|
'contact_email' => (string) ($row['contact_email'] ?? ''),
|
|
'_row_group' => 'todo',
|
|
'_row_group_label' => 'Offen',
|
|
'status' => (string) ($row['status'] ?? ''),
|
|
'status_label' => '',
|
|
'assigned_at_sort' => (string) ($row['created_at'] ?? ''),
|
|
'due_at_sort' => (string) ($row['due_at'] ?? ''),
|
|
'completed_at_sort' => null,
|
|
'overdue_at_sort' => (string) ($row['overdue_at'] ?? ''),
|
|
'escalation_level' => (int) ($row['escalation_level'] ?? 0),
|
|
'grace_badge_html' => $graceBadgeHtml,
|
|
'last_activity_sort' => (string) ($row['last_submitted_at'] ?? ''),
|
|
'certificate_label' => null,
|
|
'certificate_code' => null,
|
|
'valid_until_sort' => null,
|
|
'cycle_key' => (string) ($row['cycle_key'] ?? ''),
|
|
'_row_html_action' => $todoActionHtml,
|
|
];
|
|
}
|
|
|
|
foreach ($completedRows as $row) {
|
|
$completedAssignmentId = (int) ($row['id'] ?? 0);
|
|
if ($completedAssignmentId > 0 && isset($seenAssignmentIds[$completedAssignmentId])) {
|
|
continue;
|
|
}
|
|
if ($completedAssignmentId > 0) {
|
|
$seenAssignmentIds[$completedAssignmentId] = true;
|
|
}
|
|
$certStatus = (string) ($row['certificate_status'] ?? '');
|
|
|
|
$actionHtml = '';
|
|
if ($completedAssignmentId > 0) {
|
|
$actionHtml = '<button type="button" class="btn task-cert-btn-danger task-cert-btn-sm task-cert-btn-reset-assignment" data-assignment-id="' . $completedAssignmentId . '">Zurücksetzen</button>';
|
|
}
|
|
|
|
$mergedRows[] = [
|
|
'contact_name' => (string) ($row['contact_name'] ?? '-'),
|
|
'contact_email' => (string) ($row['contact_email'] ?? ''),
|
|
'_row_group' => 'completed',
|
|
'_row_group_label' => 'Erledigt',
|
|
'status' => 'completed',
|
|
'status_label' => '',
|
|
'assigned_at_sort' => null,
|
|
'due_at_sort' => null,
|
|
'completed_at_sort' => (string) ($row['completed_at'] ?? ''),
|
|
'overdue_at_sort' => null,
|
|
'escalation_level' => null,
|
|
'grace_badge_html' => '',
|
|
'last_activity_sort' => null,
|
|
'certificate_label' => '',
|
|
'certificate_code' => (string) ($row['certificate_code'] ?? ''),
|
|
'valid_until_sort' => (string) ($row['certificate_valid_until'] ?? ''),
|
|
'cycle_key' => (string) ($row['cycle_key'] ?? ''),
|
|
'_row_html_action' => $actionHtml,
|
|
];
|
|
|
|
// Zertifikat-Label setzen
|
|
$lastIdx = count($mergedRows) - 1;
|
|
if ((int) ($row['certificate_id'] ?? 0) > 0) {
|
|
$mergedRows[$lastIdx]['certificate_label'] = $certStatus;
|
|
}
|
|
}
|
|
|
|
foreach ($overdueRows as $row) {
|
|
$assignmentId = (int) ($row['id'] ?? 0);
|
|
if ($assignmentId > 0 && isset($seenAssignmentIds[$assignmentId])) {
|
|
continue; // Bereits in Todo- oder Completed-Zeilen enthalten
|
|
}
|
|
if ($assignmentId > 0) {
|
|
$seenAssignmentIds[$assignmentId] = true;
|
|
}
|
|
|
|
$overdueActionHtml = '';
|
|
if ($assignmentId > 0) {
|
|
$overdueActionHtml = '<button type="button" class="btn task-cert-btn-success task-cert-btn-sm task-cert-btn-mark-completed" data-assignment-id="' . $assignmentId . '">Erledigt markieren</button>';
|
|
}
|
|
|
|
$mergedRows[] = [
|
|
'contact_name' => (string) ($row['contact_name'] ?? '-'),
|
|
'contact_email' => (string) ($row['contact_email'] ?? ''),
|
|
'_row_group' => 'overdue',
|
|
'_row_group_label' => 'Überfällig',
|
|
'status' => (string) ($row['status'] ?? ''),
|
|
'status_label' => '',
|
|
'assigned_at_sort' => null,
|
|
'due_at_sort' => (string) ($row['due_at'] ?? ''),
|
|
'completed_at_sort' => null,
|
|
'overdue_at_sort' => (string) ($row['overdue_at'] ?? ''),
|
|
'escalation_level' => (int) ($row['escalation_level'] ?? 0),
|
|
'grace_badge_html' => '',
|
|
'last_activity_sort' => (string) ($row['last_submitted_at'] ?? ''),
|
|
'certificate_label' => null,
|
|
'certificate_code' => null,
|
|
'valid_until_sort' => null,
|
|
'cycle_key' => '',
|
|
'_row_html_action' => $overdueActionHtml,
|
|
];
|
|
}
|
|
|
|
$statusLabelMap = TaskCertConstants::assignmentStatusLabels();
|
|
$methodLabelMap = TaskCertConstants::methodLabels();
|
|
$submissionStatusLabelMap = TaskCertConstants::submissionStatusLabels();
|
|
$certificateStatusLabelMap = TaskCertConstants::certificateStatusLabels();
|
|
|
|
// Status- und Zertifikat-Labels in die mergedRows einfügen
|
|
foreach ($mergedRows as &$mergedRow) {
|
|
$s = $mergedRow['status'] ?? '';
|
|
if ($mergedRow['status_label'] === '' && $s !== '') {
|
|
$mergedRow['status_label'] = $statusLabelMap[$s] ?? $s;
|
|
}
|
|
$cl = $mergedRow['certificate_label'] ?? null;
|
|
if ($cl !== null && $cl !== '' && isset($certificateStatusLabelMap[$cl])) {
|
|
$mergedRow['certificate_label'] = $certificateStatusLabelMap[$cl];
|
|
}
|
|
}
|
|
unset($mergedRow);
|
|
|
|
$mergedRowsJson = json_encode($mergedRows, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_UNICODE);
|
|
|
|
// Antworten-Zeilen für Grid.js aufbereiten
|
|
$downloadFileUrl = task_cert_base_url() . '/endpoints/admin/download_submission_file.php';
|
|
$answersGridRows = [];
|
|
foreach ($submissionRows as $subRow) {
|
|
$subStatus = (string) ($subRow['status'] ?? '');
|
|
$methodType = (string) ($subRow['method_type'] ?? '');
|
|
$answerItems = is_array($subRow['answer_items'] ?? null) ? $subRow['answer_items'] : [];
|
|
|
|
$detailHtml = '';
|
|
if ($answerItems !== []) {
|
|
$detailHtml = '<div class="task-cert-monitoring-answer-items">';
|
|
foreach ($answerItems as $item) {
|
|
$detailHtml .= '<div><strong>' . TaskCertView::e((string) ($item['label'] ?? '')) . ':</strong> ';
|
|
|
|
$itemFiles = is_array($item['files'] ?? null) ? $item['files'] : [];
|
|
if ($itemFiles !== []) {
|
|
$links = [];
|
|
foreach ($itemFiles as $file) {
|
|
$url = $downloadFileUrl . '?' . http_build_query([
|
|
'submission_id' => (int) ($file['submission_id'] ?? 0),
|
|
'field_key' => (string) ($file['field_key'] ?? ''),
|
|
'index' => (int) ($file['index'] ?? 0),
|
|
'csrf_token' => $csrfToken,
|
|
]);
|
|
$links[] = '<a href="' . TaskCertView::e($url) . '" target="_blank" rel="noopener">'
|
|
. TaskCertView::e((string) ($file['name'] ?? 'Datei')) . '</a>';
|
|
}
|
|
$detailHtml .= implode(', ', $links);
|
|
} else {
|
|
$detailHtml .= TaskCertView::e((string) ($item['value'] ?? ''));
|
|
}
|
|
|
|
$detailHtml .= '</div>';
|
|
}
|
|
$detailHtml .= '</div>';
|
|
}
|
|
|
|
$score = null;
|
|
if ($subRow['score'] !== null && $subRow['max_score'] !== null) {
|
|
$score = (int) $subRow['score'] . '/' . (int) $subRow['max_score'];
|
|
}
|
|
|
|
// answer_items als Key-Value-Map für CSV-Export
|
|
$answerMap = [];
|
|
foreach ($answerItems as $item) {
|
|
$label = (string) ($item['label'] ?? '');
|
|
if ($label !== '') {
|
|
$answerMap[$label] = (string) ($item['value'] ?? '');
|
|
}
|
|
}
|
|
|
|
$answersGridRows[] = [
|
|
'contact_name' => (string) ($subRow['contact_name'] ?? '-'),
|
|
'contact_email' => (string) ($subRow['contact_email'] ?? ''),
|
|
'method_label' => (string) ($subRow['method_label'] ?? ($methodLabelMap[$methodType] ?? $methodType)),
|
|
'status_label' => $submissionStatusLabelMap[$subStatus] ?? $subStatus,
|
|
'submitted_at_sort' => (string) ($subRow['submitted_at'] ?? ''),
|
|
'score' => $score,
|
|
'score_num' => $subRow['score'] !== null ? (int) $subRow['score'] : null,
|
|
'detail_html' => $detailHtml,
|
|
'answer_fields' => $answerMap,
|
|
];
|
|
}
|
|
$answersGridRowsJson = json_encode($answersGridRows, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_UNICODE);
|
|
|
|
$formatDateTime = static function ($value): string {
|
|
$value = trim((string) $value);
|
|
if ($value === '') {
|
|
return '-';
|
|
}
|
|
|
|
try {
|
|
return (new DateTimeImmutable($value))->format('d.m.Y H:i');
|
|
} catch (Throwable $throwable) {
|
|
return $value;
|
|
}
|
|
};
|
|
|
|
$hasCycle = (bool) ($dashboard['has_cycle'] ?? ($selectedCycleKey !== ''));
|
|
$noCycleReason = trim((string) ($dashboard['no_cycle_reason'] ?? ''));
|
|
if ($noCycleReason === '') {
|
|
$noCycleReason = 'Aktuell ist kein Zyklus verfügbar.';
|
|
}
|
|
|
|
$exportAnswersUrl = '';
|
|
if ($taskId > 0 && $canExportAnswers) {
|
|
$exportQuery = [
|
|
'id' => $taskId,
|
|
'mode' => $exportMode,
|
|
'csrf_token' => $csrfToken,
|
|
];
|
|
if ($exportMode === 'cycle' && $selectedCycleKey !== '') {
|
|
$exportQuery['cycle_key'] = $selectedCycleKey;
|
|
}
|
|
$exportAnswersUrl = task_cert_base_url() . '/endpoints/admin/export_answers_csv.php?' . http_build_query($exportQuery);
|
|
}
|
|
?>
|
|
<div
|
|
class="task-cert task-cert-admin-detail task-cert-admin-monitoring"
|
|
data-task-cert-tabs
|
|
data-tabs-active="<?php echo TaskCertView::e($activeTabIndex); ?>"
|
|
data-csrf-token="<?php echo TaskCertView::e($csrfToken); ?>"
|
|
data-reset-assignment-url="<?php echo TaskCertView::e($resetAssignmentUrl); ?>"
|
|
data-reset-cycle-url="<?php echo TaskCertView::e($resetCycleUrl); ?>"
|
|
data-approval-url="<?php echo TaskCertView::e($approvalDecisionUrl); ?>"
|
|
data-task-id="<?php echo (int) $taskId; ?>"
|
|
>
|
|
<div id="task-cert-toast-container" class="task-cert-toast-container" aria-live="polite"></div>
|
|
|
|
<div class="task-cert-form-header">
|
|
<div class="task-cert-title-wrap">
|
|
<a
|
|
href="<?php echo TaskCertView::e($taskListUrl); ?>"
|
|
class="task-cert-back-link"
|
|
aria-label="Zur Aufgabenliste"
|
|
title="Zur Aufgabenliste"
|
|
>←</a>
|
|
<h2><?php echo TaskCertView::e($taskTitle); ?></h2>
|
|
<?php
|
|
$frequencyType = (string) ($task['frequency_type'] ?? '');
|
|
$recurrenceBasis = (string) ($task['recurrence_basis'] ?? '');
|
|
$recurrenceUnit = (string) ($task['recurrence_unit'] ?? '');
|
|
$isCompletionBased = $frequencyType === 'recurring' && $recurrenceBasis === 'completion';
|
|
$isCalendarBased = $frequencyType === 'recurring' && $recurrenceBasis !== 'completion';
|
|
$isOneTime = $frequencyType !== 'recurring';
|
|
$unitLabels = ['day' => 'Täglich', 'week' => 'Wöchentlich', 'month' => 'Monatlich', 'year' => 'Jährlich'];
|
|
?>
|
|
<?php if ($isCompletionBased): ?>
|
|
<span class="task-cert-recurrence-badge is-completion"><?php echo TaskCertView::e($unitLabels[$recurrenceUnit] ?? 'Wiederkehrend'); ?> (erledigungsbasiert)</span>
|
|
<?php elseif ($isCalendarBased): ?>
|
|
<span class="task-cert-recurrence-badge is-calendar"><?php echo TaskCertView::e($unitLabels[$recurrenceUnit] ?? 'Wiederkehrend'); ?> (kalenderbasiert)</span>
|
|
<?php else: ?>
|
|
<span class="task-cert-recurrence-badge is-onetime">Einmalig</span>
|
|
<?php endif; ?>
|
|
</div>
|
|
<div class="task-cert-toolbar-right">
|
|
<?php if ($showCycleSelector && $cycleOptions !== []): ?>
|
|
<form action="<?php echo TaskCertView::e($taskDetailUrl); ?>" method="get" class="task-cert-cycle-switch-inline" data-cycle-switch-form>
|
|
<input type="hidden" name="id" value="<?php echo (int) $taskId; ?>">
|
|
<input type="hidden" name="tab" value="<?php echo TaskCertView::e($activeTabKey); ?>" data-tabs-active-input>
|
|
<select name="cycle_key" onchange="this.form.submit()">
|
|
<?php foreach ($cycleOptions as $option): ?>
|
|
<?php $cycleKey = (string) ($option['cycle_key'] ?? ''); ?>
|
|
<?php if ($cycleKey === '') { continue; } ?>
|
|
<option value="<?php echo TaskCertView::e($cycleKey); ?>" <?php echo $cycleKey === $selectedCycleKey ? 'selected' : ''; ?>>
|
|
<?php echo TaskCertView::e((string) ($option['label'] ?? $cycleKey)); ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</form>
|
|
<?php endif; ?>
|
|
<a href="<?php echo TaskCertView::e($taskListUrl); ?>" class="btn">Zur Liste</a>
|
|
<?php if ($taskId > 0): ?>
|
|
<a href="<?php echo TaskCertView::e($taskEditUrl); ?>" class="btn">Bearbeiten</a>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<?php if ($error): ?>
|
|
<div class="task-cert-error"><?php echo TaskCertView::e($error); ?></div>
|
|
<?php elseif ($task): ?>
|
|
<?php if ($cycleSelectionWarning !== ''): ?>
|
|
<div class="task-cert-warning"><?php echo TaskCertView::e($cycleSelectionWarning); ?></div>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($hasCycle): ?>
|
|
<div class="task-cert-wizard-tabs" role="tablist" aria-label="Reiter-Navigation">
|
|
<button type="button" class="task-cert-wizard-tab" data-tabs-tab="0" data-tab-key="overview" role="tab">Übersicht</button>
|
|
<button type="button" class="task-cert-wizard-tab" data-tabs-tab="1" data-tab-key="approvals" role="tab">Freigaben</button>
|
|
<button type="button" class="task-cert-wizard-tab" data-tabs-tab="2" data-tab-key="answers" role="tab">Antworten</button>
|
|
</div>
|
|
|
|
<!-- Panel 0: Übersicht (KPIs + zusammengeführte Tabelle) -->
|
|
<section class="task-cert-wizard-step" data-tabs-panel="0" hidden>
|
|
<?php
|
|
$totalAssigned = (int) ($kpis['total_assigned'] ?? 0);
|
|
$completedCount = (int) ($kpis['completed_count'] ?? 0);
|
|
$completionRate = $totalAssigned > 0 ? round(($completedCount / $totalAssigned) * 100, 1) : 0;
|
|
$overdueCount = (int) ($kpis['overdue_count'] ?? 0);
|
|
$approvalCount = (int) ($kpis['awaiting_approval_count'] ?? 0);
|
|
$escalatedCount = (int) ($kpis['escalated_count'] ?? 0);
|
|
?>
|
|
|
|
<div class="task-cert-kpi-tiles">
|
|
<div class="task-cert-kpi-tile">
|
|
<span class="task-cert-kpi-value"><?php echo $completedCount; ?> / <?php echo $totalAssigned; ?></span>
|
|
<span class="task-cert-kpi-label">Abgeschlossen (<?php echo TaskCertView::e((string) $completionRate); ?>%)</span>
|
|
</div>
|
|
<div class="task-cert-kpi-tile">
|
|
<span class="task-cert-kpi-value"><?php echo $overdueCount; ?></span>
|
|
<span class="task-cert-kpi-label">Überfällig</span>
|
|
</div>
|
|
<div class="task-cert-kpi-tile">
|
|
<span class="task-cert-kpi-value"><?php echo $approvalCount; ?></span>
|
|
<span class="task-cert-kpi-label">Freigaben offen</span>
|
|
</div>
|
|
<div class="task-cert-kpi-tile">
|
|
<span class="task-cert-kpi-value"><?php echo $escalatedCount; ?></span>
|
|
<span class="task-cert-kpi-label">Eskaliert</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="task-cert-overview-grid-toolbar">
|
|
<input type="text" id="task-cert-overview-search" placeholder="Suchen..." autocomplete="off">
|
|
|
|
<select id="task-cert-overview-status-filter">
|
|
<option value="">Alle Status</option>
|
|
<?php foreach ($statusLabelMap as $statusKey => $statusLabel): ?>
|
|
<option value="<?php echo TaskCertView::e($statusKey); ?>"><?php echo TaskCertView::e($statusLabel); ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
|
|
<button type="button" id="task-cert-overview-export" class="btn">CSV Export</button>
|
|
|
|
<?php if ($completedRows !== []): ?>
|
|
<button
|
|
type="button"
|
|
class="btn task-cert-btn-danger task-cert-btn-reset-cycle"
|
|
data-task-id="<?php echo (int) $taskId; ?>"
|
|
>Alle im Zyklus zurücksetzen</button>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<div
|
|
id="task-cert-overview-grid"
|
|
class="task-cert-table-wrap"
|
|
data-initial-status-filter="<?php echo TaskCertView::e($initialStatusFilter); ?>"
|
|
></div>
|
|
|
|
<script type="application/json" id="task-cert-overview-grid-data"><?php echo $mergedRowsJson; ?></script>
|
|
</section>
|
|
|
|
<!-- Panel 1: Freigaben (unverändert) -->
|
|
<section class="task-cert-wizard-step" data-tabs-panel="1" hidden>
|
|
<div class="task-cert-block">
|
|
<h3>Freigaben</h3>
|
|
<div class="task-cert-table-wrap">
|
|
<table class="task-cert-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Mitarbeiter</th>
|
|
<th>Methode</th>
|
|
<th>Einreichungsstatus</th>
|
|
<th>Eingereicht am</th>
|
|
<th>Zuweisungsstatus</th>
|
|
<th>Fällig am</th>
|
|
<th>Aktion</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($approvalRows as $row): ?>
|
|
<?php
|
|
$submissionStatus = (string) ($row['status'] ?? '');
|
|
$assignmentStatus = (string) ($row['assignment_status'] ?? '');
|
|
?>
|
|
<tr>
|
|
<td>
|
|
<?php echo TaskCertView::e((string) ($row['contact_name'] ?? '-')); ?><br>
|
|
<small><?php echo TaskCertView::e((string) ($row['contact_email'] ?? '')); ?></small>
|
|
</td>
|
|
<?php $methodType = (string) ($row['method_type'] ?? ''); ?>
|
|
<td><?php echo TaskCertView::e($methodLabelMap[$methodType] ?? $methodType); ?></td>
|
|
<td><?php echo TaskCertView::e($submissionStatusLabelMap[$submissionStatus] ?? $submissionStatus); ?></td>
|
|
<td><?php echo TaskCertView::e($formatDateTime($row['submitted_at'] ?? '')); ?></td>
|
|
<td><span class="task-cert-status-badge status-<?php echo TaskCertView::e($assignmentStatus); ?>"><?php echo TaskCertView::e($statusLabelMap[$assignmentStatus] ?? $assignmentStatus); ?></span></td>
|
|
<td><?php echo TaskCertView::e($formatDateTime($row['due_at'] ?? '')); ?></td>
|
|
<td>
|
|
<?php if ((int) ($row['id'] ?? 0) > 0): ?>
|
|
<div
|
|
class="task-cert-inline-approval-form"
|
|
data-submission-id="<?php echo (int) ($row['id'] ?? 0); ?>"
|
|
>
|
|
<textarea class="task-cert-approval-note" rows="2" placeholder="Notiz (optional)"></textarea>
|
|
<div class="task-cert-inline-approval-actions">
|
|
<button type="button" class="btn task-cert-btn-success task-cert-btn-approval" data-decision="approve">Freigeben</button>
|
|
<button type="button" class="btn task-cert-btn-danger task-cert-btn-approval" data-decision="reject">Ablehnen</button>
|
|
</div>
|
|
</div>
|
|
<?php else: ?>
|
|
-
|
|
<?php endif; ?>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
|
|
<?php if ($approvalRows === []): ?>
|
|
<tr>
|
|
<td colspan="7">Keine Freigaben im aktuellen Filter.</td>
|
|
</tr>
|
|
<?php endif; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Panel 2: Antworten -->
|
|
<section class="task-cert-wizard-step" data-tabs-panel="2" hidden>
|
|
<div class="task-cert-overview-grid-toolbar">
|
|
<input type="text" id="task-cert-answers-search" placeholder="Suchen..." autocomplete="off">
|
|
<button type="button" id="task-cert-answers-export" class="btn">CSV Export</button>
|
|
</div>
|
|
|
|
<div id="task-cert-answers-grid" class="task-cert-table-wrap"></div>
|
|
<script type="application/json" id="task-cert-answers-grid-data"><?php echo $answersGridRowsJson; ?></script>
|
|
</section>
|
|
<?php else: ?>
|
|
<div class="task-cert-warning"><?php echo TaskCertView::e($noCycleReason); ?></div>
|
|
<?php endif; ?>
|
|
<?php endif; ?>
|
|
|
|
<dialog id="task-cert-mark-completed-dialog" class="task-cert-dialog"></dialog>
|
|
<div
|
|
id="task-cert-mark-completed-config"
|
|
hidden
|
|
data-methods-url="<?php echo TaskCertView::e($markCompletedMethodsUrl); ?>"
|
|
data-submit-url="<?php echo TaskCertView::e($markCompletedUrl); ?>"
|
|
data-task-id="<?php echo $taskId; ?>"
|
|
></div>
|
|
</div>
|