Keep completed todos at the end (#1430)
Co-authored-by: Konstantin Zolin <zolin_ka@vk.com>
This commit is contained in:
committed by
GitHub
co-authored by
Konstantin Zolin
parent
db2464d94d
commit
4c34031f8d
@@ -101,6 +101,19 @@ const createTodoId = (): string => {
|
|||||||
return `todo_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
return `todo_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const sortTodosWithCompletedLast = (items: OpenChamberProjectTodoItem[]): OpenChamberProjectTodoItem[] => [
|
||||||
|
...items.filter((todo) => !todo.completed),
|
||||||
|
...items.filter((todo) => todo.completed),
|
||||||
|
];
|
||||||
|
|
||||||
|
const insertTodoBeforeCompleted = (items: OpenChamberProjectTodoItem[], item: OpenChamberProjectTodoItem): OpenChamberProjectTodoItem[] => {
|
||||||
|
const firstCompletedIndex = items.findIndex((todo) => todo.completed);
|
||||||
|
if (firstCompletedIndex === -1) {
|
||||||
|
return [...items, item];
|
||||||
|
}
|
||||||
|
return [...items.slice(0, firstCompletedIndex), item, ...items.slice(firstCompletedIndex)];
|
||||||
|
};
|
||||||
|
|
||||||
type SortableTodoHandleProps = {
|
type SortableTodoHandleProps = {
|
||||||
attributes: ReturnType<typeof useSortable>['attributes'];
|
attributes: ReturnType<typeof useSortable>['attributes'];
|
||||||
listeners: ReturnType<typeof useSortable>['listeners'];
|
listeners: ReturnType<typeof useSortable>['listeners'];
|
||||||
@@ -216,7 +229,7 @@ export const ProjectNotesTodoPanel: React.FC<ProjectNotesTodoPanelProps> = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setNotes(data.notes);
|
setNotes(data.notes);
|
||||||
setTodos(data.todos);
|
setTodos(sortTodosWithCompletedLast(data.todos));
|
||||||
setPlans(nextPlans);
|
setPlans(nextPlans);
|
||||||
lastSavedNotesRef.current = data.notes;
|
lastSavedNotesRef.current = data.notes;
|
||||||
notesHydratedRef.current = true;
|
notesHydratedRef.current = true;
|
||||||
@@ -341,15 +354,12 @@ export const ProjectNotesTodoPanel: React.FC<ProjectNotesTodoPanelProps> = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextTodos = [
|
const nextTodos = insertTodoBeforeCompleted(todos, {
|
||||||
...todos,
|
id: createTodoId(),
|
||||||
{
|
text: trimmed.slice(0, OPENCHAMBER_PROJECT_TODO_TEXT_MAX_LENGTH),
|
||||||
id: createTodoId(),
|
completed: false,
|
||||||
text: trimmed.slice(0, OPENCHAMBER_PROJECT_TODO_TEXT_MAX_LENGTH),
|
createdAt: Date.now(),
|
||||||
completed: false,
|
});
|
||||||
createdAt: Date.now(),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
setTodos(nextTodos);
|
setTodos(nextTodos);
|
||||||
setNewTodoText('');
|
setNewTodoText('');
|
||||||
void persistProjectData(notes, nextTodos);
|
void persistProjectData(notes, nextTodos);
|
||||||
@@ -369,7 +379,15 @@ export const ProjectNotesTodoPanel: React.FC<ProjectNotesTodoPanelProps> = ({
|
|||||||
|
|
||||||
const handleToggleTodo = React.useCallback(
|
const handleToggleTodo = React.useCallback(
|
||||||
(id: string, completed: boolean) => {
|
(id: string, completed: boolean) => {
|
||||||
const nextTodos = todos.map((todo) => (todo.id === id ? { ...todo, completed } : todo));
|
const todo = todos.find((item) => item.id === id);
|
||||||
|
if (!todo || todo.completed === completed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const remainingTodos = todos.filter((item) => item.id !== id);
|
||||||
|
const updatedTodo = { ...todo, completed };
|
||||||
|
const nextTodos = completed
|
||||||
|
? [...remainingTodos, updatedTodo]
|
||||||
|
: insertTodoBeforeCompleted(remainingTodos, updatedTodo);
|
||||||
setTodos(nextTodos);
|
setTodos(nextTodos);
|
||||||
void persistProjectData(notes, nextTodos);
|
void persistProjectData(notes, nextTodos);
|
||||||
},
|
},
|
||||||
@@ -405,7 +423,7 @@ export const ProjectNotesTodoPanel: React.FC<ProjectNotesTodoPanelProps> = ({
|
|||||||
if (oldIndex === -1 || newIndex === -1) {
|
if (oldIndex === -1 || newIndex === -1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const nextTodos = arrayMove(todos, oldIndex, newIndex);
|
const nextTodos = sortTodosWithCompletedLast(arrayMove(todos, oldIndex, newIndex));
|
||||||
setTodos(nextTodos);
|
setTodos(nextTodos);
|
||||||
void persistProjectData(notes, nextTodos);
|
void persistProjectData(notes, nextTodos);
|
||||||
},
|
},
|
||||||
@@ -799,7 +817,7 @@ export const ProjectNotesTodoPanel: React.FC<ProjectNotesTodoPanelProps> = ({
|
|||||||
return (
|
return (
|
||||||
<SortableTodoItem key={todo.id} id={todo.id}>
|
<SortableTodoItem key={todo.id} id={todo.id}>
|
||||||
{(dragHandleProps) => (
|
{(dragHandleProps) => (
|
||||||
<div className="flex items-start gap-1.5 px-2.5 py-1.5">
|
<div className={cn('flex gap-1.5 px-2.5 py-1.5', isExpandedTodo ? 'items-start' : 'items-center')}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
ref={dragHandleProps.setActivatorNodeRef}
|
ref={dragHandleProps.setActivatorNodeRef}
|
||||||
@@ -826,7 +844,7 @@ export const ProjectNotesTodoPanel: React.FC<ProjectNotesTodoPanelProps> = ({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => handleToggleTodoExpanded(todo.id)}
|
onClick={() => handleToggleTodoExpanded(todo.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'block min-h-6 min-w-0 flex-1 bg-transparent p-0 text-left typography-ui-label leading-6 text-foreground',
|
'block min-h-6 min-w-0 flex-1 bg-transparent p-0 text-left typography-ui-label leading-normal text-foreground',
|
||||||
isExpandedTodo ? 'whitespace-normal break-words' : 'overflow-hidden text-ellipsis whitespace-nowrap',
|
isExpandedTodo ? 'whitespace-normal break-words' : 'overflow-hidden text-ellipsis whitespace-nowrap',
|
||||||
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50',
|
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50',
|
||||||
todo.completed && 'text-muted-foreground line-through'
|
todo.completed && 'text-muted-foreground line-through'
|
||||||
|
|||||||
Reference in New Issue
Block a user