mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-05 21:39:54 +02:00
feat(squads): skeleton loader + AlertDialog archive confirm (MUL-2437) (#2890)
* feat(squads): skeleton loader + AlertDialog archive confirm (MUL-2437) - Replace `Loading...` text on the squads list with a Skeleton placeholder matching the SquadCard shape (avatar + title + subtitle), aligning with the Agents / Dashboard pattern. - Replace the native `confirm()` on the squad detail Archive button with the project's AlertDialog (destructive variant, pending-disabled, i18n copy interpolating the squad name). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * fix(squads): drop misleading restore copy from archive confirm (MUL-2437) Archive is irreversible — there is no unarchive command (see apps/docs/content/docs/squads.mdx:113). Aligns dialog copy with docs: tells the user the action can't be undone and to create a new squad if they need the routing back. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
@@ -9,6 +9,13 @@
|
||||
"details_section": "Details",
|
||||
"archive_button": "Archive"
|
||||
},
|
||||
"archive_dialog": {
|
||||
"title": "Archive this squad?",
|
||||
"description": "\"{{name}}\" will be archived. Issues currently assigned to this squad will be transferred to its leader. This can't be undone — create a new squad if you need the routing back.",
|
||||
"cancel": "Cancel",
|
||||
"confirm": "Archive",
|
||||
"archiving": "Archiving…"
|
||||
},
|
||||
"name_editor": {
|
||||
"cancel": "Cancel"
|
||||
},
|
||||
|
||||
@@ -9,6 +9,13 @@
|
||||
"details_section": "详情",
|
||||
"archive_button": "归档"
|
||||
},
|
||||
"archive_dialog": {
|
||||
"title": "归档这个小队?",
|
||||
"description": "“{{name}}” 将被归档,该小队当前承接的 issue 会转交给小队负责人。此操作无法撤销,如需恢复路由请新建小队。",
|
||||
"cancel": "取消",
|
||||
"confirm": "归档",
|
||||
"archiving": "归档中…"
|
||||
},
|
||||
"name_editor": {
|
||||
"cancel": "取消"
|
||||
},
|
||||
|
||||
@@ -116,6 +116,7 @@ export function SquadDetailPage() {
|
||||
|
||||
const [showAddMember, setShowAddMember] = useState(false);
|
||||
const [showCreateAgent, setShowCreateAgent] = useState(false);
|
||||
const [confirmArchive, setConfirmArchive] = useState(false);
|
||||
|
||||
const updateSquadMut = useMutation({
|
||||
mutationFn: (data: { name?: string; description?: string; instructions?: string; avatar_url?: string; leader_id?: string }) => api.updateSquad(squadId, data),
|
||||
@@ -225,7 +226,7 @@ export function SquadDetailPage() {
|
||||
<SquadHeaderAvatar squad={squad} initials={initials} />
|
||||
<h1 className="text-sm font-medium">{squad.name}</h1>
|
||||
</div>
|
||||
<Button size="sm" variant="ghost" className="text-destructive hover:text-destructive" onClick={() => { if (confirm("Archive this squad? Issues will be transferred to the leader.")) deleteMut.mutate(); }}>
|
||||
<Button size="sm" variant="ghost" className="text-destructive hover:text-destructive" onClick={() => setConfirmArchive(true)}>
|
||||
<Trash2 className="size-3.5 mr-1" />
|
||||
{t(($) => $.inspector.archive_button)}
|
||||
</Button>
|
||||
@@ -288,6 +289,36 @@ export function SquadDetailPage() {
|
||||
onCreate={handleCreateAgent}
|
||||
/>
|
||||
)}
|
||||
|
||||
{confirmArchive && (
|
||||
<AlertDialog
|
||||
open
|
||||
onOpenChange={(v) => { if (!v && !deleteMut.isPending) setConfirmArchive(false); }}
|
||||
>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>{t(($) => $.archive_dialog.title)}</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
{t(($) => $.archive_dialog.description, { name: squad.name })}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel disabled={deleteMut.isPending}>
|
||||
{t(($) => $.archive_dialog.cancel)}
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={() => deleteMut.mutate()}
|
||||
disabled={deleteMut.isPending}
|
||||
className="bg-destructive text-white hover:bg-destructive/90"
|
||||
>
|
||||
{deleteMut.isPending
|
||||
? t(($) => $.archive_dialog.archiving)
|
||||
: t(($) => $.archive_dialog.confirm)}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import { PageHeader } from "../../layout/page-header";
|
||||
import { Users, Plus, Search, Bot, User } from "lucide-react";
|
||||
import { Button } from "@multica/ui/components/ui/button";
|
||||
import { Input } from "@multica/ui/components/ui/input";
|
||||
import { Skeleton } from "@multica/ui/components/ui/skeleton";
|
||||
import { ActorAvatar as ActorAvatarBase } from "@multica/ui/components/common/actor-avatar";
|
||||
import { useModalStore } from "@multica/core/modals";
|
||||
import type { Agent, Squad } from "@multica/core/types";
|
||||
@@ -83,7 +84,7 @@ export function SquadsPage() {
|
||||
|
||||
<div className="flex flex-1 min-h-0 flex-col overflow-hidden">
|
||||
{isLoading ? (
|
||||
<div className="p-6 text-muted-foreground text-sm">Loading...</div>
|
||||
<SquadsListSkeleton />
|
||||
) : squads.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center gap-3 py-16 text-center">
|
||||
<Users className="size-10 text-muted-foreground/50" />
|
||||
@@ -184,6 +185,30 @@ function ScopeButton({ active, label, count, onClick }: { active: boolean; label
|
||||
);
|
||||
}
|
||||
|
||||
function SquadsListSkeleton() {
|
||||
return (
|
||||
<>
|
||||
<div className="flex h-12 shrink-0 items-center gap-3 border-b px-4">
|
||||
<Skeleton className="h-8 w-full max-w-sm rounded-md" />
|
||||
<Skeleton className="h-7 w-32 rounded-md" />
|
||||
</div>
|
||||
<div className="flex-1 overflow-y-auto p-4">
|
||||
<div className="grid gap-3">
|
||||
{Array.from({ length: 4 }).map((_, i) => (
|
||||
<div key={i} className="flex items-center gap-3 sm:gap-4 rounded-lg border p-3 sm:p-4">
|
||||
<Skeleton className="h-9 w-9 shrink-0 rounded-md" />
|
||||
<div className="flex-1 space-y-2">
|
||||
<Skeleton className="h-4 w-1/3 rounded" />
|
||||
<Skeleton className="h-3 w-2/3 rounded" />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function SquadAvatar({ squad }: { squad: Squad }) {
|
||||
const initials = squad.name
|
||||
.split(" ")
|
||||
|
||||
Reference in New Issue
Block a user