Working rename

This commit is contained in:
2026-02-28 14:24:18 +01:00
parent e71b481dbb
commit 3af4921fd2
+168 -153
View File
@@ -1,6 +1,8 @@
"use client"; "use client";
import { CollapsibleTrigger } from "@radix-ui/react-collapsible"; import { useState } from "react";
import { ChevronDown } from "lucide-react";
import { import {
SidebarGroup, SidebarGroup,
SidebarGroupContent, SidebarGroupContent,
@@ -9,27 +11,13 @@ import {
SidebarMenuButton, SidebarMenuButton,
SidebarMenuItem, SidebarMenuItem,
} from "../ui/sidebar"; } from "../ui/sidebar";
import { Collapsible, CollapsibleContent } from "../ui/collapsible";
import { ChevronDown, MoreHorizontal } from "lucide-react";
import { Route, Workspace, LayoutNode } from "@/lib/types";
import { import {
DropdownMenu, Collapsible,
DropdownMenuContent, CollapsibleContent,
DropdownMenuItem, CollapsibleTrigger,
DropdownMenuSeparator, } from "../ui/collapsible";
DropdownMenuTrigger,
} from "../ui/dropdown-menu";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { useState } from "react";
import { import {
ContextMenu, ContextMenu,
ContextMenuContent, ContextMenuContent,
@@ -38,6 +26,22 @@ import {
ContextMenuTrigger, ContextMenuTrigger,
} from "../ui/context-menu"; } from "../ui/context-menu";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
} from "../ui/dialog";
import { Input } from "../ui/input";
import { Button } from "../ui/button";
import { Route, Workspace, LayoutNode } from "@/lib/types";
type DialogMode = "rename" | "add-item" | "add-group";
type Props = { type Props = {
node: LayoutNode; node: LayoutNode;
workspace: Workspace; workspace: Workspace;
@@ -47,101 +51,151 @@ type Props = {
parentGroupId: string | null, parentGroupId: string | null,
name?: string, name?: string,
) => string | undefined; ) => string | undefined;
addGroup: (name: string | undefined, parentGroupId: string | null) => string; addGroup: (name: string, parentGroupId: string | null) => string;
renameEntity: (entityId: string, newName: string) => void; renameEntity: (entityId: string, newName: string) => void;
deleteEntity: (entityId: string) => void; deleteEntity: (entityId: string) => void;
removeNode: (id: string) => void; removeNode: (id: string) => void;
renameGroup: (id: string, name: string) => void; renameGroup: (id: string, name: string) => void;
}; };
export default function EntitySwitch({ export default function EntitySwitch(props: Props) {
node, const {
workspace, node,
setRoute, workspace,
addEntity, setRoute,
addGroup, addEntity,
renameEntity, addGroup,
deleteEntity, renameEntity,
removeNode, deleteEntity,
renameGroup, removeNode,
}: Props) { renameGroup,
const [dialogOpen, setDialogOpen] = useState(false); } = props;
const [dialogValue, setDialogValue] = useState(""); const [dialogValue, setDialogValue] = useState("");
const [dialogMode, setDialogMode] = useState< const [dialogMode, setDialogMode] = useState<DialogMode | undefined>();
"rename-group" | "rename-item" | "add-item" | "add-group"
>("rename-item");
const openDialog = (mode: typeof dialogMode, initial = "") => { const parentId = node.type === "group" ? node.id : null;
const openDialog = (mode: DialogMode, initial = "") => {
setDialogMode(mode); setDialogMode(mode);
// supply default for add-item setDialogValue(
if (mode === "add-item") { initial ||
setDialogValue("New list"); (mode === "add-item"
} else { ? "New list"
setDialogValue(initial); : mode === "add-group"
} ? "New group"
setDialogOpen(true); : ""),
);
}; };
const handleDialogSubmit = () => { const closeDialog = () => setDialogMode(undefined);
if (dialogMode === "rename-group" && node.type === "group") {
renameGroup(node.id, dialogValue); const handleSubmit = () => {
} else if (dialogMode === "rename-item" && node.type === "item") { const value = dialogValue.trim();
renameEntity(node.entityId, dialogValue); if (!value) return;
} else if (dialogMode === "add-item") {
const parentId = node.type === "group" ? node.id : null; switch (dialogMode) {
addEntity("todo", parentId, dialogValue || "New list"); case "rename":
} else if (dialogMode === "add-group") { if (node.type === "group") renameGroup(node.id, value);
const parentId = node.type === "group" ? node.id : null; else if (node.type === "item") renameEntity(node.entityId, value);
addGroup(dialogValue || "New group", parentId); break;
case "add-item":
addEntity("todo", parentId, value);
break;
case "add-group":
addGroup(value, parentId);
break;
} }
setDialogOpen(false);
closeDialog();
}; };
// ---- GROUP ---- const confirmDelete = (message: string, action: () => void) => {
if (window.confirm(message)) action();
};
const dialogTitle = dialogMode?.startsWith("rename")
? "Rename"
: dialogMode === "add-item"
? "Add new list"
: "Add new group";
const dialog = (
<Dialog open={!!dialogMode} onOpenChange={(o) => !o && closeDialog()}>
<DialogContent>
<DialogHeader>
<DialogTitle>{dialogTitle}</DialogTitle>
{(dialogMode === "add-item" || dialogMode === "add-group") && (
<DialogDescription>Enter a name below.</DialogDescription>
)}
</DialogHeader>
<Input
autoFocus
value={dialogValue}
onChange={(e) => setDialogValue(e.target.value)}
/>
<DialogFooter>
<Button variant="outline" onClick={closeDialog}>
Cancel
</Button>
<Button onClick={handleSubmit}>Save</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
/* ================= GROUP ================= */
if (node.type === "group") { if (node.type === "group") {
return ( return (
<> <>
<Collapsible defaultOpen className="group/collapsible"> <Collapsible defaultOpen className="group/collapsible">
<SidebarGroup> <SidebarGroup>
<SidebarGroupLabel asChild> <SidebarGroupLabel asChild>
<div className="flex items-center justify-between w-full"> <ContextMenu>
<ContextMenu> <ContextMenuTrigger asChild>
<ContextMenuTrigger asChild> <CollapsibleTrigger asChild>
<SidebarMenuButton asChild> <SidebarMenuButton className="w-full">
<CollapsibleTrigger className="flex w-full items-center"> {node.name}
{node.name} <ChevronDown className="ml-auto h-4 w-4 transition-transform group-data-[state=open]/collapsible:rotate-180" />
<ChevronDown className="ml-auto h-4 w-4 transition-transform group-data-[state=open]/collapsible:rotate-180" />
</CollapsibleTrigger>
</SidebarMenuButton> </SidebarMenuButton>
</ContextMenuTrigger> </CollapsibleTrigger>
<ContextMenuContent> </ContextMenuTrigger>
<ContextMenuItem onSelect={() => openDialog("add-item")}>
Add list <ContextMenuContent>
</ContextMenuItem> <ContextMenuItem onSelect={() => openDialog("add-item")}>
<ContextMenuItem onSelect={() => openDialog("add-group")}> Add list
Add group </ContextMenuItem>
</ContextMenuItem>
<ContextMenuSeparator /> <ContextMenuItem onSelect={() => openDialog("add-group")}>
<ContextMenuItem Add group
onSelect={() => openDialog("rename-group", node.name)} </ContextMenuItem>
>
Rename <ContextMenuSeparator />
</ContextMenuItem>
<ContextMenuItem <ContextMenuItem
variant="destructive" onSelect={() => openDialog("rename", node.name)}
onSelect={() => { >
if ( Rename
window.confirm("Delete this group and its children?") </ContextMenuItem>
) {
removeNode(node.id); <ContextMenuItem
} variant="destructive"
}} onSelect={() =>
> confirmDelete("Delete this group and its children?", () =>
Delete removeNode(node.id),
</ContextMenuItem> )
</ContextMenuContent> }
</ContextMenu> >
</div> Delete
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
</SidebarGroupLabel> </SidebarGroupLabel>
<CollapsibleContent> <CollapsibleContent>
@@ -150,15 +204,8 @@ export default function EntitySwitch({
{node.children.map((child) => ( {node.children.map((child) => (
<EntitySwitch <EntitySwitch
key={child.type === "group" ? child.id : child.entityId} key={child.type === "group" ? child.id : child.entityId}
{...props}
node={child} node={child}
workspace={workspace}
setRoute={setRoute}
addEntity={addEntity}
addGroup={addGroup}
renameEntity={renameEntity}
deleteEntity={deleteEntity}
removeNode={removeNode}
renameGroup={renameGroup}
/> />
))} ))}
</SidebarMenu> </SidebarMenu>
@@ -166,52 +213,19 @@ export default function EntitySwitch({
</CollapsibleContent> </CollapsibleContent>
</SidebarGroup> </SidebarGroup>
</Collapsible> </Collapsible>
{dialog}
{/* dialog for add/rename */}
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>
{dialogMode.startsWith("rename")
? "Rename"
: dialogMode === "add-item"
? "Add new list"
: "Add new group"}
</DialogTitle>
<DialogDescription>
{dialogMode === "add-item" &&
"A default todo list will be created."}
{dialogMode === "add-group" &&
"Enter the name for the new group."}
</DialogDescription>
</DialogHeader>
<Input
value={dialogValue}
onChange={(e) => setDialogValue(e.target.value)}
className="w-full"
/>
<DialogFooter>
<Button variant="outline" onClick={() => setDialogOpen(false)}>
Cancel
</Button>
<Button onClick={handleDialogSubmit}>Save</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</> </>
); );
} }
// ---- ITEM ---- /* ================= ITEM ================= */
const workspaceEntity = workspace.entities[node.entityId];
if (!workspaceEntity) { const entity = workspace.entities[node.entityId];
return null; if (!entity) return null;
}
return ( return (
<SidebarMenuItem> <>
<div className="flex items-center justify-between w-full"> <SidebarMenuItem>
<ContextMenu> <ContextMenu>
<ContextMenuTrigger asChild> <ContextMenuTrigger asChild>
<SidebarMenuButton <SidebarMenuButton
@@ -222,28 +236,29 @@ export default function EntitySwitch({
}) })
} }
> >
{workspaceEntity.name} {entity.name}
</SidebarMenuButton> </SidebarMenuButton>
</ContextMenuTrigger> </ContextMenuTrigger>
<ContextMenuContent> <ContextMenuContent>
<ContextMenuItem <ContextMenuItem onSelect={() => openDialog("rename", entity.name)}>
onSelect={() => openDialog("rename-item", workspaceEntity.name)}
>
Rename Rename
</ContextMenuItem> </ContextMenuItem>
<ContextMenuItem <ContextMenuItem
variant="destructive" variant="destructive"
onSelect={() => { onSelect={() =>
if (window.confirm("Delete this list?")) { confirmDelete("Delete this list?", () =>
deleteEntity(node.entityId); deleteEntity(node.entityId),
} )
}} }
> >
Delete Delete
</ContextMenuItem> </ContextMenuItem>
</ContextMenuContent> </ContextMenuContent>
</ContextMenu> </ContextMenu>
</div> </SidebarMenuItem>
</SidebarMenuItem> {dialog}
</>
); );
} }