MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 58s
667 lines
26 KiB
TypeScript
667 lines
26 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import {
|
|
X,
|
|
Check,
|
|
Calendar,
|
|
Trash2,
|
|
FileText,
|
|
Plus,
|
|
CheckSquare,
|
|
Clock,
|
|
Layers,
|
|
Hash,
|
|
AlertTriangle,
|
|
Flame,
|
|
ChevronRight,
|
|
Folder,
|
|
} from 'lucide-react';
|
|
import { Thought, SubTask, TaskList, KanbanColumn } from '../../lib/types';
|
|
import { triggerHaptic } from '../../lib/api';
|
|
|
|
interface TaskDetailModalProps {
|
|
task: Thought | null;
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
activeColumns: KanbanColumn[];
|
|
taskLists: TaskList[];
|
|
onUpdateTitle: (task: Thought, newTitle: string) => Promise<void>;
|
|
onUpdateStatus: (task: Thought, newStatus: string) => Promise<void>;
|
|
onUpdatePriority: (task: Thought, newPriority: string) => Promise<void>;
|
|
onUpdateDueDate: (task: Thought, newDueDate: string) => Promise<void>;
|
|
onUpdateTaskList: (task: Thought, newListId: string) => Promise<void>;
|
|
onUpdateNotes: (task: Thought, newNotes: string) => Promise<void>;
|
|
onUpdateTags?: (task: Thought, newTags: string[]) => Promise<void>;
|
|
onToggleSubtask: (task: Thought, subtaskId: string) => Promise<void>;
|
|
onAddSubtask: (task: Thought, title: string) => Promise<void>;
|
|
onDeleteSubtask: (task: Thought, subtaskId: string) => Promise<void>;
|
|
onConvertToDoc: (task: Thought) => void;
|
|
onDeleteTask: (taskId: string) => void;
|
|
}
|
|
|
|
export const TaskDetailModal: React.FC<TaskDetailModalProps> = ({
|
|
task,
|
|
isOpen,
|
|
onClose,
|
|
activeColumns,
|
|
taskLists,
|
|
onUpdateTitle,
|
|
onUpdateStatus,
|
|
onUpdatePriority,
|
|
onUpdateDueDate,
|
|
onUpdateTaskList,
|
|
onUpdateNotes,
|
|
onUpdateTags,
|
|
onToggleSubtask,
|
|
onAddSubtask,
|
|
onDeleteSubtask,
|
|
onConvertToDoc,
|
|
onDeleteTask,
|
|
}) => {
|
|
if (!isOpen || !task) return null;
|
|
|
|
const [titleDraft, setTitleDraft] = useState(task.content);
|
|
const [isEditingTitle, setIsEditingTitle] = useState(false);
|
|
|
|
const [isEditingNotes, setIsEditingNotes] = useState(false);
|
|
const [notesDraft, setNotesDraft] = useState(task.notes || '');
|
|
|
|
const [newSubtaskTitle, setNewSubtaskTitle] = useState('');
|
|
const [newTagDraft, setNewTagDraft] = useState('');
|
|
|
|
useEffect(() => {
|
|
setTitleDraft(task.content);
|
|
setNotesDraft(task.notes || '');
|
|
setIsEditingTitle(false);
|
|
setIsEditingNotes(false);
|
|
}, [task.id]);
|
|
|
|
// Handle escape key to close
|
|
useEffect(() => {
|
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
if (e.key === 'Escape') {
|
|
if (isEditingNotes) {
|
|
setIsEditingNotes(false);
|
|
} else if (isEditingTitle) {
|
|
setIsEditingTitle(false);
|
|
} else {
|
|
onClose();
|
|
}
|
|
}
|
|
};
|
|
window.addEventListener('keydown', handleKeyDown);
|
|
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
}, [onClose, isEditingNotes, isEditingTitle]);
|
|
|
|
const handleTitleSubmit = async () => {
|
|
const trimmed = titleDraft.trim();
|
|
if (!trimmed) {
|
|
setTitleDraft(task.content);
|
|
setIsEditingTitle(false);
|
|
return;
|
|
}
|
|
if (trimmed !== task.content) {
|
|
await onUpdateTitle(task, trimmed);
|
|
}
|
|
setIsEditingTitle(false);
|
|
};
|
|
|
|
const handleNotesSubmit = async () => {
|
|
await onUpdateNotes(task, notesDraft);
|
|
setIsEditingNotes(false);
|
|
};
|
|
|
|
const handleAddSubtaskSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
const trimmed = newSubtaskTitle.trim();
|
|
if (!trimmed) return;
|
|
await onAddSubtask(task, trimmed);
|
|
setNewSubtaskTitle('');
|
|
};
|
|
|
|
const handleAddTagSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
let cleaned = newTagDraft.trim().replace(/^#+/, '').toLowerCase();
|
|
if (!cleaned) return;
|
|
const currentTags = Array.isArray(task.tags) ? [...task.tags] : [];
|
|
if (!currentTags.includes(cleaned)) {
|
|
const updated = [...currentTags, cleaned];
|
|
if (onUpdateTags) {
|
|
await onUpdateTags(task, updated);
|
|
} else {
|
|
await onUpdateTitle(task, `${task.content} #${cleaned}`);
|
|
}
|
|
}
|
|
setNewTagDraft('');
|
|
};
|
|
|
|
const handleRemoveTag = async (tagToRemove: string) => {
|
|
const currentTags = Array.isArray(task.tags) ? [...task.tags] : [];
|
|
const updated = currentTags.filter((t) => t !== tagToRemove);
|
|
if (onUpdateTags) {
|
|
await onUpdateTags(task, updated);
|
|
}
|
|
};
|
|
|
|
const handleQuickDueDate = (daysOffset: number | null) => {
|
|
if (daysOffset === null) {
|
|
onUpdateDueDate(task, '');
|
|
return;
|
|
}
|
|
const d = new Date();
|
|
d.setDate(d.getDate() + daysOffset);
|
|
const dateStr = d.toISOString().split('T')[0];
|
|
onUpdateDueDate(task, dateStr);
|
|
};
|
|
|
|
const currentColumn = activeColumns.find((c) => c.id === task.kanbanStatus) ||
|
|
(task.isCompleted
|
|
? activeColumns.find((c) => c.isDone) || activeColumns[activeColumns.length - 1]
|
|
: activeColumns.find((c) => !c.isDone) || activeColumns[0]);
|
|
|
|
const subtasks = Array.isArray(task.subtasks) ? task.subtasks : [];
|
|
const completedSubtasks = subtasks.filter((s) => s.isCompleted).length;
|
|
const subtasksPercent = subtasks.length > 0 ? Math.round((completedSubtasks / subtasks.length) * 100) : 0;
|
|
|
|
return (
|
|
<div className="fixed inset-0 modal-overlay z-50 flex items-center justify-center p-3 sm:p-4 overflow-y-auto">
|
|
<div
|
|
className="w-full max-w-2xl panel-surface shadow-panel border border-border my-auto flex flex-col max-h-[92vh] overflow-hidden"
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
{/* Top Header */}
|
|
<div className="flex items-center justify-between px-4 py-3 border-b border-border bg-panel">
|
|
<div className="flex items-center gap-2 min-w-0">
|
|
<button
|
|
onClick={() => {
|
|
triggerHaptic(15);
|
|
const nextState = !task.isCompleted;
|
|
const doneCol = activeColumns.find((c) => c.isDone) || activeColumns[activeColumns.length - 1];
|
|
const todoCol = activeColumns.find((c) => !c.isDone) || activeColumns[0];
|
|
const nextKanban = nextState ? (doneCol ? doneCol.id : 'done') : (todoCol ? todoCol.id : 'todo');
|
|
onUpdateStatus(task, nextKanban);
|
|
}}
|
|
className={`w-5 h-5 checkbox-square flex items-center justify-center flex-shrink-0 transition-colors border ${
|
|
task.isCompleted
|
|
? 'bg-status-ok border-status-ok text-on-accent'
|
|
: 'border-strong hover:border-accent text-transparent'
|
|
}`}
|
|
title={task.isCompleted ? 'Als offen markieren' : 'Als erledigt markieren'}
|
|
>
|
|
<Check className="w-3.5 h-3.5 stroke-[3]" />
|
|
</button>
|
|
<span className="text-xs font-semibold text-main truncate">
|
|
{task.isCompleted ? 'Erledigte Aufgabe' : 'Aufgabe bearbeiten'}
|
|
</span>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-1.5">
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
onClose();
|
|
onConvertToDoc(task);
|
|
}}
|
|
title="Als Dokument / Notiz vertiefen"
|
|
className="btn-action min-h-[38px] sm:min-h-0 h-9 sm:h-7 px-2.5 sm:px-2 text-xs flex items-center gap-1.5"
|
|
>
|
|
<FileText className="w-4 h-4 sm:w-3.5 sm:h-3.5 text-accent" />
|
|
<span className="hidden sm:inline">Als Notiz vertiefen</span>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
if (window.confirm('Möchten Sie diese Aufgabe wirklich löschen?')) {
|
|
onClose();
|
|
onDeleteTask(task.id);
|
|
}
|
|
}}
|
|
title="Aufgabe löschen"
|
|
className="btn-action min-h-[38px] sm:min-h-0 h-9 sm:h-7 px-2.5 sm:px-2 text-xs text-status-err border-status-err/30 hover:bg-status-err/10 flex items-center gap-1"
|
|
>
|
|
<Trash2 className="w-4 h-4 sm:w-3.5 sm:h-3.5" />
|
|
<span className="sm:hidden font-medium">Löschen</span>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={onClose}
|
|
className="text-muted hover:text-main p-2 sm:p-1.5 min-h-[38px] min-w-[38px] sm:min-h-0 sm:min-w-0 flex items-center justify-center transition-colors"
|
|
title="Schließen"
|
|
>
|
|
<X className="w-5 h-5 sm:w-4 sm:h-4" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Scrollable Body */}
|
|
<div className="p-4 overflow-y-auto space-y-4 flex-1">
|
|
{/* Task Title */}
|
|
<div>
|
|
{isEditingTitle ? (
|
|
<div className="space-y-2">
|
|
<textarea
|
|
value={titleDraft}
|
|
onChange={(e) => setTitleDraft(e.target.value)}
|
|
onKeyDown={(e) => {
|
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
e.preventDefault();
|
|
handleTitleSubmit();
|
|
} else if (e.key === 'Escape') {
|
|
setIsEditingTitle(false);
|
|
setTitleDraft(task.content);
|
|
}
|
|
}}
|
|
autoFocus
|
|
rows={2}
|
|
className="w-full text-base font-semibold input-field p-2.5 resize-none leading-snug"
|
|
/>
|
|
<div className="flex justify-end gap-1.5">
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
setIsEditingTitle(false);
|
|
setTitleDraft(task.content);
|
|
}}
|
|
className="btn-action h-7 px-2.5 text-xs"
|
|
>
|
|
Abbrechen
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={handleTitleSubmit}
|
|
className="btn-primary h-7 px-3 text-xs"
|
|
>
|
|
Speichern
|
|
</button>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div
|
|
onClick={() => setIsEditingTitle(true)}
|
|
className="group cursor-pointer p-2 rounded-card hover:bg-panel transition-colors border border-transparent hover:border-border"
|
|
title="Klicken zum Bearbeiten des Titels"
|
|
>
|
|
<h2
|
|
className={`text-base font-semibold break-words leading-snug ${
|
|
task.isCompleted ? 'line-through text-faint' : 'text-main'
|
|
}`}
|
|
>
|
|
{task.content}
|
|
</h2>
|
|
<span className="text-[10px] text-muted opacity-0 group-hover:opacity-100 transition-opacity">
|
|
Klicken zum Umbenennen
|
|
</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Kanban Status Buttons */}
|
|
<div className="space-y-1.5 pt-1 border-t border-border">
|
|
<label className="block text-xs font-semibold text-main">
|
|
Zustand / Kanban-Status:
|
|
</label>
|
|
<div className="flex flex-wrap items-center gap-1.5">
|
|
{activeColumns.map((col) => {
|
|
const isActive = (task.kanbanStatus === col.id) || (!task.kanbanStatus && col.id === 'todo');
|
|
return (
|
|
<button
|
|
key={col.id}
|
|
type="button"
|
|
onClick={() => {
|
|
triggerHaptic(10);
|
|
onUpdateStatus(task, col.id);
|
|
}}
|
|
className={`h-8 px-3 text-xs rounded-btn font-medium transition-all flex items-center gap-1.5 border ${
|
|
isActive
|
|
? 'bg-panel border-accent text-accent font-semibold shadow-sm'
|
|
: 'bg-canvas border-border text-muted hover:text-main hover:border-strong'
|
|
}`}
|
|
>
|
|
<span
|
|
className={`w-2 h-2 rounded-full ${
|
|
col.isDone
|
|
? 'bg-status-ok'
|
|
: col.id === 'in_progress'
|
|
? 'bg-accent'
|
|
: col.id === 'waiting'
|
|
? 'bg-status-warn'
|
|
: 'bg-faint'
|
|
}`}
|
|
/>
|
|
<span>{col.name}</span>
|
|
{isActive && <Check className="w-3 h-3 text-accent stroke-[3] ml-0.5" />}
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Priority Buttons */}
|
|
<div className="space-y-1.5 pt-1 border-t border-border">
|
|
<label className="block text-xs font-semibold text-main">
|
|
Priorität:
|
|
</label>
|
|
<div className="flex flex-wrap items-center gap-1.5">
|
|
<button
|
|
type="button"
|
|
onClick={() => onUpdatePriority(task, 'normal')}
|
|
className={`h-7 px-3 text-xs rounded-btn font-medium transition-colors border ${
|
|
!task.priority || task.priority === 'normal' || task.priority === 'low'
|
|
? 'bg-panel border-accent text-main font-semibold'
|
|
: 'bg-canvas border-border text-muted hover:text-main'
|
|
}`}
|
|
>
|
|
Normal / Keine
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => onUpdatePriority(task, 'high')}
|
|
className={`h-7 px-3 text-xs rounded-btn font-medium transition-colors border flex items-center gap-1 ${
|
|
task.priority === 'high'
|
|
? 'bg-status-warn/15 border-status-warn text-status-warn font-semibold'
|
|
: 'bg-canvas border-border text-muted hover:text-main'
|
|
}`}
|
|
>
|
|
<AlertTriangle className="w-3 h-3 text-status-warn" />
|
|
<span>Wichtig</span>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => onUpdatePriority(task, 'urgent')}
|
|
className={`h-7 px-3 text-xs rounded-btn font-medium transition-colors border flex items-center gap-1 ${
|
|
task.priority === 'urgent'
|
|
? 'bg-status-err/15 border-status-err text-status-err font-semibold'
|
|
: 'bg-canvas border-border text-muted hover:text-main'
|
|
}`}
|
|
>
|
|
<Flame className="w-3 h-3 text-status-err" />
|
|
<span>Dringend</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Due Date & List Selection Row */}
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 pt-1 border-t border-border">
|
|
{/* Due Date */}
|
|
<div className="space-y-1.5">
|
|
<label className="block text-xs font-semibold text-main flex items-center gap-1">
|
|
<Calendar className="w-3.5 h-3.5 text-accent" />
|
|
<span>Fälligkeitsdatum:</span>
|
|
</label>
|
|
<div className="flex items-center gap-1.5">
|
|
<input
|
|
type="date"
|
|
value={task.dueDate || ''}
|
|
onChange={(e) => onUpdateDueDate(task, e.target.value)}
|
|
className="flex-1 h-8 input-field px-2 text-xs bg-panel"
|
|
/>
|
|
{task.dueDate && (
|
|
<button
|
|
type="button"
|
|
onClick={() => onUpdateDueDate(task, '')}
|
|
className="btn-action h-8 px-2 text-xs text-muted hover:text-status-err"
|
|
title="Datum entfernen"
|
|
>
|
|
<X className="w-3.5 h-3.5" />
|
|
</button>
|
|
)}
|
|
</div>
|
|
<div className="flex flex-wrap gap-1 text-[10px]">
|
|
<button
|
|
type="button"
|
|
onClick={() => handleQuickDueDate(0)}
|
|
className="px-1.5 py-0.5 bg-canvas border border-border rounded-btn text-muted hover:text-main"
|
|
>
|
|
Heute
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => handleQuickDueDate(1)}
|
|
className="px-1.5 py-0.5 bg-canvas border border-border rounded-btn text-muted hover:text-main"
|
|
>
|
|
Morgen
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => handleQuickDueDate(7)}
|
|
className="px-1.5 py-0.5 bg-canvas border border-border rounded-btn text-muted hover:text-main"
|
|
>
|
|
+1 Woche
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Task List assignment */}
|
|
<div className="space-y-1.5">
|
|
<label className="block text-xs font-semibold text-main flex items-center gap-1">
|
|
<Folder className="w-3.5 h-3.5 text-accent" />
|
|
<span>Aufgabenliste:</span>
|
|
</label>
|
|
<select
|
|
value={task.taskListId || ''}
|
|
onChange={(e) => onUpdateTaskList(task, e.target.value)}
|
|
className="w-full h-8 input-field px-2.5 text-xs bg-panel"
|
|
>
|
|
<option value="">-- Keine Liste (Allgemein) --</option>
|
|
{taskLists.map((tl) => (
|
|
<option key={tl.id} value={tl.id}>
|
|
{tl.icon} {tl.title}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Hashtags Section */}
|
|
<div className="space-y-1.5 pt-1 border-t border-border">
|
|
<label className="block text-xs font-semibold text-main flex items-center gap-1">
|
|
<Hash className="w-3.5 h-3.5 text-accent" />
|
|
<span>Hashtags:</span>
|
|
</label>
|
|
<div className="flex flex-wrap items-center gap-1.5">
|
|
{Array.isArray(task.tags) &&
|
|
task.tags.map((tag) => (
|
|
<span
|
|
key={tag}
|
|
className="inline-flex items-center gap-1 px-2 py-0.5 rounded-pill bg-panel border border-border text-xs text-main"
|
|
>
|
|
<span>#{tag}</span>
|
|
<button
|
|
type="button"
|
|
onClick={() => handleRemoveTag(tag)}
|
|
className="text-muted hover:text-status-err"
|
|
>
|
|
<X className="w-3 h-3" />
|
|
</button>
|
|
</span>
|
|
))}
|
|
<form onSubmit={handleAddTagSubmit} className="inline-flex items-center gap-1">
|
|
<input
|
|
type="text"
|
|
value={newTagDraft}
|
|
onChange={(e) => setNewTagDraft(e.target.value)}
|
|
placeholder="+ #hashtag"
|
|
className="h-6 w-24 px-1.5 text-xs input-field"
|
|
/>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Subtasks Checklist */}
|
|
<div className="space-y-2 pt-1 border-t border-border">
|
|
<div className="flex items-center justify-between">
|
|
<label className="block text-xs font-semibold text-main flex items-center gap-1.5">
|
|
<CheckSquare className="w-3.5 h-3.5 text-accent" />
|
|
<span>Unteraufgaben ({completedSubtasks}/{subtasks.length}):</span>
|
|
</label>
|
|
{subtasks.length > 0 && (
|
|
<span className="text-[11px] font-mono text-muted">{subtasksPercent}%</span>
|
|
)}
|
|
</div>
|
|
|
|
{subtasks.length > 0 && (
|
|
<div className="w-full h-1 bg-border rounded-full overflow-hidden">
|
|
<div
|
|
className={`h-full transition-all duration-300 ${
|
|
subtasksPercent === 100 ? 'bg-status-ok' : 'bg-accent'
|
|
}`}
|
|
style={{ width: `${subtasksPercent}%` }}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{/* Subtask items list */}
|
|
{subtasks.length > 0 && (
|
|
<div className="space-y-1 max-h-48 overflow-y-auto pr-1">
|
|
{subtasks.map((st) => (
|
|
<div
|
|
key={st.id}
|
|
className="p-1.5 bg-canvas border border-border rounded-card flex items-center justify-between text-xs group"
|
|
>
|
|
<div className="flex items-center gap-2 min-w-0 flex-1">
|
|
<button
|
|
type="button"
|
|
onClick={() => onToggleSubtask(task, st.id)}
|
|
className={`w-3.5 h-3.5 checkbox-square flex items-center justify-center flex-shrink-0 transition-colors border ${
|
|
st.isCompleted
|
|
? 'bg-status-ok border-status-ok text-on-accent'
|
|
: 'border-strong hover:border-accent text-transparent'
|
|
}`}
|
|
>
|
|
<Check className="w-2.5 h-2.5 stroke-[3]" />
|
|
</button>
|
|
<span
|
|
className={`truncate ${
|
|
st.isCompleted ? 'line-through text-faint' : 'text-main'
|
|
}`}
|
|
>
|
|
{st.title}
|
|
</span>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
onClick={() => onDeleteSubtask(task, st.id)}
|
|
className="opacity-0 group-hover:opacity-100 text-faint hover:text-status-err p-0.5 transition-opacity"
|
|
>
|
|
<Trash2 className="w-3 h-3" />
|
|
</button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{/* Inline add subtask form */}
|
|
<form onSubmit={handleAddSubtaskSubmit} className="flex items-center gap-1.5">
|
|
<input
|
|
type="text"
|
|
value={newSubtaskTitle}
|
|
onChange={(e) => setNewSubtaskTitle(e.target.value)}
|
|
placeholder="Neue Unteraufgabe hinzufügen…"
|
|
className="flex-1 h-7 px-2 text-xs input-field"
|
|
/>
|
|
<button
|
|
type="submit"
|
|
disabled={!newSubtaskTitle.trim()}
|
|
className="btn-action h-7 px-2.5 text-xs disabled:opacity-40"
|
|
>
|
|
<Plus className="w-3 h-3" />
|
|
<span>Hinzufügen</span>
|
|
</button>
|
|
</form>
|
|
</div>
|
|
|
|
{/* Notes Section */}
|
|
<div className="space-y-2 pt-1 border-t border-border">
|
|
<div className="flex items-center justify-between">
|
|
<label className="block text-xs font-semibold text-main flex items-center gap-1.5">
|
|
<FileText className="w-3.5 h-3.5 text-accent" />
|
|
<span>Detailnotizen:</span>
|
|
</label>
|
|
{!isEditingNotes && task.notes && (
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
setNotesDraft(task.notes || '');
|
|
setIsEditingNotes(true);
|
|
}}
|
|
className="text-[11px] text-accent hover:underline"
|
|
>
|
|
Bearbeiten
|
|
</button>
|
|
)}
|
|
</div>
|
|
|
|
{isEditingNotes ? (
|
|
<div className="space-y-2">
|
|
<textarea
|
|
value={notesDraft}
|
|
onChange={(e) => setNotesDraft(e.target.value)}
|
|
placeholder="Detailnotizen eingeben… (Markdown unterstützt)"
|
|
rows={6}
|
|
autoFocus
|
|
className="w-full input-field p-2.5 text-xs font-mono leading-relaxed resize-y min-h-[100px]"
|
|
/>
|
|
<div className="flex justify-end gap-1.5">
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
setIsEditingNotes(false);
|
|
setNotesDraft(task.notes || '');
|
|
}}
|
|
className="btn-action h-7 px-2.5 text-xs"
|
|
>
|
|
Abbrechen
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={handleNotesSubmit}
|
|
className="btn-primary h-7 px-3 text-xs"
|
|
>
|
|
Notiz speichern
|
|
</button>
|
|
</div>
|
|
</div>
|
|
) : task.notes ? (
|
|
<div
|
|
onClick={() => {
|
|
setNotesDraft(task.notes || '');
|
|
setIsEditingNotes(true);
|
|
}}
|
|
className="p-3 bg-canvas border border-border rounded-card text-xs text-muted leading-relaxed whitespace-pre-wrap cursor-pointer hover:border-strong transition-colors"
|
|
title="Klicken zum Bearbeiten"
|
|
>
|
|
{task.notes}
|
|
</div>
|
|
) : (
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
setNotesDraft('');
|
|
setIsEditingNotes(true);
|
|
}}
|
|
className="w-full py-2.5 px-3 border border-dashed border-border rounded-card text-xs text-muted hover:text-main hover:bg-panel transition-colors text-left flex items-center gap-1.5"
|
|
>
|
|
<Plus className="w-3.5 h-3.5 text-accent" />
|
|
<span>Detailnotiz hinzufügen…</span>
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Modal Footer */}
|
|
<div className="flex items-center justify-between px-4 py-2.5 border-t border-border bg-panel">
|
|
<div className="text-[11px] text-faint">
|
|
Erstellt: {new Date(task.createdAt).toLocaleDateString('de-DE')}
|
|
</div>
|
|
<button
|
|
type="button"
|
|
onClick={onClose}
|
|
className="btn-action h-7 px-3 text-xs"
|
|
>
|
|
Fertig
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|