workspace-ui-kit/components/workspace/PositionPane.tsx
snc777 f6a781c47e initial commit
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 14:24:17 +09:00

169 lines
6.1 KiB
TypeScript

"use client";
import { useState } from "react";
import { MoreHorizontal, Plus, Trash2 } from "lucide-react";
import { type Department } from "@/lib/schema";
import { DeleteConfirmDialog } from "@/components/workspace/DeleteConfirmDialog";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupAction,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarMenu,
SidebarMenuAction,
SidebarMenuButton,
SidebarMenuItem,
} from "@/components/ui/sidebar";
import { Pane1Toggle } from "@/components/workspace/Pane1Toggle";
import { AddItemDialog } from "@/components/workspace/AddItemDialog";
type PositionPaneProps = {
workspaceName: string;
departments: Department[];
selectedPositionName?: string;
onAddPosition: (deptId: string, posName: string) => void;
onDeletePosition: (deptId: string, posId: string) => void;
};
export function PositionPane({
workspaceName,
departments,
selectedPositionName,
onAddPosition,
onDeletePosition,
}: PositionPaneProps) {
const [addDialogDeptId, setAddDialogDeptId] = useState<string | null>(null);
const [deleteTarget, setDeleteTarget] = useState<{
deptId: string;
posId: string;
posName: string;
} | null>(null);
const addDialogDept = departments.find((d) => d.id === addDialogDeptId);
return (
<>
<Sidebar
collapsible="icon"
className="border-r border-sidebar-border [&_[data-slot=sidebar-container]]:bg-sidebar"
>
<SidebarHeader className="border-b border-sidebar-border p-0">
<div className="flex h-12 items-center justify-between gap-2 px-3 group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:px-0 group-data-[state=expanded]:px-5">
<h2 className="truncate text-sm font-semibold text-sidebar-foreground group-data-[collapsible=icon]:hidden">
{workspaceName}
</h2>
<Pane1Toggle />
</div>
</SidebarHeader>
<SidebarContent className="px-1 py-3 group-data-[collapsible=icon]:hidden">
{departments.map((dept) => (
<SidebarGroup key={dept.id} className="px-1">
<SidebarGroupLabel className="px-2 text-xs font-semibold tracking-wide text-sidebar-foreground/70 uppercase">
{dept.name}
</SidebarGroupLabel>
<SidebarGroupAction
title={`${dept.name} にポジションを追加`}
onClick={() => setAddDialogDeptId(dept.id)}
className="w-6 rounded-[min(var(--radius-md),10px)] text-muted-foreground hover:bg-muted hover:text-foreground [&>svg]:size-3"
>
<Plus />
<span className="sr-only">{dept.name} </span>
</SidebarGroupAction>
<SidebarGroupContent>
<SidebarMenu>
{dept.positions.map((pos) => {
const active = pos.name === selectedPositionName;
return (
<SidebarMenuItem key={pos.id}>
<SidebarMenuButton
tooltip={pos.name}
isActive={active}
aria-current={active ? "page" : undefined}
>
<span className="truncate">{pos.name}</span>
<span className="ml-auto text-xs text-muted-foreground tabular-nums">
{pos.count}
</span>
</SidebarMenuButton>
<DropdownMenu>
<DropdownMenuTrigger
render={
<SidebarMenuAction showOnHover>
<MoreHorizontal />
<span className="sr-only"></span>
</SidebarMenuAction>
}
/>
<DropdownMenuContent side="right" align="start">
<DropdownMenuGroup>
<DropdownMenuItem
variant="destructive"
onSelect={() =>
setDeleteTarget({
deptId: dept.id,
posId: pos.id,
posName: pos.name,
})
}
>
<Trash2 />
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
);
})}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
))}
</SidebarContent>
</Sidebar>
{addDialogDept && (
<AddItemDialog
open={addDialogDeptId !== null}
onOpenChange={(open) => {
if (!open) setAddDialogDeptId(null);
}}
title="ポジションを追加"
description={`${addDialogDept.name} に新しいポジションを追加します`}
fieldLabel="ポジション名"
fieldId="pos-name"
placeholder="例: データエンジニア"
onAdd={(name) => onAddPosition(addDialogDept.id, name)}
/>
)}
<DeleteConfirmDialog
open={deleteTarget !== null}
onOpenChange={(open) => {
if (!open) setDeleteTarget(null);
}}
title="ポジションを削除しますか?"
itemName={deleteTarget?.posName ?? ""}
onConfirm={() => {
if (deleteTarget) {
onDeletePosition(deleteTarget.deptId, deleteTarget.posId);
setDeleteTarget(null);
}
}}
/>
</>
);
}