mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-14 12:16:54 +00:00
fix(ui): add missing delete buttons for agents and teams (#63)
The list pages had ConfirmDialog for deletion wired up but no UI element to trigger it. Detail pages had no delete option at all. - Add delete button with trash icon to agent and team cards - Wire onDelete callbacks to existing setDeleteTarget state - Add delete button + confirm dialog to agent detail page header - Add delete button + confirm dialog to team detail page header Closes #17 Co-authored-by: Luvu182 <208665161+Luvu182@users.noreply.github.com>
This commit is contained in:
1 parent
6041c683cb
commit
1ebbe53093
6 files changed
+90
-7
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import { Bot, Star, RotateCcw } from "lucide-react";
|
||||
import { Bot, Star, RotateCcw, Trash2 } from "lucide-react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import type { AgentData } from "@/types/agent";
|
||||
@@ -7,11 +7,12 @@ interface AgentCardProps {
|
||||
agent: AgentData;
|
||||
onClick: () => void;
|
||||
onResummon?: () => void;
|
||||
onDelete?: () => void;
|
||||
}
|
||||
|
||||
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||
|
||||
export function AgentCard({ agent, onClick, onResummon }: AgentCardProps) {
|
||||
export function AgentCard({ agent, onClick, onResummon, onDelete }: AgentCardProps) {
|
||||
const displayName = agent.display_name
|
||||
|| (UUID_RE.test(agent.agent_key) ? "Unnamed Agent" : agent.agent_key);
|
||||
|
||||
@@ -91,6 +92,20 @@ export function AgentCard({ agent, onClick, onResummon }: AgentCardProps) {
|
||||
Resummon
|
||||
</Button>
|
||||
)}
|
||||
{onDelete && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
className={`text-muted-foreground hover:text-destructive ${agent.status === "summon_failed" && onResummon ? "" : "ml-auto"}`}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onDelete();
|
||||
}}
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
Delete
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
|
||||
@@ -2,8 +2,9 @@ import { useState } from "react";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { ArrowLeft, Bot, Star } from "lucide-react";
|
||||
import { ArrowLeft, Bot, Star, Trash2 } from "lucide-react";
|
||||
import { useAgentDetail } from "../hooks/use-agent-detail";
|
||||
import { useAgents } from "../hooks/use-agents";
|
||||
import { AgentGeneralTab } from "./agent-general-tab";
|
||||
import { AgentConfigTab } from "./agent-config-tab";
|
||||
import { AgentFilesTab } from "./agent-files-tab";
|
||||
@@ -11,6 +12,7 @@ import { AgentSharesTab } from "./agent-shares-tab";
|
||||
import { AgentLinksTab } from "./agent-links-tab";
|
||||
import { AgentSkillsTab } from "./agent-skills-tab";
|
||||
import { SummoningModal } from "../summoning-modal";
|
||||
import { ConfirmDialog } from "@/components/shared/confirm-dialog";
|
||||
import { DeferredSpinner } from "@/components/shared/loading-skeleton";
|
||||
|
||||
interface AgentDetailPageProps {
|
||||
@@ -37,8 +39,10 @@ function agentSubtitle(agent: { display_name?: string; agent_key: string; id: st
|
||||
export function AgentDetailPage({ agentId, onBack }: AgentDetailPageProps) {
|
||||
const { agent, files, loading, updateAgent, getFile, setFile, regenerateAgent, resummonAgent, refresh } =
|
||||
useAgentDetail(agentId);
|
||||
const { deleteAgent } = useAgents();
|
||||
const [summoningOpen, setSummoningOpen] = useState(false);
|
||||
const [activeTab, setActiveTab] = useState("general");
|
||||
const [deleteOpen, setDeleteOpen] = useState(false);
|
||||
|
||||
const handleRegenerate = async (prompt: string) => {
|
||||
await regenerateAgent(prompt);
|
||||
@@ -106,6 +110,14 @@ export function AgentDetailPage({ agentId, onBack }: AgentDetailPageProps) {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="shrink-0 text-muted-foreground hover:text-destructive"
|
||||
onClick={() => setDeleteOpen(true)}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
@@ -161,6 +173,20 @@ export function AgentDetailPage({ agentId, onBack }: AgentDetailPageProps) {
|
||||
onCompleted={() => {}}
|
||||
onResummon={async () => { await resummonAgent(); }}
|
||||
/>
|
||||
|
||||
<ConfirmDialog
|
||||
open={deleteOpen}
|
||||
onOpenChange={setDeleteOpen}
|
||||
title="Delete Agent"
|
||||
description={`Are you sure you want to delete "${title}"? This action cannot be undone.`}
|
||||
confirmLabel="Delete"
|
||||
variant="destructive"
|
||||
onConfirm={async () => {
|
||||
await deleteAgent(agentId);
|
||||
setDeleteOpen(false);
|
||||
onBack();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -114,6 +114,7 @@ export function AgentsPage() {
|
||||
}
|
||||
}}
|
||||
onResummon={() => handleResummon(agent)}
|
||||
onDelete={() => setDeleteTarget(agent.id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { Users } from "lucide-react";
|
||||
import { Users, Trash2 } from "lucide-react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import type { TeamData } from "@/types/team";
|
||||
|
||||
interface TeamCardProps {
|
||||
team: TeamData;
|
||||
onClick: () => void;
|
||||
onDelete?: () => void;
|
||||
}
|
||||
|
||||
export function TeamCard({ team, onClick }: TeamCardProps) {
|
||||
export function TeamCard({ team, onClick, onDelete }: TeamCardProps) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
@@ -41,6 +43,20 @@ export function TeamCard({ team, onClick }: TeamCardProps) {
|
||||
Lead: {team.lead_agent_key}
|
||||
</Badge>
|
||||
)}
|
||||
{onDelete && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
className="ml-auto text-muted-foreground hover:text-destructive"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onDelete();
|
||||
}}
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
Delete
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
|
||||
@@ -2,8 +2,9 @@ import { useState, useEffect, useCallback } from "react";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { ArrowLeft, Users } from "lucide-react";
|
||||
import { ArrowLeft, Users, Trash2 } from "lucide-react";
|
||||
import { DeferredSpinner } from "@/components/shared/loading-skeleton";
|
||||
import { ConfirmDialog } from "@/components/shared/confirm-dialog";
|
||||
import { useTeams } from "./hooks/use-teams";
|
||||
import { TeamMembersTab } from "./team-members-tab";
|
||||
import { TeamTasksTab } from "./team-tasks-tab";
|
||||
@@ -17,11 +18,12 @@ interface TeamDetailPageProps {
|
||||
}
|
||||
|
||||
export function TeamDetailPage({ teamId, onBack }: TeamDetailPageProps) {
|
||||
const { getTeam, getTeamTasks, addMember, removeMember } = useTeams();
|
||||
const { getTeam, getTeamTasks, addMember, removeMember, deleteTeam } = useTeams();
|
||||
const [team, setTeam] = useState<TeamData | null>(null);
|
||||
const [members, setMembers] = useState<TeamMemberData[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [activeTab, setActiveTab] = useState("members");
|
||||
const [deleteOpen, setDeleteOpen] = useState(false);
|
||||
|
||||
const reload = useCallback(async () => {
|
||||
try {
|
||||
@@ -103,6 +105,14 @@ export function TeamDetailPage({ teamId, onBack }: TeamDetailPageProps) {
|
||||
<p className="mt-1 text-sm text-muted-foreground/70">{team.description}</p>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="shrink-0 text-muted-foreground hover:text-destructive"
|
||||
onClick={() => setDeleteOpen(true)}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
@@ -137,6 +147,20 @@ export function TeamDetailPage({ teamId, onBack }: TeamDetailPageProps) {
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
<ConfirmDialog
|
||||
open={deleteOpen}
|
||||
onOpenChange={setDeleteOpen}
|
||||
title="Delete Team"
|
||||
description={`Are you sure you want to delete "${team.name}"? This action cannot be undone.`}
|
||||
confirmLabel="Delete"
|
||||
variant="destructive"
|
||||
onConfirm={async () => {
|
||||
await deleteTeam(teamId);
|
||||
setDeleteOpen(false);
|
||||
onBack();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -97,6 +97,7 @@ export function TeamsPage() {
|
||||
key={team.id}
|
||||
team={team}
|
||||
onClick={() => navigate(`/teams/${team.id}`)}
|
||||
onDelete={() => setDeleteTarget({ id: team.id, name: team.name })}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user