diff --git a/apps/web-dashboard/src/app/analytics/page.tsx b/apps/web-dashboard/src/app/analytics/page.tsx new file mode 100644 index 0000000..9070945 --- /dev/null +++ b/apps/web-dashboard/src/app/analytics/page.tsx @@ -0,0 +1,341 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Badge } from "@/components/ui/badge"; +import { Progress } from "@/components/ui/progress"; +import { Button } from "@/components/ui/button"; +import { BarChart3, TrendingUp, TrendingDown, AlertCircle } from "lucide-react"; +import { apiClient, ChurnMetrics, MarketMetrics, PricingMetrics, MaintenanceMetrics } from "@/lib/api-client"; + +export default function AnalyticsPage() { + const [metrics, setMetrics] = useState<{ + churn?: ChurnMetrics; + market?: MarketMetrics; + maintenance?: MaintenanceMetrics; + pricing?: PricingMetrics; + } | null>(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchMetrics = async () => { + try { + setLoading(true); + setError(null); + + // Fetch all metrics in parallel + const [churnResponse, marketResponse, maintenanceResponse, pricingResponse] = await Promise.all([ + apiClient.getChurnMetrics(), + apiClient.getMarketMetrics(), + apiClient.getMaintenanceMetrics(), + apiClient.getPricingMetrics(), + ]); + + if (churnResponse.error || marketResponse.error || maintenanceResponse.error || pricingResponse.error) { + setError('Failed to fetch some metrics'); + } + + setMetrics({ + churn: churnResponse.data, + market: marketResponse.data, + maintenance: maintenanceResponse.data, + pricing: pricingResponse.data, + }); + } catch (err) { + console.error('Failed to fetch analytics metrics:', err); + setError('Failed to load analytics data'); + } finally { + setLoading(false); + } + }; + + fetchMetrics(); + }, []); + + if (loading) { + return ( +
+
+
+ ); + } + + if (error) { + return ( +
+
+ +

Error Loading Analytics

+

{error}

+ +
+
+ ); + } + + return ( +
+
+

Analytics Dashboard

+ +
+ + + + Overview + Churn Analysis + Market Analytics + Maintenance + Pricing + + + +
+ + + Total Subscribers + + + +
{metrics?.churn?.totalSubscribers?.toLocaleString() || 'N/A'}
+

+12.5% from last month

+
+
+ + + Churn Rate + + + +
{metrics?.churn?.churnRate ? `${metrics.churn.churnRate}%` : 'N/A'}
+

-0.3% from last month

+
+
+ + + Market Share + + + +
{metrics?.market?.marketShare ? `${(metrics.market.marketShare * 100).toFixed(3)}%` : 'N/A'}
+

+0.1% from last month

+
+
+ + + System Uptime + + + +
{metrics?.maintenance?.uptime ? `${metrics.maintenance.uptime}%` : 'N/A'}
+

Excellent

+
+
+
+
+ + +
+ + + Churn Metrics + Customer churn analysis for the current period + + +
+ Total Subscribers + {metrics?.churn?.totalSubscribers?.toLocaleString() || 'N/A'} +
+
+ Churned Subscribers + {metrics?.churn?.churnedSubscribers?.toLocaleString() || 'N/A'} +
+
+ Monthly Churn Rate + {metrics?.churn?.monthlyChurnRate ? `${metrics.churn.monthlyChurnRate}%` : 'N/A'} +
+
+ Average Tenure + {metrics?.churn?.averageTenureDays ? `${metrics.churn.averageTenureDays} days` : 'N/A'} +
+
+
+ + + Risk Distribution + Customers at risk of churn + + + {Object.entries(metrics?.churn?.riskDistribution || {}).map(([level, count]) => ( +
+
+ {level} + {count?.toLocaleString() || '0'} +
+ +
+ ))} +
+
+
+
+ + +
+ + + Market Metrics + Market penetration and growth + + +
+ Total Market Size + {metrics?.market?.totalMarketSize ? `${(metrics.market.totalMarketSize / 1000000000).toFixed(1)}B` : 'N/A'} +
+
+ Our Subscribers + {metrics?.market?.ourSubscribers?.toLocaleString() || 'N/A'} +
+
+ Market Share + {metrics?.market?.marketShare ? `${(metrics.market.marketShare * 100).toFixed(3)}%` : 'N/A'} +
+
+ Growth Rate + {metrics?.market?.growthRate ? `+${metrics.market.growthRate}%` : 'N/A'} +
+
+
+ + + Market by Country + Performance across regions + + + {Object.entries(metrics?.market?.byCountry || {}).map(([country, data]: [string, any]) => ( +
+
+ {country} + {data?.penetration ? `${(data.penetration * 100).toFixed(2)}%` : 'N/A'} +
+ +
+ ))} +
+
+
+
+ + +
+ + + System Health + Infrastructure maintenance metrics + + +
+ Total Assets + {metrics?.maintenance?.totalAssets || 'N/A'} +
+
+ Healthy Assets + {metrics?.maintenance?.healthyAssets || 'N/A'} +
+
+ Assets Needing Attention + {metrics?.maintenance?.assetsNeedingAttention || 'N/A'} +
+
+ System Uptime + {metrics?.maintenance?.uptime ? `${metrics.maintenance.uptime}%` : 'N/A'} +
+
+
+ + + Performance Metrics + Mean time metrics + + +
+ Mean Time To Failure + {metrics?.maintenance?.meanTimeToFailure ? `${(metrics.maintenance.meanTimeToFailure / 24 / 365).toFixed(1)} years` : 'N/A'} +
+
+ Mean Time To Repair + {metrics?.maintenance?.meanTimeToRepair ? `${metrics.maintenance.meanTimeToRepair} hours` : 'N/A'} +
+
+ Asset Health Score + Excellent +
+
+
+
+
+ + +
+ + + Revenue Metrics + Pricing and revenue analysis + + +
+ Total Revenue + {metrics?.pricing?.totalRevenue ? `${(metrics.pricing.totalRevenue / 1000000).toFixed(1)}M` : 'N/A'} +
+
+ ARPU + {metrics?.pricing?.arpu ? `$${metrics.pricing.arpu}` : 'N/A'} +
+
+ Price Elasticity + {metrics?.pricing?.priceElasticity || 'N/A'} +
+
+ Competitive Index + {metrics?.pricing?.competitiveIndex || 'N/A'} +
+
+
+ + + Optimization + Pricing optimization metrics + + +
+ Optimization ROI + {metrics?.pricing?.optimizationRoi ? `+${metrics.pricing.optimizationRoi}%` : 'N/A'} +
+
+ Price Elasticity + {metrics?.pricing?.priceElasticity || 'N/A'} +
+ +
+
+
+
+
+
+ ); +} diff --git a/apps/web-dashboard/src/app/churn/page.tsx b/apps/web-dashboard/src/app/churn/page.tsx new file mode 100644 index 0000000..29f0017 --- /dev/null +++ b/apps/web-dashboard/src/app/churn/page.tsx @@ -0,0 +1,272 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { Progress } from "@/components/ui/progress"; +import { Button } from "@/components/ui/button"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; +import { Alert, AlertDescription } from "@/components/ui/alert"; +import { TrendingDown, AlertTriangle, Users, Target } from "lucide-react"; +import { apiClient, ChurnMetrics, ChurnPrediction } from "@/lib/api-client"; + +export default function ChurnPage() { + const [predictions, setPredictions] = useState([]); + const [metrics, setMetrics] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [selectedRiskLevel, setSelectedRiskLevel] = useState("all"); + + useEffect(() => { + const fetchData = async () => { + try { + setLoading(true); + setError(null); + + // Fetch churn metrics and predictions in parallel + const [metricsResponse, predictionsResponse] = await Promise.all([ + apiClient.getChurnMetrics(), + apiClient.getChurnPredictions(selectedRiskLevel === "all" ? undefined : selectedRiskLevel, 100), + ]); + + if (metricsResponse.error || predictionsResponse.error) { + setError('Failed to fetch some churn data'); + } + + setMetrics(metricsResponse.data || null); + setPredictions(predictionsResponse.data || []); + } catch (err) { + console.error('Failed to fetch churn data:', err); + setError('Failed to load churn analysis data'); + } finally { + setLoading(false); + } + }; + + fetchData(); + }, [selectedRiskLevel]); + + const filteredPredictions = predictions.filter(p => + selectedRiskLevel === "all" || p.riskLevel === selectedRiskLevel + ); + + if (loading) { + return ( +
+
+
+ ); + } + + if (error) { + return ( +
+
+ +

Error Loading Churn Analysis

+

{error}

+ +
+
+ ); + } + + const getRiskBadgeVariant = (level: string) => { + switch (level) { + case "critical": return "destructive"; + case "high": return "destructive"; + case "medium": return "secondary"; + case "low": return "outline"; + default: return "secondary"; + } + }; + + const getRiskColor = (level: string) => { + switch (level) { + case "critical": return "text-red-600"; + case "high": return "text-orange-600"; + case "medium": return "text-yellow-600"; + case "low": return "text-green-600"; + default: return "text-gray-600"; + } + }; + + if (loading) { + return ( +
+
+
+ ); + } + + return ( +
+
+

+ + Churn Analysis +

+ +
+ + {/* Metrics Overview */} +
+ + + Total Subscribers + + + +
{metrics?.totalSubscribers.toLocaleString()}
+

Active customers

+
+
+ + + Churn Rate + + + +
{metrics?.churnRate}%
+

Monthly churn

+
+
+ + + At Risk Customers + + + +
7,500
+

High & critical risk

+
+
+ + + Avg Tenure + + + +
{metrics?.averageTenureDays} days
+

Customer lifetime

+
+
+
+ + {/* Risk Distribution */} + + + Risk Distribution + Customer churn risk levels across the subscriber base + + + {Object.entries(metrics?.riskDistribution || {}).map(([level, count]) => ( +
+
+ {level} +
+ {count?.toLocaleString()} + {level} +
+
+ +
+ ))} +
+
+ + {/* Filter Controls */} +
+ Filter by risk level: +
+ {["all", "critical", "high", "medium", "low"].map((level) => ( + + ))} +
+
+ + {/* At-Risk Customers Table */} + + + At-Risk Customers + Customers with elevated churn risk requiring attention + + + + + + Profile ID + Risk Level + Risk Score + Predicted Churn + Key Reasons + Actions + + + + {filteredPredictions.map((prediction) => ( + + {prediction.profileId} + + + {prediction.riskLevel} + + + + + {prediction.riskScore}% + + + {prediction.predictedChurnDate || "N/A"} + +
+

+ {prediction.reasons.join(", ")} +

+
+
+ + + +
+ ))} +
+
+
+
+ + {/* Customer Retention Recommendations */} +
+ + + + Critical Risk: 1,500 customers at immediate risk of churn. Recommend immediate outreach with personalized retention offers. + + + + + + Proactive Strategy: Focus on high-risk segment with targeted campaigns to reduce churn by 25% this quarter. + + +
+
+ ); +} diff --git a/apps/web-dashboard/src/app/fraud/page.tsx b/apps/web-dashboard/src/app/fraud/page.tsx new file mode 100644 index 0000000..a9afb67 --- /dev/null +++ b/apps/web-dashboard/src/app/fraud/page.tsx @@ -0,0 +1,305 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Alert, AlertDescription } from "@/components/ui/alert"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; +import { AlertTriangle, Shield, Eye, CheckCircle, XCircle, Clock } from "lucide-react"; +import { apiClient, FraudAlert, FraudMetrics } from "@/lib/api-client"; + +export default function FraudPage() { + const [alerts, setAlerts] = useState([]); + const [metrics, setMetrics] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [selectedSeverity, setSelectedSeverity] = useState("all"); + + useEffect(() => { + const fetchData = async () => { + try { + setLoading(true); + setError(null); + + // Fetch fraud metrics and alerts in parallel + const [metricsResponse, alertsResponse] = await Promise.all([ + apiClient.getFraudMetrics(), + apiClient.getFraudAlerts(selectedSeverity === "all" ? undefined : selectedSeverity), + ]); + + if (metricsResponse.error || alertsResponse.error) { + setError('Failed to fetch some fraud data'); + } + + setMetrics(metricsResponse.data || null); + setAlerts(alertsResponse.data || []); + } catch (err) { + console.error('Failed to fetch fraud data:', err); + setError('Failed to load fraud detection data'); + } finally { + setLoading(false); + } + }; + + fetchData(); + }, [selectedSeverity]); + + const filteredAlerts = alerts.filter(a => + selectedSeverity === "all" || a.severity === selectedSeverity + ); + + if (loading) { + return ( +
+
+
+ ); + } + + if (error) { + return ( +
+
+ +

Error Loading Fraud Detection

+

{error}

+ +
+
+ ); + } + + const getSeverityBadgeVariant = (severity: string) => { + switch (severity) { + case "critical": return "destructive"; + case "high": return "destructive"; + case "medium": return "secondary"; + case "low": return "outline"; + default: return "secondary"; + } + }; + + const getStatusIcon = (status: string) => { + switch (status) { + case "new": return ; + case "investigating": return ; + case "resolved": return ; + case "blocked": return ; + default: return ; + } + }; + + const getStatusBadgeVariant = (status: string) => { + switch (status) { + case "new": return "outline"; + case "investigating": return "secondary"; + case "resolved": return "default"; + case "blocked": return "destructive"; + default: return "outline"; + } + }; + + if (loading) { + return ( +
+
+
+ ); + } + + return ( +
+
+

+ + Fraud Detection +

+ +
+ + {/* Metrics Overview */} +
+ + + Total Alerts + + + +
{metrics?.totalAlerts.toLocaleString()}
+

Last 30 days

+
+
+ + + Resolution Rate + + + +
{metrics?.resolutionRate}%
+

Alerts resolved

+
+
+ + + False Positives + + + +
{metrics?.falsePositiveRate}%
+

Accuracy improving

+
+
+ + + Critical Alerts + + + +
{metrics?.bySeverity.critical}
+

Immediate action needed

+
+
+
+ + {/* Alert Types Distribution */} +
+ + + Alerts by Type + Distribution of fraud detection alerts + + + {Object.entries(metrics?.byType || {}).map(([type, count]) => ( +
+ {type.replace("_", " ")} + {count?.toLocaleString()} +
+ ))} +
+
+ + + Alerts by Severity + Risk level distribution + + + {Object.entries(metrics?.bySeverity || {}).map(([severity, count]) => ( +
+ {severity} + {severity} +
+ ))} +
+
+
+ + {/* Filter Controls */} +
+ Filter by severity: +
+ {["all", "critical", "high", "medium", "low"].map((severity) => ( + + ))} +
+
+ + {/* Recent Fraud Alerts */} + + + Recent Fraud Alerts + Latest security alerts requiring attention + + + + + + Alert ID + Type + Severity + Profile + Risk Score + Description + Status + Actions + + + + {filteredAlerts.map((alert) => ( + + {alert.id} + + + {alert.type.replace("_", " ")} + + + + + {alert.severity} + + + {alert.profileId} + + = 90 ? 'text-red-600' : + alert.riskScore >= 75 ? 'text-orange-600' : + alert.riskScore >= 50 ? 'text-yellow-600' : 'text-green-600' + }`}> + {alert.riskScore}% + + + +
+

{alert.description}

+
+
+ +
+ {getStatusIcon(alert.status)} + + {alert.status} + +
+
+ + + +
+ ))} +
+
+
+
+ + {/* Security Recommendations */} +
+ + + + Critical Alert: Account takeover attempt detected. Immediate action required to secure the account and prevent unauthorized access. + + + + + + Security Tip: Consider implementing multi-factor authentication for high-risk accounts to prevent account takeover attempts. + + +
+
+ ); +} diff --git a/apps/web-dashboard/src/app/maintenance/page.tsx b/apps/web-dashboard/src/app/maintenance/page.tsx new file mode 100644 index 0000000..c926c79 --- /dev/null +++ b/apps/web-dashboard/src/app/maintenance/page.tsx @@ -0,0 +1,365 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { Progress } from "@/components/ui/progress"; +import { Button } from "@/components/ui/button"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; +import { Alert, AlertDescription } from "@/components/ui/alert"; +import { AlertTriangle, Wrench, Clock, CheckCircle, XCircle, Server, Database, Wifi } from "lucide-react"; +import { apiClient, MaintenanceMetrics } from "@/lib/api-client"; + +interface Asset { + id: string; + name: string; + type: string; + health: number; + status: string; + lastMaintenance: string; + nextMaintenance?: string; + predictedFailure?: string; + riskFactors: string[]; +} + +export default function MaintenancePage() { + const [assets, setAssets] = useState([]); + const [metrics, setMetrics] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + // Simulate API calls + setTimeout(() => { + setAssets([ + { + id: "server-1", + name: "Web Server 1", + type: "server", + health: 85, + status: "healthy", + lastMaintenance: "2026-04-15T10:30:00Z", + nextMaintenance: "2026-06-15T10:30:00Z", + riskFactors: [], + }, + { + id: "server-2", + name: "Web Server 2", + type: "server", + health: 92, + status: "healthy", + lastMaintenance: "2026-04-20T14:15:00Z", + nextMaintenance: "2026-06-20T14:15:00Z", + riskFactors: [], + }, + { + id: "db-1", + name: "Primary Database", + type: "database", + health: 78, + status: "warning", + lastMaintenance: "2026-03-10T09:00:00Z", + nextMaintenance: "2026-05-10T09:00:00Z", + riskFactors: ["High CPU usage", "Memory pressure"], + }, + { + id: "router-1", + name: "Core Router", + type: "network", + health: 65, + status: "warning", + lastMaintenance: "2026-02-28T16:45:00Z", + nextMaintenance: "2026-04-28T16:45:00Z", + predictedFailure: "2026-09-01T00:00:00Z", + riskFactors: ["Age", "Error rate increase", "Temperature spikes"], + }, + ]); + + setMetrics({ + totalAssets: 1250, + healthyAssets: 1180, + assetsNeedingAttention: 70, + uptime: 99.95, + meanTimeToFailure: 8760, + meanTimeToRepair: 4, + byType: { + server: 450, + database: 125, + network: 300, + storage: 375, + }, + byStatus: { + healthy: 1180, + warning: 60, + critical: 10, + }, + }); + setLoading(false); + }, 1000); + }, []); + + const getHealthColor = (health: number) => { + if (health >= 90) return "text-green-600"; + if (health >= 75) return "text-yellow-600"; + if (health >= 60) return "text-orange-600"; + return "text-red-600"; + }; + + const getStatusBadgeVariant = (status: string) => { + switch (status) { + case "healthy": return "default"; + case "warning": return "secondary"; + case "critical": return "destructive"; + default: return "outline"; + } + }; + + const getStatusIcon = (status: string) => { + switch (status) { + case "healthy": return ; + case "warning": return ; + case "critical": return ; + default: return ; + } + }; + + const getAssetIcon = (type: string) => { + switch (type) { + case "server": return ; + case "database": return ; + case "network": return ; + default: return ; + } + }; + + if (loading) { + return ( +
+
+
+ ); + } + + return ( +
+
+

+ + Predictive Maintenance +

+ +
+ + {/* Maintenance Metrics */} +
+ + + Total Assets + + + +
{metrics?.totalAssets.toLocaleString()}
+

Infrastructure assets

+
+
+ + + Healthy Assets + + + +
{metrics?.healthyAssets.toLocaleString()}
+

Operating normally

+
+
+ + + System Uptime + + + +
{metrics?.uptime}%
+

Excellent availability

+
+
+ + + MTTR + + + +
{metrics?.meanTimeToRepair}h
+

Mean time to repair

+
+
+
+ + {/* Asset Distribution */} +
+ + + Assets by Type + Infrastructure asset distribution + + + {Object.entries(metrics?.byType || {}).map(([type, count]) => ( +
+
+ {getAssetIcon(type)} + {type} +
+ {count?.toLocaleString()} +
+ ))} +
+
+ + + Assets by Status + Current health status distribution + + + {Object.entries(metrics?.byStatus || {}).map(([status, count]) => ( +
+
+ {getStatusIcon(status)} + {status} +
+ {count?.toLocaleString()} +
+ ))} +
+
+
+ + {/* Assets Health Table */} + + + Asset Health Status + Real-time health monitoring of critical infrastructure assets + + + + + + Asset + Type + Health + Status + Last Maintenance + Next Maintenance + Predicted Failure + Actions + + + + {assets.map((asset) => ( + + {asset.name} + +
+ {getAssetIcon(asset.type)} + {asset.type} +
+
+ +
+ + {asset.health}% + + +
+
+ +
+ {getStatusIcon(asset.status)} + + {asset.status} + +
+
+ + {new Date(asset.lastMaintenance).toLocaleDateString()} + + + {asset.nextMaintenance ? new Date(asset.nextMaintenance).toLocaleDateString() : "N/A"} + + + {asset.predictedFailure ? ( + + {new Date(asset.predictedFailure).toLocaleDateString()} + + ) : ( + N/A + )} + + + + +
+ ))} +
+
+
+
+ + {/* Risk Factors and Predictions */} +
+ {assets.filter(a => a.riskFactors.length > 0).map((asset) => ( + + + + {asset.name} + {asset.status} + + + +
+

Risk Factors

+
    + {asset.riskFactors.map((factor, idx) => ( +
  • + • + {factor} +
  • + ))} +
+
+ {asset.predictedFailure && ( +
+

Failure Prediction

+

+ Predicted failure on {new Date(asset.predictedFailure).toLocaleDateString()} with 82.5% confidence +

+
+ )} + +
+
+ ))} +
+ + {/* Maintenance Recommendations */} +
+ + + + Critical Maintenance Required: Core Router shows 65% health with predicted failure in September. Schedule immediate maintenance to prevent service disruption. + + + + + + Preventive Maintenance: Primary Database showing warning signs. Schedule maintenance within 30 days to maintain optimal performance. + + +
+
+ ); +} diff --git a/apps/web-dashboard/src/app/pricing/page.tsx b/apps/web-dashboard/src/app/pricing/page.tsx new file mode 100644 index 0000000..40a2804 --- /dev/null +++ b/apps/web-dashboard/src/app/pricing/page.tsx @@ -0,0 +1,221 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { Progress } from "@/components/ui/progress"; +import { Button } from "@/components/ui/button"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; +import { Alert, AlertDescription } from "@/components/ui/alert"; +import { TrendingUp, TrendingDown, DollarSign, Target, Zap, BarChart3, AlertTriangle } from "lucide-react"; +import { apiClient, PricingMetrics, PricingOptimizationResult } from "@/lib/api-client"; + +export default function PricingPage() { + const [metrics, setMetrics] = useState(null); + const [optimizations, setOptimizations] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [selectedStrategy, setSelectedStrategy] = useState("revenue_maximization"); + + useEffect(() => { + const fetchData = async () => { + try { + setLoading(true); + setError(null); + + const metricsResponse = await apiClient.getPricingMetrics(); + + if (metricsResponse.error) { + setError('Failed to fetch pricing metrics'); + } + + setMetrics(metricsResponse.data || null); + } catch (err) { + console.error('Failed to fetch pricing data:', err); + setError('Failed to load pricing optimization data'); + } finally { + setLoading(false); + } + }; + + fetchData(); + }, []); + + const handleOptimizePricing = async () => { + try { + setLoading(true); + const response = await apiClient.optimizePricing(['plan-1', 'plan-2', 'plan-3'], selectedStrategy); + + if (response.error) { + setError('Failed to optimize pricing'); + } else { + setOptimizations(response.data || []); + } + } catch (err) { + console.error('Failed to optimize pricing:', err); + setError('Failed to optimize pricing'); + } finally { + setLoading(false); + } + }; + + const getStrategyBadgeVariant = (strategy: string) => { + switch (strategy) { + case "revenue_maximization": return "default"; + case "market_share": return "secondary"; + case "profit_margin": return "outline"; + case "competitive": return "destructive"; + default: return "secondary"; + } + }; + + const getChangeIcon = (change: number) => { + return change >= 0 ? : ; + }; + + const getChangeColor = (change: number) => { + return change >= 0 ? "text-green-600" : "text-red-600"; + }; + + const getConfidenceColor = (confidence: number) => { + if (confidence >= 85) return "text-green-600"; + if (confidence >= 70) return "text-yellow-600"; + return "text-red-600"; + }; + + if (loading) { + return ( +
+
+
+ ); + } + + if (error) { + return ( +
+
+ +

Error Loading Pricing Optimization

+

{error}

+ +
+
+ ); + } + + return ( +
+
+
+

Pricing Optimization

+

Optimize pricing strategies for maximum revenue and market share

+
+
+ + +
+
+ + {/* Metrics Cards */} +
+ + + Total Revenue + + + +
${metrics?.totalRevenue ? (metrics.totalRevenue / 1000000).toFixed(1) : '0'}M
+
+
+ + + ARPU + + + +
${metrics?.arpu || '0'}
+
+
+ + + Price Elasticity + + + +
{metrics?.priceElasticity || '0'}
+
+
+ + + Optimization ROI + + + +
+{metrics?.optimizationRoi || '0'}%
+
+
+
+ + {/* Optimization Results */} + {optimizations.length > 0 && ( + + + Optimization Results + Recommended pricing adjustments based on {selectedStrategy} strategy + + + + + + Rate Plan + Current Price + Optimal Price + Change + Expected Revenue + Confidence + + + + {optimizations.map((opt, index) => ( + + {opt.ratePlanId} + ${opt.currentPrice.toFixed(2)} + ${opt.optimalPrice.toFixed(2)} + +
+ {getChangeIcon(opt.priceChangePct)} + + {opt.priceChangePct > 0 ? '+' : ''}{opt.priceChangePct.toFixed(1)}% + +
+
+ ${(opt.expectedRevenue / 1000000).toFixed(2)}M + + = 80 ? "default" : "secondary"}> + {opt.confidence.toFixed(0)}% + + +
+ ))} +
+
+
+
+ )} +
+ ); +} diff --git a/apps/web-dashboard/src/components/sidebar.tsx b/apps/web-dashboard/src/components/sidebar.tsx index 892de9c..fc4da09 100644 --- a/apps/web-dashboard/src/components/sidebar.tsx +++ b/apps/web-dashboard/src/components/sidebar.tsx @@ -11,6 +11,11 @@ import { Settings, Radio, HeartPulse, + TrendingDown, + AlertTriangle, + BarChart3, + DollarSign, + Wrench, } from "lucide-react"; import { cn } from "@/lib/utils"; @@ -20,6 +25,11 @@ const navItems = [ { href: "/usage", label: "Usage & Billing", icon: Activity }, { href: "/payments", label: "Payments", icon: CreditCard }, { href: "/esim", label: "eSIM Profiles", icon: Radio }, + { href: "/analytics", label: "Analytics", icon: BarChart3 }, + { href: "/churn", label: "Churn Analysis", icon: TrendingDown }, + { href: "/fraud", label: "Fraud Detection", icon: AlertTriangle }, + { href: "/pricing", label: "Pricing", icon: DollarSign }, + { href: "/maintenance", label: "Maintenance", icon: Wrench }, { href: "/health", label: "System Health", icon: HeartPulse }, { href: "/chaos", label: "Chaos Engineering", icon: Shield }, { href: "/settings", label: "Settings", icon: Settings }, diff --git a/apps/web-dashboard/src/components/ui/progress.tsx b/apps/web-dashboard/src/components/ui/progress.tsx new file mode 100644 index 0000000..07bf0b2 --- /dev/null +++ b/apps/web-dashboard/src/components/ui/progress.tsx @@ -0,0 +1,34 @@ +"use client" + +import * as React from "react" +import { cn } from "@/lib/utils" + +interface ProgressProps { + value?: number + max?: number + className?: string +} + +const Progress = React.forwardRef( + ({ value = 0, max = 100, className }, ref) => { + const percentage = Math.min(Math.max((value / max) * 100, 0), 100) + + return ( +
+
+
+ ) + } +) +Progress.displayName = "Progress" + +export { Progress } diff --git a/apps/web-dashboard/src/components/ui/table.tsx b/apps/web-dashboard/src/components/ui/table.tsx new file mode 100644 index 0000000..245eb00 --- /dev/null +++ b/apps/web-dashboard/src/components/ui/table.tsx @@ -0,0 +1,118 @@ +"use client" + +import * as React from "react" +import { cn } from "@/lib/utils" + +const Table = React.forwardRef< + HTMLTableElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+ + +)) +Table.displayName = "Table" + +const TableHeader = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableHeader.displayName = "TableHeader" + +const TableBody = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableBody.displayName = "TableBody" + +const TableFooter = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + tr]:last:border-b-0", + className + )} + {...props} + /> +)) +TableFooter.displayName = "TableFooter" + +const TableRow = React.forwardRef< + HTMLTableRowElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableRow.displayName = "TableRow" + +const TableHead = React.forwardRef< + HTMLTableCellElement, + React.ThHTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +TableHead.displayName = "TableHead" + +const TableCell = React.forwardRef< + HTMLTableCellElement, + React.TdHTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableCell.displayName = "TableCell" + +const TableCaption = React.forwardRef< + HTMLTableCaptionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +TableCaption.displayName = "TableCaption" + +export { + Table, + TableHeader, + TableBody, + TableFooter, + TableHead, + TableRow, + TableCell, + TableCaption, +} diff --git a/apps/web-dashboard/src/components/ui/tabs.tsx b/apps/web-dashboard/src/components/ui/tabs.tsx new file mode 100644 index 0000000..958861e --- /dev/null +++ b/apps/web-dashboard/src/components/ui/tabs.tsx @@ -0,0 +1,106 @@ +"use client" + +import * as React from "react" +import { cn } from "@/lib/utils" + +interface TabsProps { + defaultValue?: string + value?: string + onValueChange?: (value: string) => void + children: React.ReactNode + className?: string +} + +interface TabsListProps { + children: React.ReactNode + className?: string +} + +interface TabsTriggerProps { + value: string + children: React.ReactNode + className?: string + disabled?: boolean +} + +interface TabsContentProps { + value: string + children: React.ReactNode + className?: string +} + +const TabsContext = React.createContext<{ + value?: string + onValueChange?: (value: string) => void +}>({}) + +const Tabs: React.FC = ({ defaultValue, value, onValueChange, children, className }) => { + const [internalValue, setInternalValue] = React.useState(defaultValue) + const currentValue = value ?? internalValue + const handleValueChange = onValueChange ?? setInternalValue + + return ( + +
+ {children} +
+
+ ) +} + +const TabsList: React.FC = ({ children, className }) => { + return ( +
+ {children} +
+ ) +} + +const TabsTrigger: React.FC = ({ value, children, className, disabled }) => { + const { value: currentValue, onValueChange } = React.useContext(TabsContext) + const isActive = currentValue === value + + return ( + + ) +} + +const TabsContent: React.FC = ({ value, children, className }) => { + const { value: currentValue } = React.useContext(TabsContext) + const isActive = currentValue === value + + if (!isActive) { + return null + } + + return ( +
+ {children} +
+ ) +} + +export { Tabs, TabsList, TabsTrigger, TabsContent } diff --git a/apps/web-dashboard/src/lib/api-client.ts b/apps/web-dashboard/src/lib/api-client.ts new file mode 100644 index 0000000..e0f461e --- /dev/null +++ b/apps/web-dashboard/src/lib/api-client.ts @@ -0,0 +1,261 @@ +const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:8080'; + +export interface ApiResponse { + data?: T; + error?: string; + message?: string; +} + +export interface ChurnMetrics { + totalSubscribers: number; + churnedSubscribers: number; + churnRate: number; + monthlyChurnRate: number; + annualChurnRate: number; + averageTenureDays: number; + riskDistribution: { + low: number; + medium: number; + high: number; + critical: number; + }; +} + +export interface ChurnPrediction { + profileId: string; + riskLevel: string; + riskScore: number; + predictedChurnDate?: string; + reasons: string[]; + recommendations: string[]; + lastUpdated: string; +} + +export interface FraudAlert { + id: string; + type: string; + severity: string; + profileId: string; + description: string; + riskScore: number; + evidence: string[]; + ipAddress: string; + timestamp: string; + status: string; + actionsTaken: string[]; +} + +export interface FraudMetrics { + totalAlerts: number; + resolvedAlerts: number; + falsePositives: number; + resolutionRate: number; + falsePositiveRate: number; + byType: Record; + bySeverity: Record; +} + +export interface MarketMetrics { + totalMarketSize: number; + ourSubscribers: number; + marketShare: number; + growthRate: number; + byCountry: Record; +} + +export interface PricingMetrics { + totalRevenue: number; + arpu: number; + priceElasticity: number; + competitiveIndex: number; + optimizationRoi: number; + byPlan: Record; + byRegion: Record; +} + +export interface PricingOptimizationResult { + ratePlanId: string; + strategy: string; + currentPrice: number; + optimalPrice: number; + priceChangePct: number; + expectedRevenue: number; + expectedDemand: number; + confidence: number; + reasoning: string[]; + risks: string[]; + recommendations: string[]; +} + +export interface MaintenanceMetrics { + totalAssets: number; + healthyAssets: number; + assetsNeedingAttention: number; + uptime: number; + meanTimeToFailure: number; + meanTimeToRepair: number; + byType: Record; + byStatus: Record; +} + +export interface Asset { + id: string; + name: string; + type: string; + health: number; + status: string; + lastMaintenance: string; + nextMaintenance?: string; + predictedFailure?: string; + riskFactors: string[]; +} + +class ApiClient { + private baseUrl: string; + private headers: Record; + + constructor() { + this.baseUrl = API_BASE_URL; + this.headers = { + 'Content-Type': 'application/json', + }; + + // Add auth token if available + const token = this.getAuthToken(); + if (token) { + this.headers['Authorization'] = `Bearer ${token}`; + } + } + + private getAuthToken(): string | null { + if (typeof window !== 'undefined') { + return localStorage.getItem('auth_token'); + } + return null; + } + + private async request( + endpoint: string, + options: RequestInit = {} + ): Promise> { + try { + const url = `${this.baseUrl}${endpoint}`; + const response = await fetch(url, { + ...options, + headers: { + ...this.headers, + ...options.headers, + }, + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error(errorData.message || `HTTP ${response.status}: ${response.statusText}`); + } + + const data = await response.json(); + return { data }; + } catch (error) { + console.error('API request failed:', error); + return { error: error instanceof Error ? error.message : 'Unknown error' }; + } + } + + // Analytics API + async getChurnMetrics(period: string = 'monthly'): Promise> { + return this.request(`/api/v1/analytics/churn/metrics?period=${period}`); + } + + async getChurnPredictions(riskLevel?: string, limit?: number): Promise> { + const params = new URLSearchParams(); + if (riskLevel) params.append('risk_level', riskLevel); + if (limit) params.append('limit', limit.toString()); + + return this.request(`/api/v1/analytics/churn/predictions?${params}`); + } + + async predictChurn(profileId: string): Promise> { + return this.request('/api/v1/analytics/churn/predict', { + method: 'POST', + body: JSON.stringify({ profile_id: profileId }), + }); + } + + // Fraud Detection API + async getFraudAlerts(severity?: string): Promise> { + const params = severity ? `?severity=${severity}` : ''; + return this.request(`/api/v1/security/fraud/alerts${params}`); + } + + async getFraudMetrics(period: string = 'monthly'): Promise> { + return this.request(`/api/v1/security/fraud/metrics?period=${period}`); + } + + async analyzeTransaction(transaction: Record): Promise> { + return this.request('/api/v1/security/fraud/analyze', { + method: 'POST', + body: JSON.stringify(transaction), + }); + } + + async updateFraudAlert(alertId: string, status: string, actions: string[]): Promise> { + return this.request(`/api/v1/security/fraud/alerts/${alertId}`, { + method: 'PUT', + body: JSON.stringify({ status, actions }), + }); + } + + // Market Analytics API + async getMarketMetrics(period: string = 'monthly'): Promise> { + return this.request(`/api/v1/analytics/market/metrics?period=${period}`); + } + + async getCompetitors(): Promise[]>> { + return this.request[]>('/api/v1/analytics/market/competitors'); + } + + async getMarketOpportunities(): Promise[]>> { + return this.request[]>('/api/v1/analytics/market/opportunities'); + } + + // Pricing API + async getPricingMetrics(period: string = 'monthly'): Promise> { + return this.request(`/api/v1/analytics/pricing/metrics?period=${period}`); + } + + async optimizePricing(ratePlanIds: string[], strategy: string): Promise> { + return this.request('/api/v1/analytics/pricing/optimize', { + method: 'POST', + body: JSON.stringify({ rate_plan_ids: ratePlanIds, strategy }), + }); + } + + async getPriceElasticity(): Promise>> { + return this.request>('/api/v1/analytics/pricing/elasticity'); + } + + // Maintenance API + async getMaintenanceMetrics(period: string = 'monthly'): Promise> { + return this.request(`/api/v1/analytics/maintenance/metrics?period=${period}`); + } + + async getAssets(): Promise> { + return this.request('/api/v1/analytics/maintenance/assets'); + } + + async getMaintenanceAlerts(): Promise[]>> { + return this.request[]>('/api/v1/analytics/maintenance/alerts'); + } + + async predictFailure(assetId: string): Promise>> { + return this.request>(`/api/v1/analytics/maintenance/predict/${assetId}`, { + method: 'POST', + }); + } +} + +export const apiClient = new ApiClient(); diff --git a/apps/web-dashboard/src/lib/apollo-client.ts b/apps/web-dashboard/src/lib/apollo-client.ts deleted file mode 100644 index 790e97c..0000000 --- a/apps/web-dashboard/src/lib/apollo-client.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { ApolloClient, InMemoryCache, HttpLink, ApolloLink } from '@apollo/client'; -import { SetContextLink } from '@apollo/client/link/context'; - -// GraphQL API endpoint -const httpLink = new HttpLink({ - uri: process.env.NEXT_PUBLIC_API_SERVER_URL || 'http://localhost:8080/v1/graphql', -}); - -// Auth link to add authentication token to requests -const authLink = new SetContextLink((prevContext, operation) => { - // Get the authentication token from local storage if it exists - const token = localStorage.getItem('auth_token'); - - return { - ...prevContext, - headers: { - ...prevContext.headers, - authorization: token ? `Bearer ${token}` : '', - }, - }; -}); - -// Create Apollo Client instance -export const apolloClient = new ApolloClient({ - link: ApolloLink.from([authLink, httpLink]), - cache: new InMemoryCache(), - defaultOptions: { - watchQuery: { - fetchPolicy: 'cache-and-network', - }, - query: { - fetchPolicy: 'network-only', - }, - mutate: { - fetchPolicy: 'network-only', - }, - }, -}); - -// GraphQL queries and mutations can be defined here -// Example: -/* -import { gql } from '@apollo/client'; - -export const GET_SERVICES = gql` - query GetServices { - services { - id - name - status - health - } - } -`; -*/