Workspaces layout, automation page
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
import { useState } from 'react';
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import rehypeRaw from 'rehype-raw';
|
||||
import { toast } from 'sonner';
|
||||
import { ArrowLeft, Pencil, Trash2 } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog';
|
||||
import { useClient } from 'hooks/useClient';
|
||||
import { usePanelChannel } from 'hooks/usePanelChannel';
|
||||
import { Card } from '@/components/Card';
|
||||
import { FrontmatterBlock } from '../CapabilityPage';
|
||||
import type { CapabilityDetail } from '../CapabilityPage';
|
||||
import type { AutomationSelection } from './AutomationRightPanel';
|
||||
|
||||
type CapabilityDetailViewProps = {
|
||||
kind: string;
|
||||
endpoint: string;
|
||||
queryKey: string;
|
||||
dirName: string;
|
||||
isNew?: boolean;
|
||||
editing?: boolean;
|
||||
};
|
||||
|
||||
export const CapabilityDetailView = ({ kind, endpoint, queryKey, dirName, editing }: CapabilityDetailViewProps) => {
|
||||
const client = useClient();
|
||||
const qc = useQueryClient();
|
||||
const [selection, setSelection] = usePanelChannel<AutomationSelection>('automation:selected-capability', null);
|
||||
const [deleteConfirm, setDeleteConfirm] = useState(false);
|
||||
|
||||
const { data: detail } = useQuery<CapabilityDetail>({
|
||||
queryKey: [queryKey, dirName],
|
||||
queryFn: () => client.get<CapabilityDetail>(`${endpoint}/${dirName}`),
|
||||
});
|
||||
|
||||
const toggleEditing = () => {
|
||||
if (!selection) return;
|
||||
setSelection({ ...selection, editing: !editing });
|
||||
};
|
||||
|
||||
const handleDelete = async () => {
|
||||
try {
|
||||
await client.delete(`${endpoint}/${dirName}`);
|
||||
setDeleteConfirm(false);
|
||||
await qc.invalidateQueries({ queryKey: [queryKey] });
|
||||
setSelection(null);
|
||||
} catch {
|
||||
toast.error(`Failed to delete ${kind}`);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card className="h-full overflow-hidden flex flex-col min-h-0">
|
||||
<div className="shrink-0 px-4 py-2 border-b border-duck-dark/10 bg-white/60 flex items-center gap-2">
|
||||
<button onClick={() => setSelection({ kind, endpoint, queryKey, dirName: '' })} className="p-1 -ml-1 rounded hover:bg-duck-dark/10 cursor-pointer">
|
||||
<ArrowLeft className="h-4 w-4 text-duck-dark/60" />
|
||||
</button>
|
||||
<span className="text-sm font-medium text-duck-dark/70 flex-1">{detail?.name ?? `Loading...`}</span>
|
||||
{detail && (
|
||||
<>
|
||||
<button
|
||||
onClick={toggleEditing}
|
||||
className={`p-1 rounded hover:bg-duck-dark/10 cursor-pointer transition-colors ${editing ? 'bg-duck-teal/10' : ''}`}
|
||||
>
|
||||
<Pencil className={`h-3.5 w-3.5 ${editing ? 'text-duck-teal' : 'text-duck-dark/50'}`} />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setDeleteConfirm(true)}
|
||||
className="p-1 rounded hover:bg-red-50 cursor-pointer transition-colors"
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5 text-duck-dark/50 hover:text-red-500" />
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="overflow-y-auto flex-1 p-6">
|
||||
{detail?.rawFrontmatter && <FrontmatterBlock yaml={detail.rawFrontmatter} />}
|
||||
{detail?.body ? (
|
||||
<article className="skill-md">
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]}>
|
||||
{detail.body}
|
||||
</ReactMarkdown>
|
||||
</article>
|
||||
) : detail ? (
|
||||
<p className="text-sm text-duck-dark/40 text-center mt-12">Empty file</p>
|
||||
) : null}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Dialog open={deleteConfirm} onOpenChange={setDeleteConfirm}>
|
||||
<DialogContent className="sm:max-w-md z-[700]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Delete {kind}</DialogTitle>
|
||||
<DialogDescription>
|
||||
Are you sure you want to delete "{detail?.name}"? This action cannot be undone.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="flex justify-end gap-2 mt-2">
|
||||
<Button variant="outline" onClick={() => setDeleteConfirm(false)} className="cursor-pointer">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="destructive" onClick={handleDelete} className="cursor-pointer">
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user