Quelltext des aktuellen Standes
<?php
/**
* Partial: ein einzelnes To-Do (Top-Level oder Unteraufgabe).
*
* Erwartete Variablen:
* $t array – To-Do-Datensatz (siehe TodoController::COLUMNS)
* $subs array – Unteraufgaben (nur bei Top-Level-Items), optional
*
* Wird sowohl beim initialen Seitenaufbau als auch von den AJAX-Endpunkten
* gerendert, damit Server und Client identisches Markup verwenden.
*/
$subs = $subs ?? [];
$isSub = $t['parent_id'] !== null;
$isDone = (int)$t['done'] === 1;
$prio = (int)$t['priority'];
$recur = (string)($t['recurrence'] ?? 'none');
$today = date('Y-m-d');
$tomorrow = date('Y-m-d', strtotime('+1 day'));
$hasDue = !empty($t['due_date']);
$isOverdue = !$isDone && $hasDue && $t['due_date'] < $today;
$isToday = $hasDue && $t['due_date'] === $today;
$isSoon = $hasDue && $t['due_date'] === $tomorrow;
$dueCls = $isOverdue ? 'todo-due--overdue' : ($isToday ? 'todo-due--today' : ($isSoon ? 'todo-due--soon' : ''));
$duePrefix = $isOverdue ? 'Überfällig' : ($isToday ? 'Heute' : ($isSoon ? 'Morgen' : ''));
$dueFmt = $hasDue ? date('d.m.Y', strtotime((string)$t['due_date'])) : '';
$timeVal = !empty($t['due_time']) ? substr((string)$t['due_time'], 0, 5) : '';
$prioLabels = [1 => $this->te('Niedrig'), 2 => $this->te('Mittel'), 3 => $this->te('Hoch')];
$recurLabels = ['daily' => $this->te('Täglich'), 'weekly' => $this->te('Wöchentlich'), 'monthly' => $this->te('Monatlich'), 'yearly' => $this->te('Jährlich')];
$subTotal = count($subs);
$subDone = 0;
foreach ($subs as $s) {
if ((int)$s['done'] === 1) {
$subDone++;
}
}
// Icons
$icoCal = '<svg viewBox="0 0 16 16" width="11" height="11" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="12" height="11" rx="1.5"/><line x1="5" y1="1" x2="5" y2="5"/><line x1="11" y1="1" x2="11" y2="5"/><line x1="2" y1="7" x2="14" y2="7"/></svg>';
$icoRepeat= '<svg viewBox="0 0 16 16" width="11" height="11" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><polyline points="3,5 3,2 6,2"/><path d="M3 8a5 5 0 0 1 9-3l1 1"/><polyline points="13,11 13,14 10,14"/><path d="M13 8a5 5 0 0 1-9 3l-1-1"/></svg>';
$icoTag = '<svg viewBox="0 0 16 16" width="10" height="10" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M2 2h5l7 7-5 5-7-7V2z"/><circle cx="5" cy="5" r="1" fill="currentColor" stroke="none"/></svg>';
$icoClock = '<svg viewBox="0 0 16 16" width="10" height="10" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="8" r="6"/><polyline points="8,5 8,8 10,9"/></svg>';
?>
<?php if ($isSub): ?>
<li class="todo-sub<?= $isDone ? ' todo-sub--done' : '' ?>" data-id="<?= (int)$t['id'] ?>">
<label class="todo-check-wrap todo-check-wrap--sm">
<input type="checkbox" class="todo-checkbox" <?= $isDone ? 'checked' : '' ?> onchange="Todo.toggleDone(this)">
<span class="todo-checkmark<?= $isDone ? ' checked' : '' ?>"></span>
</label>
<span class="todo-sub-title" ondblclick="Todo.startRename(this)"><?= $this->e($t['title']) ?></span>
<input type="text" class="todo-rename-input" hidden maxlength="500"
onblur="Todo.saveRename(this)" onkeydown="Todo.renameKey(event, this)">
<button class="todo-act todo-act-del" onclick="Todo.del(this)" title="<?= $this->te('Löschen') ?>" type="button">
<svg viewBox="0 0 16 16" width="12" height="12" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><line x1="2" y1="2" x2="14" y2="14"/><line x1="14" y1="2" x2="2" y2="14"/></svg>
</button>
</li>
<?php return; ?>
<?php endif; ?>
<li class="todo-item todo-prio--<?= $prio ?><?= $isDone ? ' todo-item--done' : '' ?><?= $isOverdue ? ' todo-item--overdue' : '' ?>"
data-id="<?= (int)$t['id'] ?>"
data-due="<?= $this->e($t['due_date'] ?? '') ?>"
data-time="<?= $this->e($timeVal) ?>"
data-priority="<?= $prio ?>"
data-category="<?= $this->e($t['category'] ?? '') ?>"
data-recurrence="<?= $this->e($recur) ?>"
data-lead="<?= (int)($t['remind_lead_days'] ?? 1) ?>"
<?= $isDone ? '' : 'draggable="true"' ?>>
<?php if (!$isDone): ?>
<span class="todo-drag" title="<?= $this->te('Ziehen zum Sortieren') ?>">
<svg viewBox="0 0 12 16" width="12" height="16" fill="none"><circle cx="4" cy="3" r="1.2" fill="currentColor"/><circle cx="4" cy="8" r="1.2" fill="currentColor"/><circle cx="4" cy="13" r="1.2" fill="currentColor"/><circle cx="8" cy="3" r="1.2" fill="currentColor"/><circle cx="8" cy="8" r="1.2" fill="currentColor"/><circle cx="8" cy="13" r="1.2" fill="currentColor"/></svg>
</span>
<?php endif; ?>
<label class="todo-check-wrap">
<input type="checkbox" class="todo-checkbox" <?= $isDone ? 'checked' : '' ?> onchange="Todo.toggleDone(this)">
<span class="todo-checkmark<?= $isDone ? ' checked' : '' ?>"></span>
</label>
<div class="todo-main">
<div class="todo-row1">
<span class="todo-title<?= $isDone ? ' todo-title--done' : '' ?>" <?= $isDone ? '' : 'ondblclick="Todo.startRename(this)"' ?>><?= $this->e($t['title']) ?></span>
<input type="text" class="todo-rename-input" hidden maxlength="500"
onblur="Todo.saveRename(this)" onkeydown="Todo.renameKey(event, this)">
</div>
<?php $hasMeta = $prio > 0 || $hasDue || $recur !== 'none' || !empty($t['category']) || $subTotal > 0; ?>
<?php if ($hasMeta): ?>
<div class="todo-meta">
<?php if ($prio > 0): ?>
<span class="todo-badge todo-badge-prio todo-badge-prio--<?= $prio ?>"><span class="todo-prio-dot"></span><?= $prioLabels[$prio] ?></span>
<?php endif; ?>
<?php if ($hasDue): ?>
<span class="todo-badge todo-badge-due <?= $dueCls ?>">
<?= $icoCal ?><?= $duePrefix !== '' ? $this->e($duePrefix) . ' · ' : '' ?><?= $this->e($dueFmt) ?><?= $timeVal !== '' ? ' · ' . $this->e($timeVal) : '' ?>
</span>
<?php endif; ?>
<?php if ($recur !== 'none'): ?>
<span class="todo-badge todo-badge-recur" title="<?= $this->te('Wiederholung') ?>"><?= $icoRepeat ?><?= $this->e($recurLabels[$recur] ?? '') ?></span>
<?php endif; ?>
<?php if (!empty($t['category'])): ?>
<span class="todo-badge todo-badge-cat"><?= $icoTag ?><?= $this->e($t['category']) ?></span>
<?php endif; ?>
<?php if ($subTotal > 0): ?>
<span class="todo-badge todo-badge-subs<?= $subDone === $subTotal ? ' is-complete' : '' ?>" title="<?= $this->te('Unteraufgaben') ?>">
<svg viewBox="0 0 16 16" width="10" height="10" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="2,8 6,12 14,4"/></svg>
<?= $subDone ?>/<?= $subTotal ?>
</span>
<?php endif; ?>
</div>
<?php endif; ?>
<?php if (!empty($t['notes'])): ?>
<div class="todo-notes-view"><?= nl2br($this->e($t['notes'])) ?></div>
<?php endif; ?>
<?php if (!$isDone): ?>
<!-- Editor-Panel -->
<div class="todo-editor" hidden>
<textarea class="todo-f-notes" rows="2" maxlength="5000" placeholder="<?= $this->te('Notiz hinzufügen …') ?>"><?= $this->e($t['notes'] ?? '') ?></textarea>
<div class="todo-editor-grid">
<label class="todo-f-label"><?= $this->te('Priorität') ?>
<select class="todo-f-priority">
<option value="0"<?= $prio === 0 ? ' selected' : '' ?>><?= $this->te('Keine') ?></option>
<option value="1"<?= $prio === 1 ? ' selected' : '' ?>><?= $this->te('Niedrig') ?></option>
<option value="2"<?= $prio === 2 ? ' selected' : '' ?>><?= $this->te('Mittel') ?></option>
<option value="3"<?= $prio === 3 ? ' selected' : '' ?>><?= $this->te('Hoch') ?></option>
</select>
</label>
<label class="todo-f-label"><?= $this->te('Kategorie') ?>
<input type="text" class="todo-f-category" list="todoCatList" maxlength="100"
value="<?= $this->e($t['category'] ?? '') ?>" placeholder="<?= $this->te('z. B. Arbeit') ?>">
</label>
<label class="todo-f-label"><?= $this->te('Fällig am') ?>
<input type="date" class="todo-f-due" value="<?= $this->e($t['due_date'] ?? '') ?>">
</label>
<label class="todo-f-label"><?= $this->te('Uhrzeit') ?>
<input type="time" class="todo-f-time" value="<?= $this->e($timeVal) ?>">
</label>
<label class="todo-f-label"><?= $this->te('Wiederholung') ?>
<select class="todo-f-recurrence">
<option value="none"<?= $recur === 'none' ? ' selected' : '' ?>><?= $this->te('Keine') ?></option>
<option value="daily"<?= $recur === 'daily' ? ' selected' : '' ?>><?= $this->te('Täglich') ?></option>
<option value="weekly"<?= $recur === 'weekly' ? ' selected' : '' ?>><?= $this->te('Wöchentlich') ?></option>
<option value="monthly"<?= $recur === 'monthly' ? ' selected' : '' ?>><?= $this->te('Monatlich') ?></option>
<option value="yearly"<?= $recur === 'yearly' ? ' selected' : '' ?>><?= $this->te('Jährlich') ?></option>
</select>
</label>
<label class="todo-f-label"><?= $this->te('Erinnerung (Tage vorher)') ?>
<input type="number" class="todo-f-lead" min="0" max="60" value="<?= (int)($t['remind_lead_days'] ?? 1) ?>">
</label>
</div>
<div class="todo-editor-actions">
<button type="button" class="todo-btn-save" onclick="Todo.saveEditor(this)"><?= $this->te('Speichern') ?></button>
<button type="button" class="todo-btn-cancel" onclick="Todo.toggleEditor(this)"><?= $this->te('Abbrechen') ?></button>
</div>
</div>
<?php endif; ?>
<?php if (!$isDone): ?>
<!-- Unteraufgaben -->
<div class="todo-subs"<?= $subTotal > 0 ? '' : ' hidden' ?>>
<ul class="todo-sublist">
<?php foreach ($subs as $s): ?>
<?= $this->render('todos/_item', ['t' => $s, 'subs' => []]) ?>
<?php endforeach; ?>
</ul>
<form class="todo-subadd" onsubmit="Todo.addSub(event, this)">
<input type="text" class="todo-subadd-input" maxlength="500" placeholder="<?= $this->te('Unteraufgabe hinzufügen …') ?>" autocomplete="off">
</form>
</div>
<?php endif; ?>
</div>
<div class="todo-actions">
<?php if (!$isDone): ?>
<button class="todo-act" onclick="Todo.toggleSubs(this)" title="<?= $this->te('Unteraufgaben') ?>" type="button">
<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 2v7a3 3 0 0 0 3 3h6"/><polyline points="10,9 13,12 10,15"/></svg>
</button>
<button class="todo-act" onclick="Todo.toggleEditor(this)" title="<?= $this->te('Bearbeiten') ?>" type="button">
<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M11.5 2.5a1.414 1.414 0 0 1 2 2L5 13l-3 1 1-3 8.5-8.5z"/></svg>
</button>
<?php elseif ($t['done_at']): ?>
<span class="todo-done-date"><?= date('d.m.Y', strtotime((string)$t['done_at'])) ?></span>
<?php endif; ?>
<button class="todo-act todo-act-del" onclick="Todo.del(this)" title="<?= $this->te('Löschen') ?>" type="button">
<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><line x1="2" y1="2" x2="14" y2="14"/><line x1="14" y1="2" x2="2" y2="14"/></svg>
</button>
</div>
</li>