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
|
// panel stays open (document still visible) after the failed resume
|
||||||
await waitFor(() => expect(screen.getByText("Domanda originale")).toBeInTheDocument());
|
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 { DeleteConfirmDialog } from "./DeleteConfirmDialog";
|
||||||
import { SteerInput, ComposerFooter } from "./SteerInput";
|
import { SteerInput, ComposerFooter } from "./SteerInput";
|
||||||
import { WorkflowBar } from "./WorkflowBar";
|
import { WorkflowBar } from "./WorkflowBar";
|
||||||
|
import { Pencil } from "lucide-react";
|
||||||
import { Button } from "../components/ui/button";
|
import { Button } from "../components/ui/button";
|
||||||
import { Toaster } from "../components/ui/sonner";
|
import { Toaster } from "../components/ui/sonner";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
@@ -40,6 +41,7 @@ export function AppShell() {
|
|||||||
const [renameTarget, setRenameTarget] = useState<SessionSummary | null>(null);
|
const [renameTarget, setRenameTarget] = useState<SessionSummary | null>(null);
|
||||||
const [deleteTarget, setDeleteTarget] = useState<SessionSummary | null>(null);
|
const [deleteTarget, setDeleteTarget] = useState<SessionSummary | null>(null);
|
||||||
const [collapsedGroups, setCollapsedGroups] = useState<Record<string, boolean>>({});
|
const [collapsedGroups, setCollapsedGroups] = useState<Record<string, boolean>>({});
|
||||||
|
const [renameGroupTarget, setRenameGroupTarget] = useState<string | null>(null);
|
||||||
|
|
||||||
const groups = useMemo(
|
const groups = useMemo(
|
||||||
() => [...new Set(sessions.map((s) => s.group).filter((g): g is string => !!g))].sort(),
|
() => [...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) {
|
async function toggleArchive(s: SessionSummary) {
|
||||||
try {
|
try {
|
||||||
await (s.archived ? unarchiveSession(s.id) : archiveSession(s.id));
|
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">
|
<div className="flex flex-col gap-3 pb-2">
|
||||||
{groups.map((g) => (
|
{groups.map((g) => (
|
||||||
<div key={g}>
|
<div key={g}>
|
||||||
|
<div className="group/gh flex items-center gap-1">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setCollapsedGroups((c) => ({ ...c, [g]: !c[g] }))}
|
onClick={() => setCollapsedGroups((c) => ({ ...c, [g]: !c[g] }))}
|
||||||
aria-expanded={!collapsedGroups[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"
|
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="select-none">{collapsedGroups[g] ? "▸" : "▾"}</span>
|
||||||
<span className="truncate">{g}</span>
|
<span className="truncate">{g}</span>
|
||||||
</button>
|
</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] && (
|
{!collapsedGroups[g] && (
|
||||||
<NavSessions
|
<NavSessions
|
||||||
sessions={activeList.filter((s) => s.group === g)}
|
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 && (
|
{deleteTarget && (
|
||||||
<DeleteConfirmDialog
|
<DeleteConfirmDialog
|
||||||
open
|
open
|
||||||
|
|||||||
Reference in New Issue
Block a user