Spaces:
Sleeping
Sleeping
| "use client"; | |
| import { useEffect, useState } from "react"; | |
| import axios from "axios"; | |
| import { | |
| Radar, RadarChart, PolarGrid, PolarAngleAxis, PolarRadiusAxis, | |
| BarChart, Bar, XAxis, YAxis, Tooltip, CartesianGrid, ResponsiveContainer, | |
| LineChart, Line, PieChart, Pie, Cell, Legend | |
| } from 'recharts'; | |
| import { InfoTooltip } from "@/components/ui/tooltip"; | |
| const API_URL = "/api"; | |
| const COLORS = ['#10b981', '#f59e0b', '#ef4444', '#3b82f6']; | |
| export function AnalyticsSection({ definitions }: { definitions: any }) { | |
| const [globalData, setGlobalData] = useState<any>(null); | |
| const [complexity, setComplexity] = useState<any[]>([]); | |
| useEffect(() => { | |
| // Fetch new global analytics | |
| axios.get(`${API_URL}/analytics/global`).then(res => setGlobalData(res.data)).catch(err => console.error(err)); | |
| // Keep existing complexity data | |
| axios.get(`${API_URL}/analytics/finish-complexity`).then(res => setComplexity(res.data)); | |
| }, []); | |
| if (!globalData) return <div className="p-8 text-neutral-500 animate-pulse">Loading Global Insights...</div>; | |
| const { kpis, distributions, trends, global_waterfall, global_blame } = globalData; | |
| // Prepare blame data for Pie chart | |
| const blameData = global_blame ? [ | |
| { name: 'Policy (norms)', value: global_blame.policy_pct, color: '#f59e0b' }, | |
| { name: 'Execution (planner)', value: global_blame.execution_pct, color: '#3b82f6' }, | |
| { name: 'Process (manufacturing)', value: global_blame.process_pct, color: '#ef4444' } | |
| ] : []; | |
| return ( | |
| <div className="space-y-8 mb-12"> | |
| {/* 1. Global KPI Cards */} | |
| <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4"> | |
| <KPICard | |
| label="Total Order Qty" | |
| value={`${(kpis.total_volume_m / 1000000).toFixed(2)}M`} | |
| sub="Meters Processed" | |
| tooltip="Sum of all DORQT1 (Order Quantity) across every Sale Order line. Formula: Σ Order Qty (meters)." | |
| /> | |
| <KPICard | |
| label="Global Processing Efficiency" | |
| value={`${kpis.global_yield_pct}%`} | |
| sub="Weighted Average" | |
| color={kpis.global_yield_pct > 95 ? "text-emerald-400" : "text-amber-400"} | |
| tooltip="Weighted manufacturing efficiency. Formula: (Σ Pack Fresh Output ÷ Σ Greige Issued) × 100. Higher = less process loss." | |
| /> | |
| <KPICard | |
| label="Shortfall Risk Rate" | |
| value={`${kpis.shortfall_risk_pct}%`} | |
| sub="Orders with Deficit Risk" | |
| color={kpis.shortfall_risk_pct < 5 ? "text-emerald-400" : "text-red-400"} | |
| tooltip="Percentage of orders where Output < Order Qty. Formula: (Count of orders with Pack Fresh < Order Qty ÷ Total Orders) × 100." | |
| /> | |
| <KPICard | |
| label="Active Orders" | |
| value={kpis.total_orders} | |
| sub="Total Sale Orders" | |
| tooltip="Count of distinct Sale Order numbers in the loaded 1-year dataset." | |
| /> | |
| </div> | |
| {/* 2. Charts Grid */} | |
| <div className="grid grid-cols-1 lg:grid-cols-2 gap-6"> | |
| {/* Finish Performance */} | |
| <ChartCard | |
| title="Efficiency by Finish" | |
| subtitle="Top 10 Finishes by Order Quantity" | |
| description="Displays the average manufacturing efficiency percentage for the top 10 most heavily produced finish types. Helps identify which chemical or mechanical finishes cause the most process loss." | |
| > | |
| <ResponsiveContainer width="100%" height="100%"> | |
| <BarChart data={distributions.finish} layout="vertical" margin={{ left: 40, right: 20 }}> | |
| <CartesianGrid strokeDasharray="3 3" horizontal={true} vertical={false} stroke="#333" /> | |
| <XAxis type="number" domain={[80, 100]} stroke="#555" unit="%" /> | |
| <YAxis dataKey="Finish" type="category" stroke="#9ca3af" width={100} tick={{ fontSize: 11 }} /> | |
| <Tooltip | |
| contentStyle={{ backgroundColor: '#171717', borderColor: '#404040' }} | |
| cursor={{ fill: '#ffffff05' }} | |
| /> | |
| <Bar dataKey="yield" fill="#3b82f6" radius={[0, 4, 4, 0]} name="Efficiency %" /> | |
| </BarChart> | |
| </ResponsiveContainer> | |
| </ChartCard> | |
| {/* Route Performance */} | |
| <ChartCard | |
| title="Efficiency by Route" | |
| subtitle="Machine Type Analysis" | |
| description="Compares the production efficiency across different dyeing machines and processing routes. Useful for spotting hardware or technique bottlenecks." | |
| > | |
| <ResponsiveContainer width="100%" height="100%"> | |
| <BarChart data={distributions.route}> | |
| <CartesianGrid strokeDasharray="3 3" stroke="#333" /> | |
| <XAxis dataKey="Route" stroke="#9ca3af" /> | |
| <YAxis domain={[80, 100]} stroke="#555" unit="%" /> | |
| <Tooltip contentStyle={{ backgroundColor: '#171717', borderColor: '#404040' }} /> | |
| <Bar dataKey="yield" fill="#10b981" radius={[4, 4, 0, 0]} name="Efficiency %" /> | |
| </BarChart> | |
| </ResponsiveContainer> | |
| </ChartCard> | |
| {/* NEW: Yield by Shade */} | |
| {distributions.shade && distributions.shade.length > 0 && ( | |
| <ChartCard | |
| title="Efficiency by Shade" | |
| subtitle="Shade Type Analysis" | |
| description="Shows the efficiency percentage grouped by base shade categories (e.g., Dyed, RFD, Bleach). Darker or more complex shades often have lower efficiency." | |
| > | |
| <ResponsiveContainer width="100%" height="100%"> | |
| <BarChart data={distributions.shade}> | |
| <CartesianGrid strokeDasharray="3 3" stroke="#333" /> | |
| <XAxis dataKey="Shade Type" stroke="#9ca3af" tick={{ fontSize: 10 }} /> | |
| <YAxis domain={[80, 100]} stroke="#555" unit="%" /> | |
| <Tooltip contentStyle={{ backgroundColor: '#171717', borderColor: '#404040' }} /> | |
| <Bar dataKey="yield" fill="#8b5cf6" radius={[4, 4, 0, 0]} name="Efficiency %" /> | |
| </BarChart> | |
| </ResponsiveContainer> | |
| </ChartCard> | |
| )} | |
| {/* Monthly Trend */} | |
| <ChartCard | |
| title="Efficiency Trend" | |
| subtitle="Monthly Global Average" | |
| description="Tracks the overall manufacturing efficiency rate month-over-month to show macro trends in production efficiency over time." | |
| > | |
| <ResponsiveContainer width="100%" height="100%"> | |
| <LineChart data={trends}> | |
| <CartesianGrid strokeDasharray="3 3" stroke="#333" /> | |
| <XAxis dataKey="month" stroke="#9ca3af" tick={{ fontSize: 11 }} /> | |
| <YAxis domain={['auto', 'auto']} stroke="#555" unit="%" /> | |
| <Tooltip contentStyle={{ backgroundColor: '#171717', borderColor: '#404040' }} /> | |
| <Line type="monotone" dataKey="yield" stroke="#f59e0b" strokeWidth={2} dot={{ r: 4 }} activeDot={{ r: 6 }} /> | |
| </LineChart> | |
| </ResponsiveContainer> | |
| </ChartCard> | |
| {/* Complexity Bar Chart (Fixed from Radar) */} | |
| <ChartCard | |
| title="Complexity & Deviation" | |
| subtitle="Attribute Impact Analysis (Top Finishes)" | |
| description="Highlights which product finishes diverge the most from the standard norms. A high deviation means the planner frequently overrides the system recommendations for this attribute." | |
| > | |
| <ResponsiveContainer width="100%" height="100%"> | |
| <BarChart data={complexity} layout="vertical" margin={{ left: 40, right: 20 }}> | |
| <CartesianGrid strokeDasharray="3 3" horizontal={true} vertical={false} stroke="#333" /> | |
| <XAxis type="number" stroke="#555" unit="%" /> | |
| <YAxis dataKey="attribute" type="category" stroke="#9ca3af" width={100} tick={{ fontSize: 11 }} /> | |
| <Tooltip | |
| contentStyle={{ backgroundColor: '#171717', borderColor: '#404040' }} | |
| cursor={{ fill: '#ffffff05' }} | |
| formatter={(value: any) => [`${Number(value).toFixed(2)}%`, 'Avg Deviation']} | |
| /> | |
| <Bar dataKey="avg_deviation" fill="#8884d8" radius={[0, 4, 4, 0]} name="Avg Deviation %" /> | |
| </BarChart> | |
| </ResponsiveContainer> | |
| </ChartCard> | |
| {/* NEW: Global Blame/Friction Breakdown */} | |
| {global_blame && ( | |
| <ChartCard | |
| title="Global Friction Breakdown" | |
| subtitle="Where Effort is Lost" | |
| description="Analyzes the root cause of shortfalls across all orders. Attributes blame to Policy (Norms too tight), Execution (Planner under-issued), or Process (Manufacturing loss exceeded buffer)." | |
| > | |
| <ResponsiveContainer width="100%" height="100%"> | |
| <PieChart margin={{ top: 0, right: 0, left: 0, bottom: 20 }}> | |
| <Pie | |
| data={blameData} | |
| cx="50%" | |
| cy="45%" | |
| innerRadius={50} | |
| outerRadius={80} | |
| paddingAngle={3} | |
| dataKey="value" | |
| nameKey="name" | |
| // Removed the cramped inline labels to rely on Legend & Menu | |
| labelLine={false} | |
| > | |
| {blameData.map((entry, index) => ( | |
| <Cell key={`cell-${index}`} fill={entry.color} /> | |
| ))} | |
| </Pie> | |
| <Legend | |
| verticalAlign="bottom" | |
| wrapperStyle={{ fontSize: '11px', paddingTop: '10px' }} | |
| layout="horizontal" | |
| /> | |
| <Tooltip | |
| contentStyle={{ backgroundColor: '#171717', borderColor: '#404040', color: '#fff' }} | |
| itemStyle={{ color: '#fff' }} | |
| formatter={(value: any) => [`${Number(value).toFixed(1)}%`, 'Share of Shortfall']} | |
| /> | |
| </PieChart> | |
| </ResponsiveContainer> | |
| </ChartCard> | |
| )} | |
| </div> | |
| {/* 3. NEW: Top Customers & Segment Analysis */} | |
| {(distributions.customer?.length > 0 || distributions.segment?.length > 0) && ( | |
| <div className="grid grid-cols-1 lg:grid-cols-2 gap-6"> | |
| {/* Top Customers Table */} | |
| {distributions.customer && distributions.customer.length > 0 && ( | |
| <div className="bg-neutral-900/40 border border-neutral-800 p-6 rounded-xl"> | |
| <h3 className="text-lg font-semibold text-neutral-200 mb-1">Top Customers</h3> | |
| <p className="text-sm text-neutral-500 mb-4">By Order Quantity (Meters)</p> | |
| <div className="overflow-auto max-h-[300px]"> | |
| <table className="w-full text-sm"> | |
| <thead className="text-neutral-400 border-b border-neutral-700"> | |
| <tr> | |
| <th className="text-left py-2 pr-4">Customer</th> | |
| <th className="text-right py-2 px-2">Order Qty <InfoTooltip content="Sum of all DORQT1 for this customer. Formula: Σ Order Qty (meters)." /></th> | |
| <th className="text-right py-2 pl-2">Efficiency % <InfoTooltip content="Pack Fresh ÷ Greige Issued × 100 for this customer." /></th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| {distributions.customer.map((c: any, i: number) => ( | |
| <tr key={i} className="border-b border-neutral-800/50 hover:bg-neutral-800/30"> | |
| <td className="py-2 pr-4 text-neutral-300 truncate max-w-[200px]" title={c.customer}>{c.customer}</td> | |
| <td className="py-2 px-2 text-right text-neutral-400">{(c.volume / 1000).toFixed(1)}K</td> | |
| <td className={`py-2 pl-2 text-right font-medium ${c.yield > 95 ? 'text-emerald-400' : 'text-amber-400'}`}>{c.yield}%</td> | |
| </tr> | |
| ))} | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| )} | |
| {/* Segment Distribution */} | |
| {distributions.segment && distributions.segment.length > 0 && ( | |
| <ChartCard | |
| title="Order Qty by Segment" | |
| subtitle="Business Segment Breakdown" | |
| description="Categorizes total mapped order quantity into internal business segments or product families for high-level portfolio analysis." | |
| > | |
| <ResponsiveContainer width="100%" height="100%"> | |
| <BarChart data={distributions.segment} layout="vertical" margin={{ left: 60, right: 20 }}> | |
| <CartesianGrid strokeDasharray="3 3" horizontal={true} vertical={false} stroke="#333" /> | |
| <XAxis type="number" stroke="#555" tickFormatter={(v) => `${(v / 1000).toFixed(0)}K`} /> | |
| <YAxis dataKey="segment" type="category" stroke="#9ca3af" width={120} tick={{ fontSize: 11 }} /> | |
| <Tooltip | |
| contentStyle={{ backgroundColor: '#171717', borderColor: '#404040' }} | |
| formatter={(value: any) => [`${(value / 1000).toFixed(1)}K meters`, 'Order Qty']} | |
| /> | |
| <Bar dataKey="volume" fill="#14b8a6" radius={[0, 4, 4, 0]} name="Order Qty" /> | |
| </BarChart> | |
| </ResponsiveContainer> | |
| </ChartCard> | |
| )} | |
| </div> | |
| ) | |
| } | |
| {/* 4. NEW: Global Waterfall (Opportunity Loss) */} | |
| { | |
| global_waterfall && ( | |
| <div className="bg-neutral-900/40 border border-neutral-800 p-6 rounded-xl"> | |
| <h3 className="text-lg font-semibold text-neutral-200 mb-1">Global Opportunity Waterfall <InfoTooltip content="Visualizes the material flow from Order Quantity to final Output. Each bar shows how Reserved Qty, Issued Qty, and Process Loss transform the original order into delivered meters." /></h3> | |
| <p className="text-sm text-neutral-500 mb-6">Order Quantity → Delivery Flow (Meters)</p> | |
| <div className="h-[250px] w-full"> | |
| <ResponsiveContainer width="100%" height="100%"> | |
| <BarChart data={global_waterfall} margin={{ top: 20, right: 30, left: 20, bottom: 5 }}> | |
| <CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#333" /> | |
| <XAxis dataKey="label" stroke="#9ca3af" tick={{ fontSize: 11 }} /> | |
| <YAxis stroke="#555" tickFormatter={(v) => `${(v / 1000000).toFixed(1)}M`} /> | |
| <Tooltip | |
| contentStyle={{ backgroundColor: '#171717', borderColor: '#404040' }} | |
| formatter={(value: any, name: any, props: any) => { | |
| const isPositive = props.payload.value >= 0; | |
| return [`${isPositive ? '+' : ''}${(Number(value) / 1000000).toFixed(2)}M`, props.payload.desc || props.payload.label]; | |
| }} | |
| /> | |
| <Bar dataKey="value" radius={[4, 4, 0, 0]}> | |
| {global_waterfall.map((entry: any, index: number) => { | |
| const bgColor = entry.type === 'base' ? '#3b82f6' : | |
| entry.type === 'final' ? '#10b981' : | |
| entry.value >= 0 ? '#f59e0b' : '#ef4444'; | |
| return <Cell key={`cell-${index}`} fill={bgColor} />; | |
| })} | |
| </Bar> | |
| </BarChart> | |
| </ResponsiveContainer> | |
| </div> | |
| </div> | |
| ) | |
| } | |
| </div > | |
| ) | |
| } | |
| function KPICard({ label, value, sub, color = "text-white", tooltip }: { label: string, value: string | number, sub: string, color?: string, tooltip?: string }) { | |
| return ( | |
| <div className="bg-neutral-900/40 border border-neutral-800 p-6 rounded-xl relative group"> | |
| <h4 className="flex items-center gap-2 text-neutral-400 text-sm font-medium uppercase tracking-wider mb-2"> | |
| {label} | |
| {tooltip && <InfoTooltip content={tooltip} />} | |
| </h4> | |
| <div className={`text-3xl font-bold ${color} mb-1`}>{value}</div> | |
| <div className="text-neutral-500 text-xs">{sub}</div> | |
| </div> | |
| ) | |
| } | |
| function ChartCard({ title, subtitle, description, children }: { title: string, subtitle: string, description?: string, children: React.ReactNode }) { | |
| return ( | |
| <div className="bg-neutral-900/40 border border-neutral-800 p-6 rounded-xl min-h-[350px] flex flex-col group relative"> | |
| <div className="mb-6 pr-8"> | |
| <h3 className="flex items-center gap-2 text-lg font-semibold text-neutral-200"> | |
| {title} | |
| {description && <InfoTooltip content={description} />} | |
| </h3> | |
| <p className="text-sm text-neutral-500">{subtitle}</p> | |
| </div> | |
| <div className="flex-1 min-h-[250px] relative z-10"> | |
| {children} | |
| </div> | |
| </div> | |
| ) | |
| } | |