Files
kandev/apps/web/components/threads/threads-view-editor.tsx
T
Jérémy Coatelen 538813de02 feat(web): add Threads view for live agent conversations (#3112)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Carlos Florencio <carlosmflorencio@gmail.com>
2026-09-03 08:07:16 +00:00

127 lines
3.9 KiB
TypeScript

"use client";
import { useEffect, useMemo, useState } from "react";
import type { ThreadCandidate } from "@/lib/threads/thread-view-query";
import type { ThreadView, ThreadViewDraft } from "@/lib/state/slices/ui/thread-view-types";
import type { Repository } from "@/lib/types/http";
import { ThreadsViewTaskPicker } from "./threads-view-task-picker";
import { EditorBody, type EditorBodyProps } from "./threads-view-editor-sections";
import { parseThreadMaxColumns } from "./threads-view-editor-utils";
type EditorProps = {
activeView: ThreadView;
draft: ThreadViewDraft | null;
candidates: ThreadCandidate[];
repositories: ReadonlyArray<Pick<Repository, "id" | "name">>;
viewCount: number;
canDelete: boolean;
onUpdate: EditorBodyProps["onUpdate"];
onSave: () => void;
onSaveAs: (name: string) => void;
onDiscard: () => void;
onRename: (name: string) => void;
onDelete: () => void;
onDuplicate: () => void;
onReapplySort: () => void;
showHeader?: boolean;
mobile?: boolean;
};
export function ThreadsViewEditor({
activeView,
draft,
candidates,
repositories,
viewCount,
canDelete,
onUpdate,
onSave,
onSaveAs,
onDiscard,
onRename,
onDelete,
onDuplicate,
onReapplySort,
showHeader = true,
mobile = false,
}: EditorProps) {
const [pickerOpen, setPickerOpen] = useState(false);
const [nameMode, setNameMode] = useState<"rename" | "saveAs" | null>(null);
const [name, setName] = useState("");
const current = draft && draft.baseViewId === activeView.id ? draft : activeView;
const [maxColumnsInput, setMaxColumnsInput] = useState(() =>
formatMaxColumns(current.maxColumns),
);
const [maxColumnsInvalid, setMaxColumnsInvalid] = useState(false);
const invalidSelectedScope =
current.taskScope.mode === "selected" && current.taskScope.taskIds.length === 0;
const hasDraft = !!draft && draft.baseViewId === activeView.id;
const repositoryNames = useMemo(
() =>
new Map<string, string>(
repositories.map((repository) => [String(repository.id), repository.name]),
),
[repositories],
);
const invalidDraft = invalidSelectedScope || maxColumnsInvalid;
useEffect(() => {
setMaxColumnsInput(formatMaxColumns(current.maxColumns));
setMaxColumnsInvalid(false);
}, [activeView.id, current.maxColumns]);
if (pickerOpen) {
return (
<div className="flex min-h-0 flex-col" data-testid="threads-view-editor">
<ThreadsViewTaskPicker
candidates={candidates}
selectedTaskIds={current.taskScope.mode === "selected" ? current.taskScope.taskIds : []}
onChange={(taskIds) => onUpdate({ taskScope: { mode: "selected", taskIds } })}
onBack={() => setPickerOpen(false)}
/>
</div>
);
}
return (
<EditorBody
activeView={activeView}
current={current}
candidates={candidates}
repositoryNames={repositoryNames}
mobile={mobile}
showHeader={showHeader}
nameMode={nameMode}
name={name}
maxColumnsInput={maxColumnsInput}
maxColumnsInvalid={maxColumnsInvalid}
invalidSelectedScope={invalidSelectedScope}
invalidDraft={invalidDraft}
hasDraft={hasDraft}
viewCount={viewCount}
canDelete={canDelete}
onNameChange={setName}
onNameModeChange={setNameMode}
onUpdate={onUpdate}
onSave={onSave}
onSaveAs={onSaveAs}
onRename={onRename}
onDiscard={onDiscard}
onDelete={onDelete}
onDuplicate={onDuplicate}
onReapplySort={onReapplySort}
onSetMaxColumns={(value, badInput = false) => {
setMaxColumnsInput(value);
const parsed = parseThreadMaxColumns(value, badInput);
setMaxColumnsInvalid(parsed === undefined);
if (parsed !== undefined) onUpdate({ maxColumns: parsed });
}}
onOpenPicker={() => setPickerOpen(true)}
/>
);
}
function formatMaxColumns(value: number | null): string {
return value === null ? "" : String(value);
}