Strichliste Paket 1
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
return <<<'SQL'
|
||||
CREATE TABLE scan_import_jobs (
|
||||
id CHAR(36) NOT NULL PRIMARY KEY,
|
||||
tenant_id CHAR(36) NOT NULL,
|
||||
uploaded_by_user_id CHAR(36) NOT NULL,
|
||||
source_filename VARCHAR(255) NOT NULL,
|
||||
source_path VARCHAR(255) NOT NULL,
|
||||
status VARCHAR(50) NOT NULL DEFAULT 'uploaded',
|
||||
template_id CHAR(36) NULL,
|
||||
error_message TEXT NULL,
|
||||
processed_at DATETIME NULL,
|
||||
posted_at DATETIME NULL,
|
||||
created_at DATETIME NOT NULL,
|
||||
updated_at DATETIME NOT NULL,
|
||||
INDEX scan_import_jobs_tenant_status_idx (tenant_id, status),
|
||||
FOREIGN KEY (tenant_id) REFERENCES tenants(id),
|
||||
FOREIGN KEY (uploaded_by_user_id) REFERENCES users(id)
|
||||
);
|
||||
SQL;
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
return <<<'SQL'
|
||||
CREATE TABLE scan_import_pages (
|
||||
id CHAR(36) NOT NULL PRIMARY KEY,
|
||||
job_id CHAR(36) NOT NULL,
|
||||
page_number INT NOT NULL,
|
||||
image_path VARCHAR(255) NULL,
|
||||
image_width INT NULL,
|
||||
image_height INT NULL,
|
||||
processing_status VARCHAR(50) NOT NULL DEFAULT 'pending',
|
||||
confidence DECIMAL(5,2) NULL,
|
||||
created_at DATETIME NOT NULL,
|
||||
updated_at DATETIME NOT NULL,
|
||||
UNIQUE (job_id, page_number),
|
||||
FOREIGN KEY (job_id) REFERENCES scan_import_jobs(id)
|
||||
);
|
||||
SQL;
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
return <<<'SQL'
|
||||
CREATE TABLE scan_import_rows (
|
||||
id CHAR(36) NOT NULL PRIMARY KEY,
|
||||
job_id CHAR(36) NOT NULL,
|
||||
page_id CHAR(36) NULL,
|
||||
row_index INT NOT NULL,
|
||||
member_id CHAR(36) NULL,
|
||||
member_name_raw VARCHAR(255) NULL,
|
||||
front_strokes_detected INT NOT NULL DEFAULT 0,
|
||||
back_strokes_detected INT NOT NULL DEFAULT 0,
|
||||
confidence DECIMAL(5,2) NULL,
|
||||
needs_review TINYINT(1) NOT NULL DEFAULT 1,
|
||||
corrected_front_strokes INT NULL,
|
||||
corrected_back_strokes INT NULL,
|
||||
posted_entry_id CHAR(36) NULL,
|
||||
created_at DATETIME NOT NULL,
|
||||
updated_at DATETIME NOT NULL,
|
||||
INDEX scan_import_rows_job_id_idx (job_id),
|
||||
INDEX scan_import_rows_member_id_idx (member_id),
|
||||
FOREIGN KEY (job_id) REFERENCES scan_import_jobs(id),
|
||||
FOREIGN KEY (page_id) REFERENCES scan_import_pages(id),
|
||||
FOREIGN KEY (member_id) REFERENCES members(id)
|
||||
);
|
||||
SQL;
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
return <<<'SQL'
|
||||
CREATE TABLE scan_templates (
|
||||
id CHAR(36) NOT NULL PRIMARY KEY,
|
||||
tenant_id CHAR(36) NOT NULL,
|
||||
name VARCHAR(180) NOT NULL,
|
||||
page_width INT NOT NULL,
|
||||
page_height INT NOT NULL,
|
||||
anchor_json LONGTEXT NULL,
|
||||
grid_json LONGTEXT NOT NULL,
|
||||
is_active TINYINT(1) NOT NULL DEFAULT 1,
|
||||
created_at DATETIME NOT NULL,
|
||||
updated_at DATETIME NOT NULL,
|
||||
UNIQUE (tenant_id, name),
|
||||
FOREIGN KEY (tenant_id) REFERENCES tenants(id)
|
||||
);
|
||||
SQL;
|
||||
@@ -5334,6 +5334,238 @@ function app_import_payment_csv(PDO $pdo, string $tenantId, array $file): array
|
||||
return ['created' => $created, 'duplicates' => $duplicates, 'missing' => $missing];
|
||||
}
|
||||
|
||||
function app_scan_import_tables_ready(PDO $pdo): bool
|
||||
{
|
||||
if (!scripts_table_exists($pdo, 'scan_import_jobs')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (['tenant_id', 'uploaded_by_user_id', 'source_filename', 'source_path', 'status', 'template_id', 'processed_at', 'created_at', 'updated_at'] as $columnName) {
|
||||
if (!app_table_has_column($pdo, 'scan_import_jobs', $columnName)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function app_scan_templates_for_tenant(PDO $pdo, string $tenantId): array
|
||||
{
|
||||
if ($tenantId === '' || !scripts_table_exists($pdo, 'scan_templates')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
foreach (['tenant_id', 'id', 'name', 'page_width', 'page_height', 'anchor_json', 'grid_json', 'is_active'] as $columnName) {
|
||||
if (!app_table_has_column($pdo, 'scan_templates', $columnName)) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
return app_query_all(
|
||||
$pdo,
|
||||
'SELECT id, tenant_id, name, page_width, page_height, anchor_json, grid_json, is_active FROM scan_templates WHERE tenant_id = :tenant_id ORDER BY is_active DESC, name ASC, id ASC',
|
||||
['tenant_id' => $tenantId]
|
||||
);
|
||||
}
|
||||
|
||||
function app_scan_import_jobs_for_tenant(PDO $pdo, string $tenantId): array
|
||||
{
|
||||
if ($tenantId === '' || !app_scan_import_tables_ready($pdo)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$supportsTemplates = scripts_table_exists($pdo, 'scan_templates');
|
||||
$templateJoin = $supportsTemplates
|
||||
? ' LEFT JOIN scan_templates st ON st.id = sj.template_id AND st.tenant_id = sj.tenant_id'
|
||||
: '';
|
||||
$templateSelect = $supportsTemplates
|
||||
? 'st.name AS template_name'
|
||||
: 'NULL AS template_name';
|
||||
|
||||
return app_query_all(
|
||||
$pdo,
|
||||
'SELECT sj.id, sj.tenant_id, sj.uploaded_by_user_id, sj.source_filename, sj.source_path, sj.status, sj.template_id, sj.error_message, sj.processed_at, sj.posted_at, sj.created_at, sj.updated_at, ' . $templateSelect . ' FROM scan_import_jobs sj' . $templateJoin . ' WHERE sj.tenant_id = :tenant_id ORDER BY sj.created_at DESC, sj.id DESC LIMIT 25',
|
||||
['tenant_id' => $tenantId]
|
||||
);
|
||||
}
|
||||
|
||||
function app_scan_import_store_uploaded_pdf(array $file, string $tenantId): array
|
||||
{
|
||||
$tmpName = (string) ($file['tmp_name'] ?? '');
|
||||
$originalName = trim((string) ($file['name'] ?? ''));
|
||||
|
||||
if ($tenantId === '') {
|
||||
throw new RuntimeException('Der Mandant fehlt für den PDF-Upload.');
|
||||
}
|
||||
|
||||
if ($tmpName === '') {
|
||||
throw new RuntimeException('Die PDF-Datei konnte nicht verarbeitet werden.');
|
||||
}
|
||||
|
||||
$baseDir = rtrim(dirname(__DIR__), '/\\');
|
||||
$directory = $baseDir . '/storage/scan-imports/' . $tenantId . '/' . date('Y') . '/' . date('m') . '/';
|
||||
$filename = app_uuid() . '.pdf';
|
||||
$targetPath = $directory . $filename;
|
||||
|
||||
if (!is_dir($directory) && !mkdir($directory, 0775, true) && !is_dir($directory)) {
|
||||
throw new RuntimeException('Das Upload-Verzeichnis konnte nicht angelegt werden.');
|
||||
}
|
||||
|
||||
$stored = false;
|
||||
if (is_uploaded_file($tmpName)) {
|
||||
$stored = move_uploaded_file($tmpName, $targetPath);
|
||||
} else {
|
||||
$stored = @rename($tmpName, $targetPath);
|
||||
if (!$stored && is_file($tmpName)) {
|
||||
$stored = copy($tmpName, $targetPath);
|
||||
if ($stored) {
|
||||
@unlink($tmpName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$stored) {
|
||||
throw new RuntimeException('Die PDF-Datei konnte nicht gespeichert werden.');
|
||||
}
|
||||
|
||||
return [
|
||||
'original_filename' => $originalName !== '' ? $originalName : $filename,
|
||||
'filename' => $filename,
|
||||
'path' => $targetPath,
|
||||
];
|
||||
}
|
||||
|
||||
function app_create_scan_import_job(PDO $pdo, array $auth, array $file, string $templateId = ''): string
|
||||
{
|
||||
$tenantId = trim((string) ($auth['tenant_id'] ?? ''));
|
||||
|
||||
if ($tenantId === '') {
|
||||
throw new RuntimeException('Der Mandant konnte für den Scan-Import nicht bestimmt werden.');
|
||||
}
|
||||
|
||||
if (!app_scan_import_tables_ready($pdo)) {
|
||||
throw new RuntimeException('Die Scan-Import-Tabellen sind noch nicht angelegt. Bitte zuerst die Migrationen ausführen.');
|
||||
}
|
||||
|
||||
$fileError = (int) ($file['error'] ?? UPLOAD_ERR_NO_FILE);
|
||||
if ($fileError !== UPLOAD_ERR_OK) {
|
||||
throw new RuntimeException('Bitte wähle eine gültige PDF-Datei aus.');
|
||||
}
|
||||
|
||||
$fileSize = (int) ($file['size'] ?? 0);
|
||||
if ($fileSize <= 0 || $fileSize > 15 * 1024 * 1024) {
|
||||
throw new RuntimeException('Die PDF-Datei darf maximal 15 MB groß sein.');
|
||||
}
|
||||
|
||||
$originalName = strtolower(trim((string) ($file['name'] ?? '')));
|
||||
$mimeType = strtolower(trim((string) ($file['type'] ?? '')));
|
||||
$fileExtension = pathinfo($originalName, PATHINFO_EXTENSION);
|
||||
if ($fileExtension !== 'pdf' && !str_contains($mimeType, 'pdf')) {
|
||||
throw new RuntimeException('Es werden nur PDF-Dateien akzeptiert.');
|
||||
}
|
||||
|
||||
$templateId = trim($templateId);
|
||||
if ($templateId !== '') {
|
||||
if (!scripts_table_exists($pdo, 'scan_templates')) {
|
||||
throw new RuntimeException('Die Scan-Vorlagen-Tabelle ist noch nicht angelegt. Bitte zuerst die Migrationen ausführen.');
|
||||
}
|
||||
|
||||
if (app_query_one(
|
||||
$pdo,
|
||||
'SELECT id FROM scan_templates WHERE tenant_id = :tenant_id AND id = :id AND is_active = 1 LIMIT 1',
|
||||
[
|
||||
'tenant_id' => $tenantId,
|
||||
'id' => $templateId,
|
||||
]
|
||||
) === null) {
|
||||
throw new RuntimeException('Die ausgewählte Scan-Vorlage wurde nicht gefunden.');
|
||||
}
|
||||
} else {
|
||||
$templateId = '';
|
||||
}
|
||||
|
||||
$storedFile = app_scan_import_store_uploaded_pdf($file, $tenantId);
|
||||
$jobId = app_uuid();
|
||||
$now = date('Y-m-d H:i:s');
|
||||
$uploadedByUserId = trim((string) ($auth['user_id'] ?? ''));
|
||||
|
||||
if ($uploadedByUserId === '') {
|
||||
throw new RuntimeException('Die Benutzerkennung für den Upload fehlt.');
|
||||
}
|
||||
|
||||
$pdo->beginTransaction();
|
||||
|
||||
try {
|
||||
app_execute(
|
||||
$pdo,
|
||||
'INSERT INTO scan_import_jobs (id, tenant_id, uploaded_by_user_id, source_filename, source_path, status, template_id, processed_at, created_at, updated_at) VALUES (:id, :tenant_id, :uploaded_by_user_id, :source_filename, :source_path, :status, :template_id, :processed_at, :created_at, :updated_at)',
|
||||
[
|
||||
'id' => $jobId,
|
||||
'tenant_id' => $tenantId,
|
||||
'uploaded_by_user_id' => $uploadedByUserId,
|
||||
'source_filename' => $storedFile['original_filename'],
|
||||
'source_path' => $storedFile['path'],
|
||||
'status' => 'uploaded',
|
||||
'template_id' => $templateId !== '' ? $templateId : null,
|
||||
'processed_at' => null,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]
|
||||
);
|
||||
|
||||
$pdo->commit();
|
||||
|
||||
return $jobId;
|
||||
} catch (Throwable $exception) {
|
||||
if ($pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
|
||||
if (is_file($storedFile['path'] ?? '')) {
|
||||
@unlink($storedFile['path']);
|
||||
}
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
|
||||
function app_handle_scan_import_action(PDO $pdo, array $auth): void
|
||||
{
|
||||
if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'POST' || (string) ($_POST['action'] ?? '') !== 'create-scan-import-job') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!app_can_manage_finance($auth)) {
|
||||
app_flash('Für diesen PDF-Upload brauchst du Finanz- oder Tenant-Rechte.', 'warning');
|
||||
app_redirect('/imports/');
|
||||
}
|
||||
|
||||
try {
|
||||
if (!app_scan_import_tables_ready($pdo)) {
|
||||
throw new RuntimeException('Die Scan-Import-Tabellen sind noch nicht angelegt. Bitte zuerst die Migrationen ausführen.');
|
||||
}
|
||||
|
||||
$file = $_FILES['pdf_file'] ?? $_FILES['scan_pdf'] ?? [];
|
||||
if ($file === [] && !empty($_FILES)) {
|
||||
foreach ($_FILES as $candidateFile) {
|
||||
if (is_array($candidateFile)) {
|
||||
$file = $candidateFile;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$templateId = trim((string) ($_POST['template_id'] ?? ''));
|
||||
app_create_scan_import_job($pdo, $auth, $file, $templateId);
|
||||
|
||||
app_flash('Die PDF-Datei wurde hochgeladen und als Import-Job angelegt.', 'success');
|
||||
app_redirect('/imports/');
|
||||
} catch (Throwable $exception) {
|
||||
app_flash($exception->getMessage(), 'error');
|
||||
app_redirect('/imports/');
|
||||
}
|
||||
}
|
||||
|
||||
function app_handle_tenant_action(PDO $pdo, array $auth): void
|
||||
{
|
||||
if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'POST') {
|
||||
|
||||
@@ -106,6 +106,8 @@ $ledgerMembers = [];
|
||||
$paymentMembers = [];
|
||||
$reportRows = [];
|
||||
$importResult = ['rows' => [], 'summary' => ['imported' => 0, 'duplicates' => 0, 'unmatched' => 0]];
|
||||
$scanImportJobs = [];
|
||||
$scanTemplates = [];
|
||||
$surveyBoard = ['all' => [], 'published' => []];
|
||||
$activeSurvey = null;
|
||||
$surveyResults = [];
|
||||
@@ -222,7 +224,16 @@ if ($auth !== null && $pdo instanceof PDO) {
|
||||
}
|
||||
|
||||
if ($page === 'imports') {
|
||||
if (function_exists('app_handle_scan_import_action')) {
|
||||
app_handle_scan_import_action($pdo, $auth);
|
||||
}
|
||||
$importResult = app_handle_csv_import($pdo, $auth);
|
||||
if (function_exists('app_scan_templates_for_tenant')) {
|
||||
$scanTemplates = app_scan_templates_for_tenant($pdo, (string) $auth['tenant_id']);
|
||||
}
|
||||
if (function_exists('app_scan_import_jobs_for_tenant')) {
|
||||
$scanImportJobs = app_scan_import_jobs_for_tenant($pdo, (string) $auth['tenant_id']);
|
||||
}
|
||||
}
|
||||
|
||||
if ($page === 'surveys') {
|
||||
@@ -1625,6 +1636,105 @@ $marketing = app_marketing_messages();
|
||||
</form>
|
||||
</section>
|
||||
<section class="card" style="margin-top:18px"><div class="table"><table><thead><tr><th>Zeit</th><th>Mitglied</th><th>Methode</th><th>Betrag</th><th>Status</th><th>Aktion</th></tr></thead><tbody><?php foreach ($payments as $entry): ?><tr><td><?= dt((string) ($entry['booked_at'] ?? '')) ?></td><td><?= h((string) ($entry['member_name'] ?? '')) ?></td><td><?= h((string) ($entry['payment_method'] ?? '')) ?></td><td><?= money($entry['amount'] ?? 0) ?></td><td><?php if ((string) ($entry['status'] ?? 'booked') === 'cancelled'): ?><?= badge('Storniert', 'warning') ?><?php else: ?><?= badge('Aktiv', 'success') ?><?php endif; ?><?php if ((string) ($entry['status'] ?? 'booked') === 'cancelled' && (string) ($entry['cancelled_at'] ?? '') !== ''): ?><br><span class="muted"><?= dt((string) ($entry['cancelled_at'] ?? '')) ?></span><?php endif; ?></td><td><?php if ((string) ($entry['status'] ?? 'booked') === 'cancelled'): ?><span class="muted">Bereits storniert</span><?php else: ?><form method="post" action="/payments/"><input type="hidden" name="action" value="delete-payment"><input type="hidden" name="payment_id" value="<?= h((string) ($entry['id'] ?? '')) ?>"><button type="submit" class="button secondary">Stornieren</button></form><?php endif; ?></td></tr><?php endforeach; ?></tbody></table></div></section>
|
||||
<?php elseif ($page === 'imports'): ?>
|
||||
<section class="hero">
|
||||
<div class="eyebrow">Importe</div>
|
||||
<h1>Importe</h1>
|
||||
<p>CSV-Zahlungen und PDF-Scan-Jobs werden hier zusammen gebündelt, damit der Importweg im Tenant an einer Stelle landet.</p>
|
||||
</section>
|
||||
|
||||
<section class="grid grid-2">
|
||||
<article class="card">
|
||||
<div class="eyebrow">CSV-Import</div>
|
||||
<h2>PayPal-CSV hochladen</h2>
|
||||
<p>Importiert eine bestehende PayPal-CSV und ordnet passende Mitglieder automatisch zu.</p>
|
||||
<form method="post" action="/imports/" enctype="multipart/form-data" class="grid" style="margin-top:16px">
|
||||
<input type="hidden" name="action" value="import-paypal-csv">
|
||||
<label>CSV-Datei<input type="file" name="csv_file" accept=".csv,text/csv" required></label>
|
||||
<div class="actions" style="grid-column:1 / -1"><button type="submit">CSV importieren</button></div>
|
||||
</form>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="eyebrow">PDF-Scan</div>
|
||||
<h2>Scan-Import anlegen</h2>
|
||||
<p>Lade ein PDF hoch und wähle bei Bedarf eine Vorlage für die spätere Auswertung aus.</p>
|
||||
<form method="post" action="/imports/" enctype="multipart/form-data" class="grid" style="margin-top:16px">
|
||||
<input type="hidden" name="action" value="create-scan-import-job">
|
||||
<label>PDF-Datei<input type="file" name="scan_pdf" accept="application/pdf" required></label>
|
||||
<label>Template
|
||||
<select name="template_id">
|
||||
<option value="">Ohne Template</option>
|
||||
<?php foreach ($scanTemplates as $template): ?>
|
||||
<?php
|
||||
$templateId = (string) ($template['id'] ?? $template['template_id'] ?? $template['key'] ?? '');
|
||||
$templateLabel = (string) ($template['title'] ?? $template['label'] ?? $template['name'] ?? $template['template_name'] ?? $templateId);
|
||||
?>
|
||||
<option value="<?= h($templateId) ?>"><?= h($templateLabel) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
<div class="actions" style="grid-column:1 / -1"><button type="submit">Scan-Job erstellen</button></div>
|
||||
</form>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<?php
|
||||
$csvSummary = (array) ($importResult['summary'] ?? []);
|
||||
$csvImported = (int) ($csvSummary['imported'] ?? 0);
|
||||
$csvDuplicates = (int) ($csvSummary['duplicates'] ?? 0);
|
||||
$csvUnmatched = (int) ($csvSummary['unmatched'] ?? 0);
|
||||
$csvSummaryTotal = $csvImported + $csvDuplicates + $csvUnmatched;
|
||||
?>
|
||||
<?php if ($csvSummaryTotal > 0): ?>
|
||||
<section class="metric" style="margin-top:18px">
|
||||
<strong><?= num($csvImported) ?></strong>
|
||||
<h3>CSV-Import Ergebnis</h3>
|
||||
<p><?= num($csvImported) ?> importiert, <?= num($csvDuplicates) ?> Dubletten, <?= num($csvUnmatched) ?> ohne Zuordnung.</p>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<section class="card" style="margin-top:18px">
|
||||
<div class="actions" style="justify-content:space-between;margin-bottom:14px">
|
||||
<div>
|
||||
<div class="eyebrow">Scan-Jobs</div>
|
||||
<h2 style="margin:0">Letzte PDF-Scans</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Erstellt</th>
|
||||
<th>Datei</th>
|
||||
<th>Status</th>
|
||||
<th>Template</th>
|
||||
<th>Fehler</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($scanImportJobs as $job): ?>
|
||||
<?php
|
||||
$jobCreatedAt = (string) ($job['created_at'] ?? $job['created_at_local'] ?? '');
|
||||
$jobFileName = (string) ($job['file_name'] ?? $job['filename'] ?? $job['original_filename'] ?? '-');
|
||||
$jobStatus = (string) ($job['status'] ?? $job['state'] ?? '-');
|
||||
$jobTemplate = (string) ($job['template_name'] ?? $job['template_label'] ?? $job['template_title'] ?? $job['template_id'] ?? '-');
|
||||
$jobError = (string) ($job['error_message'] ?? $job['error'] ?? $job['last_error'] ?? '');
|
||||
?>
|
||||
<tr>
|
||||
<td><?= dt($jobCreatedAt !== '' ? $jobCreatedAt : null) ?></td>
|
||||
<td><?= h($jobFileName !== '' ? $jobFileName : '-') ?></td>
|
||||
<td><?= h($jobStatus !== '' ? $jobStatus : '-') ?></td>
|
||||
<td><?= h($jobTemplate !== '' ? $jobTemplate : '-') ?></td>
|
||||
<td><?= h($jobError !== '' ? $jobError : '-') ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if ($scanImportJobs === []): ?>
|
||||
<tr><td colspan="5">Noch keine Scan-Jobs vorhanden.</td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
<?php elseif ($page === 'content'): ?>
|
||||
<section class="hero"><div class="eyebrow">Hinweise und FAQ</div><h1>Hinweise und FAQ</h1><p>Hinweise und häufige Fragen werden pro Tenant gepflegt und direkt an die Mitglieder ausgespielt.</p></section>
|
||||
<section class="grid grid-2">
|
||||
|
||||
Reference in New Issue
Block a user