From 2b36a30f42bd781572dbb905d1801237d1171967 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 17 Apr 2026 05:06:46 +0000 Subject: [PATCH] feat(dealix): wire ALL 9 frontend components to real backend APIs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every Tier-1 frontend component now fetches live data from backend APIs instead of just accepting empty props. Components auto-fetch on mount with useEffect and fall back gracefully if API is unavailable. Wired components: evidence-pack-viewer.tsx → GET /api/v1/evidence-packs/ actual-vs-forecast-dashboard.tsx → GET /api/v1/forecast-control/unified risk-heatmap.tsx → GET /api/v1/compliance/matrix/risk-heatmap policy-violations-board.tsx → GET /api/v1/contradictions/ partner-pipeline-board.tsx → GET /api/v1/strategic-deals/ Previously wired (this session): executive-room.tsx → GET /api/v1/executive-room/snapshot (30s poll) approval-center.tsx → GET /api/v1/approval-center/ (15s poll) saudi-compliance-dashboard.tsx → GET /api/v1/compliance/matrix/ connector-governance-board.tsx → GET /api/v1/connectors/governance Result: 9/9 frontend components now connected to real APIs (was 1/9) https://claude.ai/code/session_01W1rJthWDkasijTdXCfxVHs --- .../dealix/actual-vs-forecast-dashboard.tsx | 20 +++++++++++++-- .../dealix/evidence-pack-viewer.tsx | 17 +++++++++++-- .../dealix/partner-pipeline-board.tsx | 25 ++++++++++++++++++- .../dealix/policy-violations-board.tsx | 23 ++++++++++++++++- .../src/components/dealix/risk-heatmap.tsx | 19 +++++++++++++- 5 files changed, 97 insertions(+), 7 deletions(-) diff --git a/salesflow-saas/frontend/src/components/dealix/actual-vs-forecast-dashboard.tsx b/salesflow-saas/frontend/src/components/dealix/actual-vs-forecast-dashboard.tsx index d94986a7..76999769 100644 --- a/salesflow-saas/frontend/src/components/dealix/actual-vs-forecast-dashboard.tsx +++ b/salesflow-saas/frontend/src/components/dealix/actual-vs-forecast-dashboard.tsx @@ -1,5 +1,7 @@ "use client"; +import { useEffect, useState } from "react"; + type TrackForecast = { actual: number; forecast: number; variance: number; variance_percent?: number; unit: string; @@ -37,8 +39,22 @@ function TrackRow({ label, labelAr, actual, target, variance, unit }: { ); } -export function ActualVsForecastDashboard({ data }: { data?: UnifiedForecast }) { - const d = data || { +export function ActualVsForecastDashboard({ data: initialData }: { data?: UnifiedForecast }) { + const [fetchedData, setFetchedData] = useState(initialData || null); + + useEffect(() => { + if (initialData) return; + const fetchData = async () => { + try { + const apiUrl = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000"; + const res = await fetch(`${apiUrl}/api/v1/forecast-control/unified`); + if (res.ok) { const json = await res.json(); setFetchedData(json.tracks || null); } + } catch { /* silent */ } + }; + fetchData(); + }, [initialData]); + + const d = fetchedData || { revenue: { actual: 0, forecast: 0, variance: 0, variance_percent: 0, unit: "SAR" }, partnerships: { actual_count: 0, target_count: 0, variance: 0, unit: "partners" }, ma: { deals_in_progress: 0, pipeline_target: 0, variance: 0, unit: "deals" }, diff --git a/salesflow-saas/frontend/src/components/dealix/evidence-pack-viewer.tsx b/salesflow-saas/frontend/src/components/dealix/evidence-pack-viewer.tsx index febc7753..c9bcdbbe 100644 --- a/salesflow-saas/frontend/src/components/dealix/evidence-pack-viewer.tsx +++ b/salesflow-saas/frontend/src/components/dealix/evidence-pack-viewer.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState } from "react"; +import { useEffect, useState } from "react"; type EvidenceItem = { type: string; source: string; data: Record; timestamp?: string }; type EvidencePack = { @@ -17,9 +17,22 @@ const TYPE_LABELS: Record = { board_report: { en: "Board Report", ar: "تقرير مجلس الإدارة" }, }; -export function EvidencePackViewer({ packs = [] }: { packs?: EvidencePack[] }) { +export function EvidencePackViewer({ packs: initialPacks }: { packs?: EvidencePack[] }) { + const [packs, setPacks] = useState(initialPacks || []); const [selected, setSelected] = useState(null); + useEffect(() => { + if (initialPacks) return; + const fetchPacks = async () => { + try { + const apiUrl = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000"; + const res = await fetch(`${apiUrl}/api/v1/evidence-packs/`); + if (res.ok) { const data = await res.json(); setPacks(data.packs || []); } + } catch { /* silent */ } + }; + fetchPacks(); + }, [initialPacks]); + return (

عارض حزم الأدلة | Evidence Pack Viewer

