"use client"; import { useState } from "react"; import { X, Trash2, Bot, Lock, UserMinus } from "lucide-react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { Popover, PopoverTrigger, PopoverContent, } from "@/components/ui/popover"; import type { Agent, UpdateIssueRequest } from "@/shared/types"; import { ALL_STATUSES, STATUS_CONFIG, PRIORITY_ORDER, PRIORITY_CONFIG } from "@/features/issues/config"; import { useAuthStore } from "@/features/auth"; import { useWorkspaceStore, useActorName } from "@/features/workspace"; import { useIssueStore } from "@/features/issues/store"; import { useIssueSelectionStore } from "@/features/issues/stores/selection-store"; import { api } from "@/shared/api"; import { StatusIcon } from "./status-icon"; import { PriorityIcon } from "./priority-icon"; export function BatchActionToolbar() { const selectedIds = useIssueSelectionStore((s) => s.selectedIds); const clear = useIssueSelectionStore((s) => s.clear); const count = selectedIds.size; const [statusOpen, setStatusOpen] = useState(false); const [priorityOpen, setPriorityOpen] = useState(false); const [assigneeOpen, setAssigneeOpen] = useState(false); const [deleteOpen, setDeleteOpen] = useState(false); const [loading, setLoading] = useState(false); if (count === 0) return null; const ids = Array.from(selectedIds); const handleBatchUpdate = async (updates: UpdateIssueRequest) => { setLoading(true); try { await api.batchUpdateIssues(ids, updates); for (const id of ids) { useIssueStore.getState().updateIssue(id, updates); } toast.success(`Updated ${count} issue${count > 1 ? "s" : ""}`); } catch { toast.error("Failed to update issues"); api.listIssues({ limit: 200 }).then((res) => { useIssueStore.getState().setIssues(res.issues); }); } finally { setLoading(false); } }; const handleBatchDelete = async () => { setLoading(true); try { await api.batchDeleteIssues(ids); for (const id of ids) { useIssueStore.getState().removeIssue(id); } clear(); toast.success(`Deleted ${count} issue${count > 1 ? "s" : ""}`); } catch { toast.error("Failed to delete issues"); api.listIssues({ limit: 200 }).then((res) => { useIssueStore.getState().setIssues(res.issues); }); } finally { setLoading(false); setDeleteOpen(false); } }; return ( <>
{count} selected
{/* Status */} } > Status {ALL_STATUSES.map((s) => { const cfg = STATUS_CONFIG[s]; return ( ); })} {/* Priority */} } > Priority {PRIORITY_ORDER.map((p) => { const cfg = PRIORITY_CONFIG[p]; return ( ); })} {/* Assignee */} {/* Delete */}
Delete {count} issue{count > 1 ? "s" : ""}? This action cannot be undone. This will permanently delete the selected issue{count > 1 ? "s" : ""} and all associated data. Cancel Delete ); } function canAssignAgent(agent: Agent, userId: string | undefined, memberRole: string | undefined): boolean { if (agent.visibility !== "private") return true; if (agent.owner_id === userId) return true; if (memberRole === "owner" || memberRole === "admin") return true; return false; } function BatchAssigneePicker({ open, onOpenChange, onUpdate, loading, }: { open: boolean; onOpenChange: (v: boolean) => void; onUpdate: (updates: UpdateIssueRequest) => void; loading: boolean; }) { const [filter, setFilter] = useState(""); const user = useAuthStore((s) => s.user); const members = useWorkspaceStore((s) => s.members); const agents = useWorkspaceStore((s) => s.agents); const { getActorInitials } = useActorName(); const currentMember = members.find((m) => m.user_id === user?.id); const memberRole = currentMember?.role; const query = filter.toLowerCase(); const filteredMembers = members.filter((m) => m.name.toLowerCase().includes(query), ); const filteredAgents = agents.filter((a) => a.name.toLowerCase().includes(query), ); return ( { onOpenChange(v); if (!v) setFilter(""); }} > } > Assignee
setFilter(e.target.value)} placeholder="Assign to..." className="w-full bg-transparent text-sm placeholder:text-muted-foreground outline-none" />
{filteredMembers.length > 0 && (
Members
{filteredMembers.map((m) => ( ))}
)} {filteredAgents.length > 0 && (
Agents
{filteredAgents.map((a) => { const allowed = canAssignAgent(a, user?.id, memberRole); return ( ); })}
)}
); }