From 1b038d4e54746c6ea0c6152db0017a3a40ca3917 Mon Sep 17 00:00:00 2001 From: ananthanarayanan-28 Date: Sat, 27 Jun 2026 21:58:57 +0530 Subject: [PATCH 1/2] feat: add Domain Files analytics view to admin dashboard with backend API support for listing shared domain datasets --- .../admin/analytics/agent-domain-files.tsx | 628 ++++++++++++++++++ .../admin/analytics/agent-domain.tsx | 1 - frontend/src/components/admin/view-tab.tsx | 89 ++- frontend/src/types/api.ts | 34 + qa-chatbot/src/promptly/admin/api/router.py | 215 +++++- qa-chatbot/src/promptly/admin/api/schemas.py | 37 ++ 6 files changed, 989 insertions(+), 15 deletions(-) create mode 100644 frontend/src/components/admin/analytics/agent-domain-files.tsx diff --git a/frontend/src/components/admin/analytics/agent-domain-files.tsx b/frontend/src/components/admin/analytics/agent-domain-files.tsx new file mode 100644 index 0000000..d2a4ecc --- /dev/null +++ b/frontend/src/components/admin/analytics/agent-domain-files.tsx @@ -0,0 +1,628 @@ +'use client'; + +import { useState, useEffect, useRef } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { api } from '@/lib/api'; +import type { AdminDomainItem, AdminDomainList, AdminDomainQAResponse } from '@/types/api'; + +const PER_PAGE = 20; + +// ── Helpers ─────────────────────────────────────────────────────────────────── + +function fmtDate(iso: string) { + return new Date(iso).toLocaleDateString(undefined, { + year: 'numeric', month: 'short', day: 'numeric', + }); +} + +function StatusBadge({ status }: { status: string }) { + const color = + status === 'completed' ? '#10b981' : + status === 'failed' ? '#f43f5e' : + status === 'cancelled' ? '#6b7280' : '#f59e0b'; + return ( + + {status.replace(/_/g, ' ')} + + ); +} + +// ── PDF Modal ───────────────────────────────────────────────────────────────── + +function PdfModal({ + domain, + onClose, +}: { + domain: AdminDomainItem; + onClose: () => void; +}) { + const [blobUrl, setBlobUrl] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(false); + const urlRef = useRef(null); + + useEffect(() => { + let cancelled = false; + setLoading(true); + setError(false); + + api.get(`/api/v1/admin/domain-prompts/${domain.domain_id}/pdf`, { + responseType: 'blob', + }).then(res => { + if (cancelled) return; + const url = URL.createObjectURL(res.data); + urlRef.current = url; + setBlobUrl(url); + setLoading(false); + }).catch(() => { + if (!cancelled) { setError(true); setLoading(false); } + }); + + return () => { + cancelled = true; + if (urlRef.current) { + URL.revokeObjectURL(urlRef.current); + urlRef.current = null; + } + }; + }, [domain.domain_id]); + + const handleDownload = () => { + if (!blobUrl) return; + const a = document.createElement('a'); + a.href = blobUrl; + a.download = `${domain.domain_name}.pdf`; + a.click(); + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Escape') onClose(); + }; + + return ( +
+ {/* Header bar */} +
+
+ + + + +
+ + {domain.domain_name} + + + {domain.user_email} + +
+
+
+ {blobUrl && ( + + )} + +
+
+ + {/* Body */} +
+ {loading && ( +
+ Loading PDF… +
+ )} + {error && ( +
+ + Failed to load PDF + + + Check that MinIO is reachable and the file exists. + +
+ )} + {blobUrl && ( +