feat(frontend): rename group (reassign members via setSessionGroup)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -77,3 +77,22 @@ test("a failed resume keeps the panel open and does not activate the session", a
|
||||
// panel stays open (document still visible) after the failed resume
|
||||
await waitFor(() => expect(screen.getByText("Domanda originale")).toBeInTheDocument());
|
||||
});
|
||||
|
||||
test("renaming a group reassigns its members via setSessionGroup", async () => {
|
||||
const groupSets: Array<{ id: string; group: string }> = [];
|
||||
server.use(
|
||||
http.post("http://localhost:8787/sessions/:id/group", async ({ params, request }) => {
|
||||
const body = (await request.json()) as { group: string };
|
||||
groupSets.push({ id: params.id as string, group: body.group });
|
||||
return new HttpResponse(null, { status: 204 });
|
||||
}),
|
||||
);
|
||||
wrap();
|
||||
await screen.findByText("Aritmologia");
|
||||
await userEvent.click(screen.getByRole("button", { name: /rename group aritmologia/i }));
|
||||
const input = await screen.findByLabelText(/name/i, { selector: "input" });
|
||||
await userEvent.clear(input);
|
||||
await userEvent.type(input, "Cardiologia");
|
||||
await userEvent.click(screen.getByRole("button", { name: /save/i }));
|
||||
await waitFor(() => expect(groupSets).toEqual([{ id: "s1", group: "Cardiologia" }]));
|
||||
});
|
||||
|
||||
@@ -9,6 +9,7 @@ import { RenameDialog } from "./RenameDialog";
|
||||
import { DeleteConfirmDialog } from "./DeleteConfirmDialog";
|
||||
import { SteerInput, ComposerFooter } from "./SteerInput";
|
||||
import { WorkflowBar } from "./WorkflowBar";
|
||||
import { Pencil } from "lucide-react";
|
||||
import { Button } from "../components/ui/button";
|
||||
import { Toaster } from "../components/ui/sonner";
|
||||
import { toast } from "sonner";
|
||||
@@ -40,6 +41,7 @@ export function AppShell() {
|
||||
const [renameTarget, setRenameTarget] = useState<SessionSummary | null>(null);
|
||||
const [deleteTarget, setDeleteTarget] = useState<SessionSummary | null>(null);
|
||||
const [collapsedGroups, setCollapsedGroups] = useState<Record<string, boolean>>({});
|
||||
const [renameGroupTarget, setRenameGroupTarget] = useState<string | null>(null);
|
||||
|
||||
const groups = useMemo(
|
||||
() => [...new Set(sessions.map((s) => s.group).filter((g): g is string => !!g))].sort(),
|
||||
@@ -79,6 +81,18 @@ export function AppShell() {
|
||||
}
|
||||
}
|
||||
}
|
||||
async function renameGroup(oldName: string, newName: string) {
|
||||
const trimmed = newName.trim();
|
||||
if (!trimmed || trimmed === oldName) return;
|
||||
try {
|
||||
for (const s of sessions.filter((x) => x.group === oldName)) {
|
||||
await setSessionGroup(s.id, trimmed);
|
||||
}
|
||||
refresh();
|
||||
} catch {
|
||||
toast.error("Failed to rename group.");
|
||||
}
|
||||
}
|
||||
async function toggleArchive(s: SessionSummary) {
|
||||
try {
|
||||
await (s.archived ? unarchiveSession(s.id) : archiveSession(s.id));
|
||||
@@ -225,15 +239,25 @@ export function AppShell() {
|
||||
<div className="flex flex-col gap-3 pb-2">
|
||||
{groups.map((g) => (
|
||||
<div key={g}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setCollapsedGroups((c) => ({ ...c, [g]: !c[g] }))}
|
||||
aria-expanded={!collapsedGroups[g]}
|
||||
className="flex w-full items-center gap-1 px-1 pb-1 text-left text-[0.7rem] font-semibold uppercase tracking-wide text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<span className="select-none">{collapsedGroups[g] ? "▸" : "▾"}</span>
|
||||
<span className="truncate">{g}</span>
|
||||
</button>
|
||||
<div className="group/gh flex items-center gap-1">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setCollapsedGroups((c) => ({ ...c, [g]: !c[g] }))}
|
||||
aria-expanded={!collapsedGroups[g]}
|
||||
className="flex min-w-0 flex-1 items-center gap-1 px-1 pb-1 text-left text-[0.7rem] font-semibold uppercase tracking-wide text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<span className="select-none">{collapsedGroups[g] ? "▸" : "▾"}</span>
|
||||
<span className="truncate">{g}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
aria-label={`Rename group ${g}`}
|
||||
onClick={() => setRenameGroupTarget(g)}
|
||||
className="rounded p-0.5 text-muted-foreground opacity-0 transition-opacity hover:bg-accent group-hover/gh:opacity-100"
|
||||
>
|
||||
<Pencil className="size-3" />
|
||||
</button>
|
||||
</div>
|
||||
{!collapsedGroups[g] && (
|
||||
<NavSessions
|
||||
sessions={activeList.filter((s) => s.group === g)}
|
||||
@@ -291,6 +315,14 @@ export function AppShell() {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{renameGroupTarget && (
|
||||
<RenameDialog
|
||||
open
|
||||
initial={renameGroupTarget}
|
||||
onOpenChange={(o) => { if (!o) setRenameGroupTarget(null); }}
|
||||
onSubmit={async (name) => { await renameGroup(renameGroupTarget, name); setRenameGroupTarget(null); }}
|
||||
/>
|
||||
)}
|
||||
{deleteTarget && (
|
||||
<DeleteConfirmDialog
|
||||
open
|
||||
|
||||
Reference in New Issue
Block a user