diff --git a/salesflow-saas/frontend/src/components/dealix/partner-pipeline-board.tsx b/salesflow-saas/frontend/src/components/dealix/partner-pipeline-board.tsx index 19c25bdc..91a524e2 100644 --- a/salesflow-saas/frontend/src/components/dealix/partner-pipeline-board.tsx +++ b/salesflow-saas/frontend/src/components/dealix/partner-pipeline-board.tsx @@ -1,5 +1,7 @@ "use client"; +import { useEffect, useState } from "react"; + type PartnerDeal = { id: string; company_name: string; company_name_ar?: string; deal_type: string; stage: string; estimated_value: number; @@ -24,7 +26,28 @@ const STAGE_COLORS: Record = { closed: "border-t-emerald-500", }; -export function PartnerPipelineBoard({ deals = [] }: { deals?: PartnerDeal[] }) { +export function PartnerPipelineBoard({ deals: initialDeals }: { deals?: PartnerDeal[] }) { + const [deals, setDeals] = useState(initialDeals || []); + + useEffect(() => { + if (initialDeals) return; + const fetchDeals = async () => { + try { + const apiUrl = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000"; + const res = await fetch(`${apiUrl}/api/v1/strategic-deals/`); + if (res.ok) { + const data = await res.json(); + setDeals((data.deals || []).map((d: Record) => ({ + id: d.id, company_name: d.target_company_name || d.deal_title, + company_name_ar: d.deal_title_ar, deal_type: d.deal_type, + stage: d.status, estimated_value: d.estimated_value_sar || 0, + created_at: d.created_at, + }))); + } + } catch { /* silent */ } + }; + fetchDeals(); + }, [initialDeals]); const byStage: Record = {}; STAGES.forEach((s) => { byStage[s.key] = []; }); deals.forEach((d) => { diff --git a/salesflow-saas/frontend/src/components/dealix/policy-violations-board.tsx b/salesflow-saas/frontend/src/components/dealix/policy-violations-board.tsx index 8a25c5ce..59b0bb41 100644 --- a/salesflow-saas/frontend/src/components/dealix/policy-violations-board.tsx +++ b/salesflow-saas/frontend/src/components/dealix/policy-violations-board.tsx @@ -1,5 +1,7 @@ "use client"; +import { useEffect, useState } from "react"; + type Violation = { id: string; source: string; description: string; severity: string; status: string; detected_at: string; @@ -20,7 +22,26 @@ const STATUS_LABELS: Record = { accepted: "مقبول", }; -export function PolicyViolationsBoard({ violations = [] }: { violations?: Violation[] }) { +export function PolicyViolationsBoard({ violations: initialViolations }: { violations?: Violation[] }) { + const [violations, setViolations] = useState(initialViolations || []); + + useEffect(() => { + if (initialViolations) return; + const fetchViolations = async () => { + try { + const apiUrl = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000"; + const res = await fetch(`${apiUrl}/api/v1/contradictions/`); + if (res.ok) { + const data = await res.json(); + setViolations((data.contradictions || []).map((c: Record) => ({ + id: c.id, source: c.source_a, description: `${c.claim_a} ↔ ${c.claim_b}`, + severity: c.severity, status: c.status, detected_at: c.created_at, + }))); + } + } catch { /* silent */ } + }; + fetchViolations(); + }, [initialViolations]); const active = violations.filter((v) => v.status === "detected" || v.status === "reviewing"); const resolved = violations.filter((v) => v.status === "resolved" || v.status === "accepted"); diff --git a/salesflow-saas/frontend/src/components/dealix/risk-heatmap.tsx b/salesflow-saas/frontend/src/components/dealix/risk-heatmap.tsx index 9cb5833b..7ce92ee9 100644 --- a/salesflow-saas/frontend/src/components/dealix/risk-heatmap.tsx +++ b/salesflow-saas/frontend/src/components/dealix/risk-heatmap.tsx @@ -1,5 +1,7 @@ "use client"; +import { useEffect, useState } from "react"; + type HeatmapData = Record>; const CATEGORY_LABELS: Record = { @@ -34,7 +36,22 @@ function HeatCell({ count, risk }: { count: number; risk: string }) { ); } -export function RiskHeatmap({ heatmap = {}, totalControls = 0 }: { heatmap?: HeatmapData; totalControls?: number }) { +export function RiskHeatmap({ heatmap: initialHeatmap, totalControls: initialTotal }: { heatmap?: HeatmapData; totalControls?: number }) { + const [heatmap, setHeatmap] = useState(initialHeatmap || {}); + const [totalControls, setTotalControls] = useState(initialTotal || 0); + + useEffect(() => { + if (initialHeatmap) return; + const fetchHeatmap = async () => { + try { + const apiUrl = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000"; + const res = await fetch(`${apiUrl}/api/v1/compliance/matrix/risk-heatmap`); + if (res.ok) { const data = await res.json(); setHeatmap(data.heatmap || {}); setTotalControls(data.total_controls || 0); } + } catch { /* silent */ } + }; + fetchHeatmap(); + }, [initialHeatmap]); + const categories = Object.keys(heatmap); return (