fix(chat,todo): show sent chat messages immediately + kanban drag-and-drop reorder
チャット送信の即時表示とKanbanのDnD並べ替えを修正。 - chat: 送信成功時に返却メッセージを楽観的にローカルへ先頭追加(SSE到着前でも即表示、idで重複排除)。setSending(false) をレスポンス処理後に移動し二重送信窓を縮小。 - todo: TodoService.reorderItems(projectId, columnId, itemIds[]) を追加(1トランザクションでカラム内を再採番、メンバーシップ+同プロジェクト検証、活動ログ/SSEは1件)。POST /todos/items/reorder を追加。 - KanbanBoard: カード単位のドロップターゲット(ポインタYで前/後挿入)+カラム空き領域は末尾追加。楽観的にorderIndex/columnIdを更新しrenderをorderIndexでソート(即時反映)、失敗時はエラー表示+再取得で巻き戻し。ドラッグ直後の誤クリック(ダイアログ誘起)をdragEndタイムスタンプで抑制。空の並べ替えはSSE/ログを出さない。
This commit is contained in:
@ -235,6 +235,45 @@ export class TodoService {
|
||||
return updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定カラム内のアイテム順序を itemIds の順で再採番する。
|
||||
* 同一カラムの並べ替えだけでなく、別カラムからの移動(対象カラムへの挿入)にも使う。
|
||||
* すべて同一プロジェクト内であることを検証し、1トランザクションで更新する。
|
||||
*/
|
||||
reorderItems(
|
||||
actorId: number,
|
||||
projectId: number,
|
||||
columnId: number,
|
||||
itemIds: number[]
|
||||
): TodoItem[] {
|
||||
this.requireMember(projectId, actorId);
|
||||
const column = this.todoRepository.findColumnById(columnId);
|
||||
if (!column || column.projectId !== projectId) {
|
||||
throw new NotFoundError('TodoColumn', columnId);
|
||||
}
|
||||
const ids = Array.isArray(itemIds) ? itemIds : [];
|
||||
if (ids.length === 0) {
|
||||
// 空の並べ替えは何も変更せず、SSE/ログも出さない
|
||||
return this.todoRepository
|
||||
.findItemsByProject(projectId)
|
||||
.filter((i) => i.columnId === columnId);
|
||||
}
|
||||
this.db.transaction(() => {
|
||||
ids.forEach((id, idx) => {
|
||||
const item = this.todoRepository.findItemById(id);
|
||||
if (!item || item.projectId !== projectId) {
|
||||
throw new NotFoundError('TodoItem', id);
|
||||
}
|
||||
this.todoRepository.updateItem(id, { columnId, orderIndex: idx });
|
||||
});
|
||||
});
|
||||
this.logTodo(projectId, actorId, 'todo_updated', ids[0]);
|
||||
this.broadcastTodo(projectId);
|
||||
return this.todoRepository
|
||||
.findItemsByProject(projectId)
|
||||
.filter((i) => i.columnId === columnId);
|
||||
}
|
||||
|
||||
toggleComplete(actorId: number, itemId: number): TodoItem {
|
||||
const item = this.todoRepository.findItemById(itemId);
|
||||
if (!item) throw new NotFoundError('TodoItem', itemId);
|
||||
|
||||
Reference in New Issue
Block a user