feat(mobile): add context-sensitive MindSift logo menu, slide-over file drawer, and larger touch targets
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 58s

This commit is contained in:
sandrodach committed 2026-10-25 21:08:48 +01:00
1 parent cb0999325f
commit 212cb32bb0
8 files changed
+943 -111

No files matched your search

+187 -20
View File
@@ -30,6 +30,7 @@ import { UnifiedShareModal } from '../../components/UnifiedShareModal';
import { TaskDetailModal } from './TaskDetailModal';
import { ManageColumnsModal, DEFAULT_KANBAN_COLUMNS } from './ManageColumnsModal';
import { UserAutocomplete } from '../UserAutocomplete';
import { useMobileActions } from '../../context/MobileActionContext';
const getInitials = (name?: string): string => {
if (!name) return '?';
@@ -71,6 +72,8 @@ export const TasksView: React.FC<TasksViewProps> = ({
// Quick Task Add state
const [quickTitle, setQuickTitle] = useState('');
const [quickDueDate, setQuickDueDate] = useState('');
const quickInputRef = useRef<HTMLInputElement | null>(null);
const { setGroups } = useMobileActions();
// New TaskList modal state
const [showNewListModal, setShowNewListModal] = useState(false);
@@ -811,6 +814,141 @@ export const TasksView: React.FC<TasksViewProps> = ({
}
};
useEffect(() => {
const actionItems: any[] = [
{
id: 'new-task',
label: 'Neue Aufgabe erstellen',
icon: <Plus className="w-4 h-4" />,
variant: 'accent',
onClick: () => {
window.scrollTo({ top: 0, behavior: 'smooth' });
setTimeout(() => quickInputRef.current?.focus(), 100);
},
},
{
id: 'new-task-list',
label: 'Neue Aufgabenliste anlegen',
icon: <Folder className="w-4 h-4" />,
onClick: () => setShowNewListModal(true),
},
];
if (currentSelectedList) {
actionItems.push({
id: 'share-task-list',
label: `Liste „${currentSelectedList.title}“ teilen`,
icon: <Users className="w-4 h-4" />,
onClick: () => handleOpenShareModal(currentSelectedList),
});
actionItems.push({
id: 'delete-task-list',
label: `Liste „${currentSelectedList.title}“ löschen`,
icon: <Trash2 className="w-4 h-4" />,
variant: 'danger',
onClick: () => handleDeleteTaskList(currentSelectedList.id),
});
}
const viewItems: any[] = [
{
id: 'filter-open',
label: 'Offene Aufgaben (Liste)',
icon: <LayoutList className="w-4 h-4" />,
badge: openTasks.length,
active: viewMode === 'list' && filter === 'open',
onClick: () => {
setViewMode('list');
setFilter('open');
},
},
{
id: 'filter-done',
label: 'Erledigte Aufgaben',
icon: <Check className="w-4 h-4" />,
badge: doneTasks.length,
active: viewMode === 'list' && filter === 'done',
onClick: () => {
setViewMode('list');
setFilter('done');
},
},
{
id: 'filter-discarded',
label: 'Papierkorb',
icon: <Trash2 className="w-4 h-4" />,
badge: safeDiscarded.length,
active: viewMode === 'list' && filter === 'discarded',
onClick: () => {
setViewMode('list');
setFilter('discarded');
},
},
{
id: 'view-kanban',
label: 'Kanban-Board Ansicht',
icon: <Columns className="w-4 h-4" />,
badge: kanbanTasks.length,
active: viewMode === 'kanban',
onClick: () => setViewMode('kanban'),
},
];
if (viewMode === 'kanban') {
viewItems.push({
id: 'manage-columns',
label: 'Kanban-Spalten verwalten',
icon: <Settings className="w-4 h-4" />,
onClick: () => setShowManageColumnsModal(true),
});
}
const listItems: any[] = [
{
id: 'list-all',
label: 'Alle Aufgaben',
icon: <Layers className="w-4 h-4" />,
active: selectedListId === 'all',
onClick: () => setSelectedListId('all'),
},
...safeTaskLists.map((tl) => ({
id: `list-${tl.id}`,
label: `${tl.icon || '📋'} ${tl.title}`,
badge: tl.tasksCount || undefined,
active: selectedListId === tl.id,
onClick: () => setSelectedListId(tl.id),
})),
];
setGroups([
{
id: 'tasks-actions',
title: 'Aktionen',
items: actionItems,
},
{
id: 'tasks-views',
title: 'Ansicht & Status',
items: viewItems,
},
{
id: 'tasks-lists',
title: 'Aufgabenlisten',
items: listItems,
},
]);
}, [
viewMode,
filter,
selectedListId,
openTasks.length,
doneTasks.length,
safeDiscarded.length,
kanbanTasks.length,
safeTaskLists,
currentSelectedList,
]);
const renderTaskProgressBar = (task: Thought) => {
if (!task.subtasks || task.subtasks.length === 0) return null;
const total = task.subtasks.length;
@@ -868,7 +1006,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
setDetailTaskId(task.id);
setShowDetailModal(true);
}}
className={`px-3 py-2.5 flex items-center justify-between gap-3 transition-colors hover:bg-panel-hover group border-b border-border last:border-b-0 relative cursor-pointer cursor-grab active:cursor-grabbing select-none ${
className={`px-3.5 py-3.5 sm:px-3 sm:py-2.5 flex items-center justify-between gap-3 transition-colors hover:bg-panel-hover group border-b border-border last:border-b-0 relative cursor-pointer cursor-grab active:cursor-grabbing select-none ${
isCompleted ? 'opacity-65' : ''
} ${isDragging ? 'opacity-30 border border-dashed border-accent/40' : ''}`}
>
@@ -880,8 +1018,8 @@ export const TasksView: React.FC<TasksViewProps> = ({
<div className="absolute bottom-0 left-0 right-0 h-0.5 bg-accent z-20 pointer-events-none rounded-full shadow-[0_0_8px_rgba(var(--accent-rgb),0.6)]" />
)}
<div className="flex items-center gap-2.5 min-w-0 flex-1">
{/* Checkbox */}
<div className="flex items-center gap-3 sm:gap-2.5 min-w-0 flex-1">
{/* Checkbox (larger touch target on mobile) */}
<button
type="button"
draggable={false}
@@ -890,14 +1028,14 @@ export const TasksView: React.FC<TasksViewProps> = ({
e.stopPropagation();
handleToggleDone(task);
}}
className={`w-4 h-4 checkbox-square flex items-center justify-center flex-shrink-0 transition-colors border ${
className={`w-5 h-5 sm:w-4 sm:h-4 checkbox-square flex items-center justify-center flex-shrink-0 transition-colors border ${
isCompleted
? 'bg-status-ok border-status-ok text-on-accent'
: 'border-strong hover:border-accent text-transparent'
}`}
title={isCompleted ? 'Als offen markieren' : 'Als erledigt abhaken'}
>
<Check className="w-3 h-3 stroke-[3]" />
<Check className="w-3.5 h-3.5 sm:w-3 sm:h-3 stroke-[3]" />
</button>
{/* Clickable Title & Metadata */}
@@ -1006,8 +1144,8 @@ export const TasksView: React.FC<TasksViewProps> = ({
</div>
</div>
{/* Action Controls */}
<div className="flex items-center gap-1 flex-shrink-0" onMouseDown={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}>
{/* Action Controls (Desktop only - on mobile, tapping the task opens Detail Modal) */}
<div className="hidden sm:flex items-center gap-1 flex-shrink-0" onMouseDown={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}>
<button
type="button"
draggable={false}
@@ -1045,8 +1183,36 @@ export const TasksView: React.FC<TasksViewProps> = ({
return (
<div className="w-full space-y-3">
{/* Top Header: Task Lists Tabs & View Mode Switcher */}
<div className="flex flex-wrap items-center justify-between gap-2 border-b border-border pb-2">
{/* Mobile Compact Context Bar (replaces cluttered button rows; options live in MindSift Logo Menu) */}
<div className="flex sm:hidden items-center justify-between gap-2 px-3 py-2 rounded-btn bg-panel border border-border">
<div className="flex items-center gap-2 min-w-0">
<span className="text-sm font-semibold text-main truncate">
{currentSelectedList ? `${currentSelectedList.icon} ${currentSelectedList.title}` : '📋 Alle Aufgaben'}
</span>
<span className="badge-neutral text-[11px] flex-shrink-0">
{viewMode === 'kanban'
? 'Kanban'
: filter === 'open'
? `Offen (${openTasks.length})`
: filter === 'done'
? `Erledigt (${doneTasks.length})`
: `Papierkorb (${safeDiscarded.length})`}
</span>
</div>
{currentSelectedList && (
<button
type="button"
onClick={() => handleOpenShareModal(currentSelectedList)}
className="btn-icon min-h-[36px] min-w-[36px] flex items-center justify-center flex-shrink-0"
title="Aufgabenliste teilen"
>
<Users className="w-4 h-4 text-accent" />
</button>
)}
</div>
{/* Top Header: Task Lists Tabs & View Mode Switcher (Desktop) */}
<div className="hidden sm:flex flex-wrap items-center justify-between gap-2 border-b border-border pb-2">
{/* Task Lists selector tabs */}
<div className="flex flex-wrap items-center gap-1">
<button
@@ -1200,32 +1366,33 @@ export const TasksView: React.FC<TasksViewProps> = ({
{/* Quick Add Task Input */}
<form
onSubmit={handleQuickAddTask}
className="flex items-center gap-1.5 panel-surface p-1.5"
className="flex items-center gap-1.5 panel-surface p-2 sm:p-1.5"
>
<Plus className="w-4 h-4 text-accent ml-1 flex-shrink-0" />
<input
ref={quickInputRef}
type="text"
value={quickTitle}
onChange={(e) => setQuickTitle(e.target.value)}
placeholder={
currentSelectedList
? `Aufgabe zu "${currentSelectedList.title}" hinzufügen… (Enter)`
: 'Neue Aufgabe anlegen… (Enter)'
? `Aufgabe zu "${currentSelectedList.title}" hinzufügen…`
: 'Neue Aufgabe anlegen…'
}
className="flex-1 bg-transparent px-2 py-1 text-sm text-main placeholder-faint focus:outline-none"
className="flex-1 bg-transparent px-2 py-1.5 sm:py-1 text-sm text-main placeholder-faint focus:outline-none min-w-0"
/>
<div className="flex items-center gap-1">
<div className="flex items-center gap-1 flex-shrink-0">
<input
type="date"
value={quickDueDate}
onChange={(e) => setQuickDueDate(e.target.value)}
className="h-7 px-2 input-field text-xs"
className="hidden sm:block h-7 px-2 input-field text-xs"
title="Fälligkeitsdatum"
/>
<button
type="submit"
disabled={!quickTitle.trim()}
className="btn-primary h-7 px-3 text-xs disabled:opacity-40"
className="btn-primary h-9 sm:h-7 px-3.5 sm:px-3 text-xs disabled:opacity-40"
>
Hinzufügen
</button>
@@ -1559,7 +1726,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
>
{task.content}
</p>
<div className="flex items-center gap-1 flex-shrink-0" onClick={(e) => e.stopPropagation()}>
<div className="hidden sm:flex items-center gap-1 flex-shrink-0" onClick={(e) => e.stopPropagation()}>
<button
onClick={() => handleConvertToDoc(task)}
title="Als Notiz vertiefen"
@@ -1689,9 +1856,9 @@ export const TasksView: React.FC<TasksViewProps> = ({
) : (
/* STANDARD LIST VIEW (CLEAN & UNCLUTTERED) */
<div className="space-y-2">
{/* Subfilter tabs for List View */}
{/* Subfilter tabs for List View (Desktop; on mobile controlled via MindSift Logo Menu) */}
<div className="flex items-center justify-between gap-2">
<div className="flex items-center border border-border rounded-btn bg-canvas overflow-hidden">
<div className="hidden sm:flex items-center border border-border rounded-btn bg-canvas overflow-hidden">
<button
onClick={() => setFilter('open')}
className={`h-8 px-3.5 text-xs font-medium border-r border-border transition-colors ${
@@ -1721,7 +1888,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
</div>
{filter === 'discarded' && safeDiscarded.length > 0 && (
<button onClick={handlePurgeAllDiscarded} className="btn-action">
<button onClick={handlePurgeAllDiscarded} className="btn-action min-h-[38px] sm:min-h-0">
<Trash2 className="w-3.5 h-3.5" />
Papierkorb leeren
</button>