build ok,

This commit is contained in:
louiscklaw
2025-04-14 09:26:24 +08:00
commit 6c931c1fe8
770 changed files with 63959 additions and 0 deletions

View File

@@ -0,0 +1,473 @@
'use client';
import * as React from 'react';
import type { Column, Comment, Task } from './types';
function noop(): void {
return undefined;
}
export interface TasksContextValue {
columns: Map<string, Column>;
tasks: Map<string, Task>;
currentColumnId?: string;
currentTaskId?: string;
setCurrentColumnId: (columnId?: string) => void;
setCurrentTaskId: (taskId?: string) => void;
createColumn: () => void;
updateColumn: (taskId: string, params: { name?: string }) => void;
clearColumn: (columnId: string) => void;
deleteColumn: (columnId: string) => void;
dragTask: (active: { id: string; type: 'task' }, over: { id: string; type: 'column' | 'task' }) => void;
createTask: (columnId: string) => void;
deleteTask: (taskId: string) => void;
updateTask: (taskId: string, params: { title?: string; description?: string }) => void;
addComment: (taskId: string, content: string) => void;
}
export const TasksContext = React.createContext<TasksContextValue>({
columns: new Map(),
tasks: new Map(),
setCurrentColumnId: noop,
setCurrentTaskId: noop,
createColumn: noop,
updateColumn: noop,
clearColumn: noop,
deleteColumn: noop,
dragTask: noop,
createTask: noop,
deleteTask: noop,
updateTask: noop,
addComment: noop,
});
export interface TasksProviderProps {
children: React.ReactNode;
columns: Column[];
tasks: Task[];
}
export function TasksProvider({
children,
columns: initialColumns = [],
tasks: initialTasks = [],
}: TasksProviderProps): React.JSX.Element {
const [columns, setColumns] = React.useState(new Map<string, Column>());
const [tasks, setTasks] = React.useState(new Map<string, Task>());
const [currentColumnId, setCurrentColumnId] = React.useState<string>();
const [currentTaskId, setCurrentTaskId] = React.useState<string>();
React.useEffect((): void => {
setColumns(new Map(initialColumns.map((column) => [column.id, column])));
}, [initialColumns]);
React.useEffect((): void => {
setTasks(new Map(initialTasks.map((task) => [task.id, task])));
}, [initialTasks]);
const handleCreateColumn = React.useCallback((): void => {
const column = { id: `COL-${Date.now()}`, name: 'Untitled', taskIds: [] } satisfies Column;
const updatedColumns = new Map<string, Column>(columns);
// Add column
updatedColumns.set(column.id, column);
// Dispatch update
setColumns(updatedColumns);
}, [columns]);
const handleUpdateColumn = React.useCallback(
(columnId: string, { name }: { name?: string }): void => {
const column = columns.get(columnId);
// Column might no longer exist
if (!column) {
return;
}
const updatedColumns = new Map<string, Column>(columns);
const updatedColumn = { ...column };
if (typeof name !== 'undefined') {
updatedColumn.name = name;
}
// Update column
updatedColumns.set(updatedColumn.id, updatedColumn);
// Dispatch update
setColumns(updatedColumns);
},
[columns]
);
const handleClearColumn = React.useCallback(
(columnId: string): void => {
const column = columns.get(columnId);
// Column might no longer exist
if (!column) {
return;
}
const updatedTasks = new Map<string, Task>(tasks);
// Delete tasks
column.taskIds.forEach((taskId): void => {
updatedTasks.delete(taskId);
});
const updatedColumns = new Map<string, Column>(columns);
const updatedColumn = { ...column, taskIds: [] };
// Update column
updatedColumns.set(updatedColumn.id, updatedColumn);
// Dispatch update
setColumns(updatedColumns);
setTasks(updatedTasks);
},
[columns, tasks]
);
const handleDeleteColumn = React.useCallback(
(columnId: string): void => {
const column = columns.get(columnId);
// Column might no longer exist
if (!column) {
return;
}
const updatedTasks = new Map<string, Task>(tasks);
// Delete tasks
column.taskIds.forEach((taskId): void => {
updatedTasks.delete(taskId);
});
const updatedColumns = new Map<string, Column>(columns);
// Delete column
updatedColumns.delete(column.id);
// Dispatch update
setColumns(updatedColumns);
setTasks(updatedTasks);
},
[columns, tasks]
);
const handleDragTask = React.useCallback(
(active: { id: string; type: 'task' }, over: { id: string; type: 'column' | 'task' }): void => {
const activeTask = tasks.get(active.id);
// Active task and might no longer exist
if (!activeTask) {
return;
}
const activeColumn = columns.get(activeTask.columnId);
// Active column might no longer exist
if (!activeColumn) {
return;
}
// Dropped over a column
if (over.type === 'column') {
// Dropped on the same column, reorder at the end
if (activeTask.columnId === over.id) {
const updatedActiveColumn = {
...activeColumn,
taskIds: [...activeColumn.taskIds.filter((taskId) => taskId !== activeTask.id), activeTask.id],
} satisfies Column;
const updatedColumns = new Map<string, Column>(columns);
updatedColumns.set(updatedActiveColumn.id, updatedActiveColumn);
// Dispatch update
setColumns(updatedColumns);
}
// Dropped in a different column, move at the end
else {
const overColumn = columns.get(over.id);
// Over column might no longer exist
if (!overColumn) {
return;
}
// Change task column
const updatedActiveTask = { ...activeTask, columnId: overColumn.id } satisfies Task;
const updatedTasks = new Map<string, Task>(tasks);
updatedTasks.set(updatedActiveTask.id, updatedActiveTask);
// Remove task from active column
const updatedActiveColumn = {
...activeColumn,
taskIds: activeColumn.taskIds.filter((taskId) => taskId !== activeTask.id),
} satisfies Column;
// Add task to over column
const updatedOverColumn = { ...overColumn, taskIds: [...overColumn.taskIds, activeTask.id] } satisfies Column;
const updatedColumns = new Map<string, Column>(columns);
updatedColumns.set(updatedActiveColumn.id, updatedActiveColumn);
updatedColumns.set(updatedOverColumn.id, updatedOverColumn);
// Dispatch update
setTasks(updatedTasks);
setColumns(updatedColumns);
}
}
// Dropped over a task
else {
// Dropped over self
if (activeTask.id === over.id) {
return;
}
const overTask = tasks.get(over.id);
// Over task might no longer exist
if (!overTask) {
return;
}
// Dropped on the same column, reorder
if (activeTask.columnId === overTask.columnId) {
const oldTaskIndex = activeColumn.taskIds.findIndex((taskId) => taskId === activeTask.id);
const newTaskIndex = activeColumn.taskIds.findIndex((taskId) => taskId === overTask.id);
const updatedActiveColumn = {
...activeColumn,
taskIds: arrayMove(activeColumn.taskIds, oldTaskIndex, newTaskIndex),
} satisfies Column;
const updatedColumns = new Map<string, Column>(columns);
updatedColumns.set(updatedActiveColumn.id, updatedActiveColumn);
// Dispatch update
setColumns(updatedColumns);
}
// Dopped on a different column, move at position
else {
const overColumn = columns.get(overTask.columnId);
// Column might no longer exist
if (!overColumn) {
return;
}
// Change task column
const updatedActiveTask = { ...activeTask, columnId: overColumn.id } satisfies Task;
const updatedTasks = new Map<string, Task>(tasks);
updatedTasks.set(updatedActiveTask.id, updatedActiveTask);
// Find new task position
const overTaskIndex = overColumn.taskIds.findIndex((taskId) => taskId === overTask.id);
// Remove task from active column
const updatedActiveColumn = {
...activeColumn,
taskIds: activeColumn.taskIds.filter((taskId) => taskId !== activeTask.id),
} satisfies Column;
// Add task to over column at position
const updatedOverColumn = {
...overColumn,
taskIds: arrayInsert(overColumn.taskIds, overTaskIndex, activeTask.id),
} satisfies Column;
const updatedColumns = new Map<string, Column>(columns);
updatedColumns.set(updatedActiveColumn.id, updatedActiveColumn);
updatedColumns.set(updatedOverColumn.id, updatedOverColumn);
// Dispatch update
setTasks(updatedTasks);
setColumns(updatedColumns);
}
}
},
[columns, tasks]
);
const handleCreateTask = React.useCallback(
(columnId: string): void => {
const column = columns.get(columnId);
// Column might no longer exist
if (!column) {
return;
}
// Create the new task
const task = {
id: `TSK-${Date.now()}`,
author: { id: 'USR-000', name: 'Sofia Rivers', username: 'sofia.rivers', avatar: '/assets/avatar.png' },
title: 'Untitled',
columnId,
createdAt: new Date(),
} satisfies Task;
const updatedTasks = new Map<string, Task>(tasks);
// Add it to the tasks
updatedTasks.set(task.id, task);
// Add the task to the column
const updatedColumn = { ...column, taskIds: [task.id, ...column.taskIds] } satisfies Column;
const updatedColumns = new Map<string, Column>(columns);
updatedColumns.set(updatedColumn.id, updatedColumn);
// Dispatch update
setTasks(updatedTasks);
setColumns(updatedColumns);
},
[columns, tasks]
);
const handleDeleteTask = React.useCallback(
(taskId: string): void => {
const task = tasks.get(taskId);
// Task might no longer exist
if (!task) {
return;
}
const updatedTasks = new Map<string, Task>(tasks);
// Delete the task
updatedTasks.delete(task.id);
const updatedColumns = new Map<string, Column>(columns);
// Delete the task ID from column
const column = updatedColumns.get(task.columnId);
// Column might no longer exist
if (column) {
const updatedColumn = { ...column, taskId: column.taskIds.filter((id) => id !== task.id) };
updatedColumns.set(updatedColumn.id, updatedColumn);
}
// Dispatch update
setColumns(updatedColumns);
setTasks(updatedTasks);
},
[columns, tasks]
);
const handleUpdateTask = React.useCallback(
(taskId: string, { title, description }: { title?: string; description?: string }): void => {
const task = tasks.get(taskId);
// Task might no longer exist
if (!task) {
return;
}
const updatedTasks = new Map<string, Task>(tasks);
const updatedTask = { ...task };
// Title changed
if (typeof title !== 'undefined') {
updatedTask.title = title;
}
// Description changed
if (typeof description !== 'undefined') {
updatedTask.description = description;
}
updatedTasks.set(updatedTask.id, updatedTask);
// Dispatch update
setTasks(updatedTasks);
},
[tasks]
);
const handleAddComment = React.useCallback(
(taskId: string, content: string): void => {
const task = tasks.get(taskId);
// Task might no longer exist
if (!task) {
return;
}
// Copy existing tasks
const updatedTasks = new Map<string, Task>(tasks);
// Create the comment and add it to the task
const comment = {
id: `MSG-${Date.now()}`,
author: { id: 'USR-000', name: 'Sofia Rivers', username: 'sofia.rivers', avatar: '/assets/avatar.png' },
content,
createdAt: new Date(),
} satisfies Comment;
updatedTasks.set(task.id, { ...task, comments: [...(task.comments ?? []), comment] });
// Dispatch update
setTasks(updatedTasks);
},
[tasks]
);
return (
<TasksContext.Provider
value={{
columns,
tasks,
currentColumnId,
currentTaskId,
setCurrentColumnId,
setCurrentTaskId,
createColumn: handleCreateColumn,
updateColumn: handleUpdateColumn,
clearColumn: handleClearColumn,
deleteColumn: handleDeleteColumn,
dragTask: handleDragTask,
createTask: handleCreateTask,
deleteTask: handleDeleteTask,
updateTask: handleUpdateTask,
addComment: handleAddComment,
}}
>
{children}
</TasksContext.Provider>
);
}
export const TasksConsumer = TasksContext.Consumer;
function arrayMove<T = unknown>(arr: T[], from: number, to: number): T[] {
const copy = [...arr];
const [item] = copy.splice(from, 1);
copy.splice(to, 0, item);
return copy;
}
function arrayInsert<T = unknown>(arr: T[], index: number, item: T): T[] {
return [...arr.slice(0, index), item, ...arr.slice(index)];
}