diff --git a/apps/ops/src/App.tsx b/apps/ops/src/App.tsx index e31873bf..8f1f12dd 100644 --- a/apps/ops/src/App.tsx +++ b/apps/ops/src/App.tsx @@ -1,7 +1,39 @@ import { useClient } from "@sanity/sdk-react"; import { useCallback, useEffect, useState } from "react"; -type WorkspaceTab = "crm" | "bookings" | "billing" | "syndication"; +type WorkspaceTab = "crm" | "bookings" | "billing" | "syndication" | "drafts"; + +interface DraftContentItem { + _id: string; + _type: "post" | "podcast" | "course" | "lesson"; + title?: string; + slug?: string; + _updatedAt?: string; + _createdAt?: string; + excerpt?: string; + contentLen?: number; + hasCover?: boolean; + hasPublishedVersion?: boolean; +} + +interface TrendSignalDoc { + _id: string; + title: string; + slug?: string; + source?: string; + sourceUrl?: string; + score?: number; + status?: string; + summary?: string; + rawMetrics?: string; + discoveredAt?: string; + socialDrafts?: { + x?: string; + linkedin?: string; + reddit?: string; + dailydev?: string; + }; +} interface DeliverableItem { _key?: string; @@ -173,6 +205,9 @@ export function App() { const [syndicationFilter, setSyndicationFilter] = useState< "all" | "missing" | "youtube" >("all"); + const [draftItems, setDraftItems] = useState([]); + const [trendSignals, setTrendSignals] = useState([]); + const [expandedTrendId, setExpandedTrendId] = useState(null); // Outbound scraper state const [discovering, setDiscovering] = useState(false); @@ -209,7 +244,10 @@ export function App() { bookings: BookingDoc[]; syndicatedContent: SyndicatedContentItem[]; syndicationDocCount: number; - }>(`{ + draftItems: DraftContentItem[]; + trendSignals: TrendSignalDoc[]; + }>( + `{ "leads": *[_type == "sponsorLead" && !(_id in path("drafts.**"))] | order(coalesce(lastEmailAt, _updatedAt, _createdAt) desc), "bookings": *[_type == "booking" && !(_id in path("drafts.**"))] | order(coalesce(slotStartAt, _createdAt) desc), "syndicatedContent": *[_type in ["post", "podcast"] && !(_id in path("drafts.**"))] | order(coalesce(date, _createdAt) desc)[0...50] { @@ -226,14 +264,43 @@ export function App() { medium, reddit }, - "syndicationDocCount": count(*[_type == "syndication"]) - }`); + "syndicationDocCount": count(*[_type == "syndication"]), + "draftItems": *[_type in ["post", "podcast", "course", "lesson"] && _id in path("drafts.**")] | order(_updatedAt desc) { + _id, + _type, + title, + "slug": slug.current, + _updatedAt, + _createdAt, + excerpt, + "contentLen": count(content), + "hasCover": defined(coverImage.asset) + }, + "trendSignals": *[_type == "trendSignal"] | order(coalesce(discoveredAt, _createdAt) desc)[0...40] { + _id, + title, + "slug": slug.current, + source, + sourceUrl, + score, + status, + summary, + rawMetrics, + discoveredAt, + socialDrafts + } + }`, + {}, + { perspective: "raw" }, + ); if (res) { setLeads(res.leads || []); setBookings(res.bookings || []); setSyndicatedContent(res.syndicatedContent || []); setSyndicationDocCount(res.syndicationDocCount || 0); + setDraftItems(res.draftItems || []); + setTrendSignals(res.trendSignals || []); } } catch (err: any) { console.error("Failed to fetch Ops data:", err); @@ -404,6 +471,43 @@ export function App() { } }; + const handleDeleteDraft = async (draftId: string, title?: string) => { + try { + await client.delete(draftId); + setDraftItems((prev) => prev.filter((d) => d._id !== draftId)); + showFlash(`Deleted draft "${title || draftId}"`); + } catch (err: any) { + showFlash(`Error deleting draft: ${err?.message || "Unknown error"}`); + } + }; + + const handleUpdateTrendStatus = async ( + trendId: string, + newStatus: string, + ) => { + try { + await client.patch(trendId).set({ status: newStatus }).commit(); + setTrendSignals((prev) => + prev.map((t) => + t._id === trendId ? { ...t, status: newStatus } : t, + ), + ); + showFlash(`Updated trend status to "${newStatus}"`); + } catch (err: any) { + showFlash(`Error updating trend: ${err?.message || "Unknown error"}`); + } + }; + + const handleDeleteTrend = async (trendId: string) => { + try { + await client.delete(trendId); + setTrendSignals((prev) => prev.filter((t) => t._id !== trendId)); + showFlash("Removed trend signal from radar"); + } catch (err: any) { + showFlash(`Error deleting trend: ${err?.message || "Unknown error"}`); + } + }; + const filteredSyndications = syndicatedContent.filter((item) => { if (syndicationFilter === "youtube") return Boolean(item.youtube); if (syndicationFilter === "missing") { @@ -581,6 +685,23 @@ export function App() { > 4. Content Syndication ({syndicatedContent.length} Posts) +