Historie: Task-Notizen + Link sichtbar, Navigation zu Tagen mit Einträgen
- Notizfeld pro Task in der Reflexion (optional, jeder Status) — nutzt das bestehende dropReason-Feld (Limit 200->500). Wird in der Historie bei JEDEM Status angezeigt (bisher nur bei "verworfen"). - Optionaler Tages-Link wird in der Historie jetzt angezeigt. - Historie-Pfeile springen zum naechsten/vorherigen Tag MIT Eintraegen statt starr ueber Werktage — Wochenend-Arbeit (z. B. Sa 6.6.) ist nun erreichbar. Default-Tag = juengster Tag vor heute mit Eintrag. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
6e7a910ed3
commit
6acf9977ec
4 files changed
+116
-56
No files matched your search
@@ -68,9 +68,10 @@ export function HistorieRoute() {
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setDate(prevDate)}
|
||||
aria-label="Vorheriger Werktag"
|
||||
className="inline-flex h-9 w-9 items-center justify-center rounded-[var(--radius-md)] border border-[var(--color-border)] text-[var(--color-ink-muted)] hover:border-[var(--color-ink)] hover:text-[var(--color-ink)]"
|
||||
onClick={() => prevDate && setDate(prevDate)}
|
||||
disabled={!prevDate}
|
||||
aria-label="Vorheriger Tag mit Eintrag"
|
||||
className="inline-flex h-9 w-9 items-center justify-center rounded-[var(--radius-md)] border border-[var(--color-border)] text-[var(--color-ink-muted)] hover:border-[var(--color-ink)] hover:text-[var(--color-ink)] disabled:opacity-40 disabled:hover:border-[var(--color-border)]"
|
||||
>
|
||||
<ChevronLeft size={16} />
|
||||
</button>
|
||||
@@ -79,7 +80,7 @@ export function HistorieRoute() {
|
||||
type="button"
|
||||
onClick={() => nextDate && setDate(nextDate)}
|
||||
disabled={!nextDate}
|
||||
aria-label="Nächster Werktag"
|
||||
aria-label="Nächster Tag mit Eintrag"
|
||||
className="inline-flex h-9 w-9 items-center justify-center rounded-[var(--radius-md)] border border-[var(--color-border)] text-[var(--color-ink-muted)] hover:border-[var(--color-ink)] hover:text-[var(--color-ink)] disabled:opacity-40 disabled:hover:border-[var(--color-border)]"
|
||||
>
|
||||
<ChevronRight size={16} />
|
||||
@@ -154,21 +155,25 @@ function PersonCard({ person: p }: { person: HistoryPerson }) {
|
||||
: 'bg-[var(--color-border-strong)]',
|
||||
)}
|
||||
/>
|
||||
<span
|
||||
className={clsx(
|
||||
t.status === 'dropped' && 'text-[var(--color-ink-muted)] line-through',
|
||||
)}
|
||||
>
|
||||
{t.title}
|
||||
{t.unplanned && (
|
||||
<span className="ml-2 rounded-full bg-black/5 px-1.5 py-0.5 text-[10px] text-[var(--color-ink-muted)]">
|
||||
ungeplant
|
||||
<div className="flex-1">
|
||||
<span
|
||||
className={clsx(
|
||||
t.status === 'dropped' && 'text-[var(--color-ink-muted)] line-through',
|
||||
)}
|
||||
>
|
||||
{t.title}
|
||||
{t.unplanned && (
|
||||
<span className="ml-2 rounded-full bg-black/5 px-1.5 py-0.5 text-[10px] text-[var(--color-ink-muted)]">
|
||||
ungeplant
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
{t.dropReason && (
|
||||
<span className="mt-0.5 block whitespace-pre-wrap text-xs text-[var(--color-ink-muted)]">
|
||||
{t.dropReason}
|
||||
</span>
|
||||
)}
|
||||
{t.status === 'dropped' && t.dropReason && (
|
||||
<span className="text-xs text-[var(--color-ink-faint)]"> — {t.dropReason}</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
@@ -177,7 +182,17 @@ function PersonCard({ person: p }: { person: HistoryPerson }) {
|
||||
{p.reflectionText && (
|
||||
<p className="whitespace-pre-wrap text-sm text-[var(--color-ink)]">{p.reflectionText}</p>
|
||||
)}
|
||||
{p.todos.length === 0 && !p.reflectionText && (
|
||||
{p.url && (
|
||||
<a
|
||||
href={p.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="block truncate text-sm text-[var(--color-ink-muted)] underline hover:text-[var(--color-ink)]"
|
||||
>
|
||||
{p.url}
|
||||
</a>
|
||||
)}
|
||||
{p.todos.length === 0 && !p.reflectionText && !p.url && (
|
||||
<p className="text-xs text-[var(--color-ink-faint)]">Keine Details.</p>
|
||||
)}
|
||||
</Card.Body>
|
||||
|
||||
@@ -51,12 +51,13 @@ function ReflectForm({
|
||||
status: TodoStatus;
|
||||
bezugKind: 'project' | 'deal' | 'campaign' | 'allgemein' | null;
|
||||
unplanned?: boolean;
|
||||
dropReason?: string | null;
|
||||
}>;
|
||||
onDone: () => void;
|
||||
}) {
|
||||
const [todoStates, setTodoStates] = useState<
|
||||
Record<string, { status: TodoStatus; reason?: string }>
|
||||
>(Object.fromEntries(todos.map((t) => [t.id, { status: t.status }])));
|
||||
>(Object.fromEntries(todos.map((t) => [t.id, { status: t.status, reason: t.dropReason ?? '' }])));
|
||||
const [reflectionText, setReflectionText] = useState('');
|
||||
const [ampel, setAmpel] = useState<Ampel | null>(null);
|
||||
const [trend, setTrend] = useState<Trend | null>(null);
|
||||
@@ -86,6 +87,10 @@ function ReflectForm({
|
||||
setTodoStates((prev) => ({ ...prev, [id]: { ...prev[id], status } }));
|
||||
}
|
||||
|
||||
function setTodoReason(id: string, reason: string) {
|
||||
setTodoStates((prev) => ({ ...prev, [id]: { status: prev[id]?.status ?? 'open', reason } }));
|
||||
}
|
||||
|
||||
function handleSubmit(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
if (!ampel) {
|
||||
@@ -107,7 +112,7 @@ function ReflectForm({
|
||||
todos: todos.map((t) => ({
|
||||
id: t.id,
|
||||
status: todoStates[t.id]?.status ?? t.status,
|
||||
dropReason: todoStates[t.id]?.reason,
|
||||
dropReason: todoStates[t.id]?.reason?.trim() || undefined,
|
||||
})),
|
||||
reflectionText,
|
||||
ampel,
|
||||
@@ -138,36 +143,47 @@ function ReflectForm({
|
||||
{todos.map((t) => {
|
||||
const status = todoStates[t.id]?.status ?? t.status;
|
||||
return (
|
||||
<li key={t.id} className="flex items-center gap-2">
|
||||
<EditableTodoTitle id={t.id} title={t.title} />
|
||||
{t.unplanned && (
|
||||
<span className="rounded-full bg-black/5 px-1.5 py-0.5 text-[10px] text-[var(--color-ink-muted)]">
|
||||
ungeplant
|
||||
</span>
|
||||
)}
|
||||
<div className="flex gap-1">
|
||||
<StatusBtn
|
||||
active={status === 'done'}
|
||||
onClick={() => setTodoStatus(t.id, 'done')}
|
||||
label="Erledigt"
|
||||
>
|
||||
<Check size={14} />
|
||||
</StatusBtn>
|
||||
<StatusBtn
|
||||
active={status === 'open'}
|
||||
onClick={() => setTodoStatus(t.id, 'open')}
|
||||
label="Offen"
|
||||
>
|
||||
<Minus size={14} />
|
||||
</StatusBtn>
|
||||
<StatusBtn
|
||||
active={status === 'dropped'}
|
||||
onClick={() => setTodoStatus(t.id, 'dropped')}
|
||||
label="Verworfen"
|
||||
>
|
||||
<X size={14} />
|
||||
</StatusBtn>
|
||||
<li key={t.id} className="space-y-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<EditableTodoTitle id={t.id} title={t.title} />
|
||||
{t.unplanned && (
|
||||
<span className="rounded-full bg-black/5 px-1.5 py-0.5 text-[10px] text-[var(--color-ink-muted)]">
|
||||
ungeplant
|
||||
</span>
|
||||
)}
|
||||
<div className="flex gap-1">
|
||||
<StatusBtn
|
||||
active={status === 'done'}
|
||||
onClick={() => setTodoStatus(t.id, 'done')}
|
||||
label="Erledigt"
|
||||
>
|
||||
<Check size={14} />
|
||||
</StatusBtn>
|
||||
<StatusBtn
|
||||
active={status === 'open'}
|
||||
onClick={() => setTodoStatus(t.id, 'open')}
|
||||
label="Offen"
|
||||
>
|
||||
<Minus size={14} />
|
||||
</StatusBtn>
|
||||
<StatusBtn
|
||||
active={status === 'dropped'}
|
||||
onClick={() => setTodoStatus(t.id, 'dropped')}
|
||||
label="Verworfen"
|
||||
>
|
||||
<X size={14} />
|
||||
</StatusBtn>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
value={todoStates[t.id]?.reason ?? ''}
|
||||
onChange={(e) => setTodoReason(t.id, e.target.value)}
|
||||
placeholder="Notiz (optional) — Ergebnis, Kontext, Link …"
|
||||
maxLength={500}
|
||||
aria-label={`Notiz zu ${t.title}`}
|
||||
className="w-full rounded-[var(--radius-md)] border border-[var(--color-border)] bg-white px-2 py-1 text-xs focus:border-[var(--color-ink)] focus:outline-none"
|
||||
/>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
|
||||
Reference in new issue
Block a user