Add project selector in task detail, archive completed tasks
This commit is contained in:
2
dist/index.html
vendored
2
dist/index.html
vendored
@@ -6,7 +6,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="description" content="Todo App - Task management made simple" />
|
<meta name="description" content="Todo App - Task management made simple" />
|
||||||
<title>Todo App</title>
|
<title>Todo App</title>
|
||||||
<script type="module" crossorigin src="/assets/index-vdiwxbr4.js"></script>
|
<script type="module" crossorigin src="/assets/index-Ce_4Zv7a.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-Cuyvk5mt.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-Cuyvk5mt.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
66
src/components/CompletedSection.tsx
Normal file
66
src/components/CompletedSection.tsx
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { ChevronRight, ChevronDown, Check } from 'lucide-react';
|
||||||
|
import type { Task } from '@/types';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
import { useTasksStore } from '@/stores/tasks';
|
||||||
|
|
||||||
|
interface CompletedSectionProps {
|
||||||
|
tasks: Task[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CompletedSection({ tasks }: CompletedSectionProps) {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const { toggleComplete, setSelectedTask } = useTasksStore();
|
||||||
|
|
||||||
|
if (tasks.length === 0) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mt-6 border-t border-gray-100 pt-4">
|
||||||
|
<button
|
||||||
|
onClick={() => setIsOpen(!isOpen)}
|
||||||
|
className="flex items-center gap-2 text-sm font-medium text-gray-500 hover:text-gray-700 transition-colors"
|
||||||
|
>
|
||||||
|
{isOpen ? (
|
||||||
|
<ChevronDown className="w-4 h-4" />
|
||||||
|
) : (
|
||||||
|
<ChevronRight className="w-4 h-4" />
|
||||||
|
)}
|
||||||
|
Completed ({tasks.length})
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{isOpen && (
|
||||||
|
<div className="mt-2 space-y-1">
|
||||||
|
{tasks.map((task) => (
|
||||||
|
<div
|
||||||
|
key={task.id}
|
||||||
|
className="flex items-center gap-3 px-3 py-2 rounded-lg hover:bg-gray-50 cursor-pointer group opacity-60"
|
||||||
|
onClick={() => setSelectedTask(task)}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
toggleComplete(task.id);
|
||||||
|
}}
|
||||||
|
className="flex-shrink-0 w-5 h-5 rounded-full bg-gray-400 border-2 border-gray-400 flex items-center justify-center"
|
||||||
|
>
|
||||||
|
<Check className="w-3 h-3 text-white" />
|
||||||
|
</button>
|
||||||
|
<span className="text-sm text-gray-500 line-through">
|
||||||
|
{task.title}
|
||||||
|
</span>
|
||||||
|
{task.project && (
|
||||||
|
<span className="ml-auto flex items-center gap-1.5 text-xs text-gray-400">
|
||||||
|
<span
|
||||||
|
className="w-2 h-2 rounded"
|
||||||
|
style={{ backgroundColor: task.project.color }}
|
||||||
|
/>
|
||||||
|
{task.project.name}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ export function TaskDetail({ task, onClose }: TaskDetailProps) {
|
|||||||
const [dueDate, setDueDate] = useState(task.dueDate || '');
|
const [dueDate, setDueDate] = useState(task.dueDate || '');
|
||||||
const [priority, setPriority] = useState<Priority>(task.priority);
|
const [priority, setPriority] = useState<Priority>(task.priority);
|
||||||
const [assigneeId, setAssigneeId] = useState(task.assigneeId || '');
|
const [assigneeId, setAssigneeId] = useState(task.assigneeId || '');
|
||||||
|
const [projectId, setProjectId] = useState(task.projectId || '');
|
||||||
const [comments, setComments] = useState<Comment[]>([]);
|
const [comments, setComments] = useState<Comment[]>([]);
|
||||||
const [isDeleting, setIsDeleting] = useState(false);
|
const [isDeleting, setIsDeleting] = useState(false);
|
||||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
||||||
@@ -32,6 +33,7 @@ export function TaskDetail({ task, onClose }: TaskDetailProps) {
|
|||||||
setDueDate(task.dueDate || '');
|
setDueDate(task.dueDate || '');
|
||||||
setPriority(task.priority);
|
setPriority(task.priority);
|
||||||
setAssigneeId(task.assigneeId || '');
|
setAssigneeId(task.assigneeId || '');
|
||||||
|
setProjectId(task.projectId || '');
|
||||||
}, [task]);
|
}, [task]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -72,6 +74,11 @@ export function TaskDetail({ task, onClose }: TaskDetailProps) {
|
|||||||
updateTask(task.id, { assigneeId: value || undefined });
|
updateTask(task.id, { assigneeId: value || undefined });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleProjectChange = (value: string) => {
|
||||||
|
setProjectId(value);
|
||||||
|
updateTask(task.id, { projectId: value || undefined });
|
||||||
|
};
|
||||||
|
|
||||||
const handleDelete = async () => {
|
const handleDelete = async () => {
|
||||||
setIsDeleting(true);
|
setIsDeleting(true);
|
||||||
try {
|
try {
|
||||||
@@ -85,6 +92,10 @@ export function TaskDetail({ task, onClose }: TaskDetailProps) {
|
|||||||
|
|
||||||
const handleComplete = async () => {
|
const handleComplete = async () => {
|
||||||
await toggleComplete(task.id);
|
await toggleComplete(task.id);
|
||||||
|
if (!task.isCompleted) {
|
||||||
|
// Task was just completed — close detail panel after brief delay
|
||||||
|
setTimeout(() => onClose(), 500);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Close on Escape
|
// Close on Escape
|
||||||
@@ -176,14 +187,26 @@ export function TaskDetail({ task, onClose }: TaskDetailProps) {
|
|||||||
<span className="text-sm text-gray-500 w-20">Project</span>
|
<span className="text-sm text-gray-500 w-20">Project</span>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{taskProject && (
|
{taskProject && (
|
||||||
<>
|
<span
|
||||||
<span
|
className="w-3 h-3 rounded flex-shrink-0"
|
||||||
className="w-3 h-3 rounded"
|
style={{ backgroundColor: taskProject.color }}
|
||||||
style={{ backgroundColor: taskProject.color }}
|
/>
|
||||||
/>
|
|
||||||
<span className="text-sm text-gray-900">{taskProject.name}</span>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
|
<select
|
||||||
|
value={projectId}
|
||||||
|
onChange={(e) => handleProjectChange(e.target.value)}
|
||||||
|
className={cn(
|
||||||
|
'text-sm border border-gray-200 rounded px-2 py-1 outline-none focus:border-blue-400 bg-white cursor-pointer',
|
||||||
|
projectId ? 'text-gray-900' : 'text-gray-400'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<option value="">No project</option>
|
||||||
|
{projects.map((p) => (
|
||||||
|
<option key={p.id} value={p.id}>
|
||||||
|
{p.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -3,15 +3,17 @@ import { Inbox as InboxIcon } from 'lucide-react';
|
|||||||
import { useTasksStore } from '@/stores/tasks';
|
import { useTasksStore } from '@/stores/tasks';
|
||||||
import { TaskItem } from '@/components/TaskItem';
|
import { TaskItem } from '@/components/TaskItem';
|
||||||
import { AddTask } from '@/components/AddTask';
|
import { AddTask } from '@/components/AddTask';
|
||||||
|
import { CompletedSection } from '@/components/CompletedSection';
|
||||||
|
|
||||||
export function InboxPage() {
|
export function InboxPage() {
|
||||||
const { tasks, projects, isLoading, fetchTasks, setSelectedTask } = useTasksStore();
|
const { tasks, completedTasks, projects, isLoading, fetchTasks, fetchCompletedTasks, setSelectedTask } = useTasksStore();
|
||||||
|
|
||||||
const inbox = projects.find(p => p.isInbox);
|
const inbox = projects.find(p => p.isInbox);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (inbox) {
|
if (inbox) {
|
||||||
fetchTasks({ projectId: inbox.id, completed: false });
|
fetchTasks({ projectId: inbox.id, completed: false });
|
||||||
|
fetchCompletedTasks({ projectId: inbox.id });
|
||||||
}
|
}
|
||||||
}, [inbox?.id]);
|
}, [inbox?.id]);
|
||||||
|
|
||||||
@@ -50,6 +52,9 @@ export function InboxPage() {
|
|||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<AddTask projectId={inbox?.id} />
|
<AddTask projectId={inbox?.id} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Completed tasks */}
|
||||||
|
<CompletedSection tasks={completedTasks} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { cn } from '@/lib/utils';
|
|||||||
import { useTasksStore } from '@/stores/tasks';
|
import { useTasksStore } from '@/stores/tasks';
|
||||||
import { TaskItem } from '@/components/TaskItem';
|
import { TaskItem } from '@/components/TaskItem';
|
||||||
import { AddTask } from '@/components/AddTask';
|
import { AddTask } from '@/components/AddTask';
|
||||||
|
import { CompletedSection } from '@/components/CompletedSection';
|
||||||
import { BoardView } from '@/pages/Board';
|
import { BoardView } from '@/pages/Board';
|
||||||
import { api } from '@/lib/api';
|
import { api } from '@/lib/api';
|
||||||
import type { Project as ProjectType, Section } from '@/types';
|
import type { Project as ProjectType, Section } from '@/types';
|
||||||
@@ -13,7 +14,7 @@ export function ProjectPage() {
|
|||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { tasks, isLoading, fetchTasks, setSelectedTask } = useTasksStore();
|
const { tasks, completedTasks, isLoading, fetchTasks, fetchCompletedTasks, setSelectedTask } = useTasksStore();
|
||||||
const [project, setProject] = useState<ProjectType | null>(null);
|
const [project, setProject] = useState<ProjectType | null>(null);
|
||||||
const [sections, setSections] = useState<Section[]>([]);
|
const [sections, setSections] = useState<Section[]>([]);
|
||||||
|
|
||||||
@@ -22,6 +23,7 @@ export function ProjectPage() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
fetchTasks({ projectId: id, completed: false });
|
fetchTasks({ projectId: id, completed: false });
|
||||||
|
fetchCompletedTasks({ projectId: id });
|
||||||
api.getProject(id).then((p) => {
|
api.getProject(id).then((p) => {
|
||||||
setProject(p);
|
setProject(p);
|
||||||
setSections(p.sections || []);
|
setSections(p.sections || []);
|
||||||
@@ -138,6 +140,9 @@ export function ProjectPage() {
|
|||||||
<AddTask projectId={id} sectionId={section.id} />
|
<AddTask projectId={id} sectionId={section.id} />
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
{/* Completed tasks */}
|
||||||
|
<CompletedSection tasks={completedTasks} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ import { Calendar } from 'lucide-react';
|
|||||||
import { useTasksStore } from '@/stores/tasks';
|
import { useTasksStore } from '@/stores/tasks';
|
||||||
import { TaskItem } from '@/components/TaskItem';
|
import { TaskItem } from '@/components/TaskItem';
|
||||||
import { AddTask } from '@/components/AddTask';
|
import { AddTask } from '@/components/AddTask';
|
||||||
|
import { CompletedSection } from '@/components/CompletedSection';
|
||||||
|
|
||||||
export function TodayPage() {
|
export function TodayPage() {
|
||||||
const { tasks, isLoading, fetchTasks, setSelectedTask } = useTasksStore();
|
const { tasks, completedTasks, isLoading, fetchTasks, fetchCompletedTasks, setSelectedTask } = useTasksStore();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchTasks({ today: true, completed: false });
|
fetchTasks({ today: true, completed: false });
|
||||||
|
fetchCompletedTasks({ today: true });
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
@@ -104,6 +106,9 @@ export function TodayPage() {
|
|||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<AddTask />
|
<AddTask />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Completed tasks */}
|
||||||
|
<CompletedSection tasks={completedTasks} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,13 +3,15 @@ import { CalendarDays } from 'lucide-react';
|
|||||||
import { useTasksStore } from '@/stores/tasks';
|
import { useTasksStore } from '@/stores/tasks';
|
||||||
import { TaskItem } from '@/components/TaskItem';
|
import { TaskItem } from '@/components/TaskItem';
|
||||||
import { AddTask } from '@/components/AddTask';
|
import { AddTask } from '@/components/AddTask';
|
||||||
|
import { CompletedSection } from '@/components/CompletedSection';
|
||||||
import { format, addDays, startOfDay, isSameDay } from 'date-fns';
|
import { format, addDays, startOfDay, isSameDay } from 'date-fns';
|
||||||
|
|
||||||
export function UpcomingPage() {
|
export function UpcomingPage() {
|
||||||
const { tasks, isLoading, fetchTasks, setSelectedTask } = useTasksStore();
|
const { tasks, completedTasks, isLoading, fetchTasks, fetchCompletedTasks, setSelectedTask } = useTasksStore();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchTasks({ upcoming: true, completed: false });
|
fetchTasks({ upcoming: true, completed: false });
|
||||||
|
fetchCompletedTasks({ upcoming: true });
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Group tasks by date
|
// Group tasks by date
|
||||||
@@ -112,6 +114,9 @@ export function UpcomingPage() {
|
|||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
<AddTask />
|
<AddTask />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Completed tasks */}
|
||||||
|
<CompletedSection tasks={completedTasks} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ describe('useTasksStore', () => {
|
|||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
useTasksStore.setState({
|
useTasksStore.setState({
|
||||||
tasks: [],
|
tasks: [],
|
||||||
|
completedTasks: [],
|
||||||
projects: [],
|
projects: [],
|
||||||
labels: [],
|
labels: [],
|
||||||
users: [],
|
users: [],
|
||||||
@@ -75,6 +76,7 @@ describe('useTasksStore', () => {
|
|||||||
it('has correct initial state', () => {
|
it('has correct initial state', () => {
|
||||||
const state = useTasksStore.getState();
|
const state = useTasksStore.getState();
|
||||||
expect(state.tasks).toEqual([]);
|
expect(state.tasks).toEqual([]);
|
||||||
|
expect(state.completedTasks).toEqual([]);
|
||||||
expect(state.projects).toEqual([]);
|
expect(state.projects).toEqual([]);
|
||||||
expect(state.labels).toEqual([]);
|
expect(state.labels).toEqual([]);
|
||||||
expect(state.users).toEqual([]);
|
expect(state.users).toEqual([]);
|
||||||
@@ -157,14 +159,37 @@ describe('useTasksStore', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('toggleComplete', () => {
|
describe('toggleComplete', () => {
|
||||||
it('flips isCompleted', async () => {
|
it('moves task to completedTasks when completing', async () => {
|
||||||
const task = makeTask({ id: '1', isCompleted: false });
|
const task = makeTask({ id: '1', isCompleted: false });
|
||||||
useTasksStore.setState({ tasks: [task] });
|
useTasksStore.setState({ tasks: [task], completedTasks: [] });
|
||||||
mockApi.updateTask.mockResolvedValueOnce({ ...task, isCompleted: true, completedAt: '2025-06-15' });
|
mockApi.updateTask.mockResolvedValueOnce({ ...task, isCompleted: true, completedAt: '2025-06-15' });
|
||||||
|
|
||||||
await useTasksStore.getState().toggleComplete('1');
|
await useTasksStore.getState().toggleComplete('1');
|
||||||
|
|
||||||
expect(useTasksStore.getState().tasks[0].isCompleted).toBe(true);
|
expect(useTasksStore.getState().tasks).toHaveLength(0);
|
||||||
|
expect(useTasksStore.getState().completedTasks[0].isCompleted).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('moves task back to tasks when uncompleting', async () => {
|
||||||
|
const task = makeTask({ id: '1', isCompleted: true, completedAt: '2025-06-15' });
|
||||||
|
useTasksStore.setState({ tasks: [], completedTasks: [task] });
|
||||||
|
mockApi.updateTask.mockResolvedValueOnce({ ...task, isCompleted: false, completedAt: undefined });
|
||||||
|
|
||||||
|
await useTasksStore.getState().toggleComplete('1');
|
||||||
|
|
||||||
|
expect(useTasksStore.getState().completedTasks).toHaveLength(0);
|
||||||
|
expect(useTasksStore.getState().tasks[0].isCompleted).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('fetchCompletedTasks', () => {
|
||||||
|
it('populates completedTasks', async () => {
|
||||||
|
const tasks = [makeTask({ id: '1', isCompleted: true })];
|
||||||
|
mockApi.getTasks.mockResolvedValueOnce(tasks);
|
||||||
|
|
||||||
|
await useTasksStore.getState().fetchCompletedTasks();
|
||||||
|
|
||||||
|
expect(useTasksStore.getState().completedTasks).toEqual(tasks);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { api } from '@/lib/api';
|
|||||||
|
|
||||||
interface TasksState {
|
interface TasksState {
|
||||||
tasks: Task[];
|
tasks: Task[];
|
||||||
|
completedTasks: Task[];
|
||||||
projects: Project[];
|
projects: Project[];
|
||||||
labels: Label[];
|
labels: Label[];
|
||||||
users: User[];
|
users: User[];
|
||||||
@@ -16,6 +17,7 @@ interface TasksState {
|
|||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
fetchTasks: (params?: Parameters<typeof api.getTasks>[0]) => Promise<void>;
|
fetchTasks: (params?: Parameters<typeof api.getTasks>[0]) => Promise<void>;
|
||||||
|
fetchCompletedTasks: (params?: Parameters<typeof api.getTasks>[0]) => Promise<void>;
|
||||||
fetchProjects: () => Promise<void>;
|
fetchProjects: () => Promise<void>;
|
||||||
fetchLabels: () => Promise<void>;
|
fetchLabels: () => Promise<void>;
|
||||||
fetchUsers: () => Promise<void>;
|
fetchUsers: () => Promise<void>;
|
||||||
@@ -33,6 +35,7 @@ interface TasksState {
|
|||||||
|
|
||||||
export const useTasksStore = create<TasksState>((set, get) => ({
|
export const useTasksStore = create<TasksState>((set, get) => ({
|
||||||
tasks: [],
|
tasks: [],
|
||||||
|
completedTasks: [],
|
||||||
projects: [],
|
projects: [],
|
||||||
labels: [],
|
labels: [],
|
||||||
users: [],
|
users: [],
|
||||||
@@ -52,6 +55,15 @@ export const useTasksStore = create<TasksState>((set, get) => ({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
fetchCompletedTasks: async (params) => {
|
||||||
|
try {
|
||||||
|
const completedTasks = await api.getTasks({ ...params, completed: true });
|
||||||
|
set({ completedTasks });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch completed tasks:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
fetchProjects: async () => {
|
fetchProjects: async () => {
|
||||||
try {
|
try {
|
||||||
const projects = await api.getProjects();
|
const projects = await api.getProjects();
|
||||||
@@ -110,15 +122,25 @@ export const useTasksStore = create<TasksState>((set, get) => ({
|
|||||||
},
|
},
|
||||||
|
|
||||||
toggleComplete: async (id) => {
|
toggleComplete: async (id) => {
|
||||||
const task = get().tasks.find((t) => t.id === id);
|
const task = get().tasks.find((t) => t.id === id) || get().completedTasks.find((t) => t.id === id);
|
||||||
if (!task) return;
|
if (!task) return;
|
||||||
|
|
||||||
const updated = await api.updateTask(id, { isCompleted: !task.isCompleted });
|
const updated = await api.updateTask(id, { isCompleted: !task.isCompleted });
|
||||||
set((state) => ({
|
const updatedTask = { ...task, isCompleted: !task.isCompleted, completedAt: updated.completedAt };
|
||||||
tasks: state.tasks.map((t) =>
|
|
||||||
t.id === id ? { ...t, isCompleted: !task.isCompleted, completedAt: updated.completedAt } : t
|
if (updatedTask.isCompleted) {
|
||||||
),
|
// Move from tasks to completedTasks
|
||||||
}));
|
set((state) => ({
|
||||||
|
tasks: state.tasks.filter((t) => t.id !== id),
|
||||||
|
completedTasks: [updatedTask, ...state.completedTasks],
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
// Move from completedTasks to tasks
|
||||||
|
set((state) => ({
|
||||||
|
tasks: [updatedTask, ...state.tasks],
|
||||||
|
completedTasks: state.completedTasks.filter((t) => t.id !== id),
|
||||||
|
}));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setSelectedTask: (task) => set({ selectedTask: task }),
|
setSelectedTask: (task) => set({ selectedTask: task }),
|
||||||
|
|||||||
Reference in New Issue
Block a user