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
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 58s
This commit is contained in:
1 parent
cb0999325f
commit
212cb32bb0
8 files changed
+943
-111
No files matched your search
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user