import React, { useState, useEffect, useMemo, useRef } from 'react'; import { View, Text, ScrollView, TouchableOpacity, ActivityIndicator, Dimensions } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { useRouter } from 'expo-router'; import { StatusBar } from 'expo-status-bar'; import { ChevronDown, ChevronUp, LayoutDashboard } from 'lucide-react-native'; import api from '@/utils/api'; import { useAlert } from '@/components/AlertComponent'; import EchartWrapper from '@/components/EchartWrapper'; import { CHART_ENDPOINTS, CHART_DATA_KEY, cumulate, formatEuro, buildPieOption, buildSoaBarOption, commesseLine, buildAperteChiuseOption } from '@/utils/dashboardCharts'; const TOOLTIP_BASE = { renderMode: 'richText', confine: true, textStyle: { fontSize: 10 } }; const screenWidth = Dimensions.get("window").width; export default function DashboardScreen() { const router = useRouter(); const alert = useAlert(); const [chartData, setChartData] = useState({ costiRicavi: null, fatturatoCliente: null, aperteChiuseCliente: null, aperteChiuseFornitore: null, fatturatoSoa: null, partiteCliente: null, marginalitaMediaSoa: null, aggregatiSoa: null }); const [loadingCharts, setLoadingCharts] = useState>({}); const [expandedCards, setExpandedCards] = useState>({ costiRicavi: false, costiRicaviCum: false, aperteChiuseCliente: false, aperteChiuseFornitore: false, fatturatoSoa: false, fatturatoCliente: false, apertoCliente: false, chiusoCliente: false, marginalitaMediaSoa: false, marginalitaCategoriaSoa: false, pesoFatturatoSoa: false }); const chartRichiesti = useRef(new Set()); const isLoading = (key: string) => !!loadingCharts[CHART_DATA_KEY[key] || key]; const fetchChart = (key: keyof typeof CHART_ENDPOINTS) => { const dataKey = CHART_DATA_KEY[key] || key; const endpoint = CHART_ENDPOINTS[key]; if (!endpoint || chartRichiesti.current.has(dataKey)) { return Promise.resolve(); } chartRichiesti.current.add(dataKey); setLoadingCharts(prev => ({ ...prev, [dataKey]: true })); return api.get(endpoint).then(res => { if (res.data && res.data.success) { setChartData((prev: any) => ({ ...prev, [dataKey]: res.data.result })); } }).catch(err => { chartRichiesti.current.delete(dataKey); console.log("Errore caricamento dashboard:", err); }).finally(() => { setLoadingCharts(prev => ({ ...prev, [dataKey]: false })); }); }; // Sequential Prefetching useEffect(() => { let cancelled = false; const prefetch = async () => { const richieste: string[] = []; const visti = new Set(); Object.keys(CHART_ENDPOINTS).forEach(key => { const dataKey = CHART_DATA_KEY[key] || key; if (visti.has(dataKey)) { return; } visti.add(dataKey); richieste.push(key); }); for (const key of richieste) { if (cancelled) { return; } await fetchChart(key as keyof typeof CHART_ENDPOINTS); } }; prefetch(); return () => { cancelled = true; }; }, []); const toggleCard = (key: string) => { const isCurrentlyExpanded = expandedCards[key]; setExpandedCards(prev => ({ ...prev, [key]: !prev[key] })); const dataKey = CHART_DATA_KEY[key] || key; if (!isCurrentlyExpanded && !chartData[dataKey] && !isLoading(key)) { fetchChart(key as keyof typeof CHART_ENDPOINTS); } }; // --- Chart Options --- const costiRicaviOption = useMemo(() => !chartData.costiRicavi ? null : { tooltip: { ...TOOLTIP_BASE, trigger: 'axis', valueFormatter: formatEuro }, legend: { bottom: 0, itemGap: 6, itemWidth: 14, itemHeight: 10, textStyle: { fontSize: 12 }, data: [ `Costi Materiali(${chartData.costiRicavi.current_year})`, `Costi Manodopera(${chartData.costiRicavi.current_year})`, `Ricavi(${chartData.costiRicavi.current_year})`, `Utile(${chartData.costiRicavi.current_year})`, `Costi Materiali(${chartData.costiRicavi.prev_year})`, `Costi Manodopera(${chartData.costiRicavi.prev_year})`, `Ricavi(${chartData.costiRicavi.prev_year})`, `Utile(${chartData.costiRicavi.prev_year})` ], selected: { [`Utile(${chartData.costiRicavi.prev_year})`]: false, [`Ricavi(${chartData.costiRicavi.prev_year})`]: false, [`Costi Materiali(${chartData.costiRicavi.prev_year})`]: false, [`Costi Manodopera(${chartData.costiRicavi.prev_year})`]: false }, }, grid: { left: '3%', right: '4%', top: '8%', bottom: 95, containLabel: true }, xAxis: { type: 'category', data: chartData.costiRicavi.mesi }, yAxis: { type: 'value' }, series: [ { name: `Costi Materiali(${chartData.costiRicavi.current_year})`, type: 'bar', data: chartData.costiRicavi.costi }, { name: `Costi Manodopera(${chartData.costiRicavi.current_year})`, type: 'bar', data: chartData.costiRicavi.costi_mdo }, { name: `Ricavi(${chartData.costiRicavi.current_year})`, type: 'bar', data: chartData.costiRicavi.ricavi }, { name: `Utile(${chartData.costiRicavi.current_year})`, type: 'bar', lineStyle: { width: 2.5, type: 'dashed' }, data: chartData.costiRicavi.utile }, { name: `Costi Materiali(${chartData.costiRicavi.prev_year})`, type: 'bar', itemStyle: { color: '#b3b3b3' }, data: chartData.costiRicavi.costi_prev }, { name: `Costi Manodopera(${chartData.costiRicavi.prev_year})`, type: 'bar', itemStyle: { color: '#b3b3b3' }, data: chartData.costiRicavi.costi_mdo_prev }, { name: `Ricavi(${chartData.costiRicavi.prev_year})`, type: 'bar', itemStyle: { color: '#b3b3b3' }, data: chartData.costiRicavi.ricavi_prev }, { name: `Utile(${chartData.costiRicavi.prev_year})`, type: 'bar', lineStyle: { width: 1.5, type: 'dashed', color: '#b3b3b3' }, data: chartData.costiRicavi.utile_prev } ] }, [chartData.costiRicavi]); const costiRicaviCumOption = useMemo(() => { if (!chartData.costiRicavi) return null; const d = chartData.costiRicavi; return { tooltip: { ...TOOLTIP_BASE, trigger: 'axis', valueFormatter: formatEuro }, legend: { bottom: 0, itemGap: 6, itemWidth: 14, itemHeight: 10, textStyle: { fontSize: 12 }, data: [ `Costi Materiali(${d.current_year})`, `Costi Manodopera(${d.current_year})`, `Ricavi(${d.current_year})`, `Utile(${d.current_year})`, `Costi Materiali(${d.prev_year})`, `Costi Manodopera(${d.prev_year})`, `Ricavi(${d.prev_year})`, `Utile(${d.prev_year})` ], selected: { [`Utile(${d.prev_year})`]: false, [`Ricavi(${d.prev_year})`]: false, [`Costi Materiali(${d.prev_year})`]: false, [`Costi Manodopera(${d.prev_year})`]: false }, }, grid: { left: '3%', right: '4%', top: '8%', bottom: 95, containLabel: true }, xAxis: { type: 'category', data: d.mesi }, yAxis: { type: 'value' }, series: [ { name: `Costi Materiali(${d.current_year})`, type: 'bar', data: cumulate(d.costi) }, { name: `Costi Manodopera(${d.current_year})`, type: 'bar', data: cumulate(d.costi_mdo) }, { name: `Ricavi(${d.current_year})`, type: 'bar', data: cumulate(d.ricavi) }, { name: `Utile(${d.current_year})`, type: 'bar', lineStyle: { width: 2.5, type: 'dashed' }, data: cumulate(d.utile) }, { name: `Costi Materiali(${d.prev_year})`, type: 'bar', itemStyle: { color: '#b3b3b3' }, data: cumulate(d.costi_prev) }, { name: `Costi Manodopera(${d.prev_year})`, type: 'bar', itemStyle: { color: '#b3b3b3' }, data: cumulate(d.costi_mdo_prev) }, { name: `Ricavi(${d.prev_year})`, type: 'bar', itemStyle: { color: '#b3b3b3' }, data: cumulate(d.ricavi_prev) }, { name: `Utile(${d.prev_year})`, type: 'bar', lineStyle: { width: 1.5, type: 'dashed', color: '#b3b3b3' }, data: cumulate(d.utile_prev) } ] }; }, [chartData.costiRicavi]); const aperteChiuseClienteOption = useMemo(() => chartData.aperteChiuseCliente ? buildAperteChiuseOption(chartData.aperteChiuseCliente) : null, [chartData.aperteChiuseCliente]); const aperteChiuseFornitoreOption = useMemo(() => chartData.aperteChiuseFornitore ? buildAperteChiuseOption(chartData.aperteChiuseFornitore) : null, [chartData.aperteChiuseFornitore]); const fatturatoClienteOption = useMemo(() => chartData.fatturatoCliente ? buildPieOption(chartData.fatturatoCliente) : null, [chartData.fatturatoCliente]); const fatturatoSoaOption = useMemo(() => chartData.fatturatoSoa ? buildPieOption(chartData.fatturatoSoa) : null, [chartData.fatturatoSoa]); const apertoClienteOption = useMemo(() => chartData.partiteCliente ? buildPieOption(chartData.partiteCliente.aperto) : null, [chartData.partiteCliente]); const chiusoClienteOption = useMemo(() => chartData.partiteCliente ? buildPieOption(chartData.partiteCliente.chiuso) : null, [chartData.partiteCliente]); const numItems = chartData.fatturatoCliente?.length || 0; const dynamicPieHeight = Math.max(260, 200 + (Math.ceil(numItems / 2) * 26)); const numItemsSoa = chartData.fatturatoSoa?.length || 0; const dynamicSoaHeight = Math.max(260, 200 + (numItemsSoa * 26)); const pieHeightFor = (arr: any) => Math.max(260, 200 + (Math.ceil((arr?.length || 0) / 2) * 26)); const dynamicApertoHeight = pieHeightFor(chartData.partiteCliente?.aperto); const dynamicChiusoHeight = pieHeightFor(chartData.partiteCliente?.chiuso); const marginalitaMediaSoaOption = useMemo(() => chartData.marginalitaMediaSoa ? buildSoaBarOption(chartData.marginalitaMediaSoa, { color: '#5470c6', valueLabel: 'Marginalità media', extraLines: commesseLine }) : null, [chartData.marginalitaMediaSoa]); const marginalitaCategoriaOption = useMemo(() => chartData.aggregatiSoa ? buildSoaBarOption(chartData.aggregatiSoa.marginalitaCategoria, { color: '#3ba272', valueLabel: 'Marginalità categoria', extraLines: commesseLine }) : null, [chartData.aggregatiSoa]); const pesoFatturatoSoaOption = useMemo(() => chartData.aggregatiSoa ? buildSoaBarOption(chartData.aggregatiSoa.pesoFatturato, { color: '#fac858', valueLabel: 'Peso', extraLines: (r: any) => [ `Fatturato categoria: ${formatEuro(r.fatturato_tot)}`, `Commesse: ${r.n_commesse}` ] }) : null, [chartData.aggregatiSoa]); const soaBarHeight = (arr: any) => Math.max(280, (arr?.length || 0) * 36 + 80); const dynamicMarginalitaHeight = soaBarHeight(chartData.marginalitaMediaSoa); const dynamicMarginalitaCatHeight = soaBarHeight(chartData.aggregatiSoa?.marginalitaCategoria); const dynamicPesoSoaHeight = soaBarHeight(chartData.aggregatiSoa?.pesoFatturato); // Render Card Helper const renderCard = (key: string, title: string, option: any, height: number) => { const isExpanded = expandedCards[key]; const loading = isLoading(key); return ( toggleCard(key)} > {title} {isExpanded ? : } {isExpanded && ( {loading ? ( ) : option ? ( ) : ( Nessun dato disponibile )} )} ); }; return ( Grafici e Statistiche Dashboard {renderCard('costiRicavi', 'Costi / Ricavi', costiRicaviOption, 390)} {renderCard('costiRicaviCum', 'Costi / Ricavi (Cumulati)', costiRicaviCumOption, 390)} {renderCard('aperteChiuseCliente', 'Aperto / Chiuso Clienti', aperteChiuseClienteOption, 330)} {renderCard('aperteChiuseFornitore', 'Aperto / Chiuso Fornitori', aperteChiuseFornitoreOption, 330)} {renderCard('fatturatoSoa', 'Fatturato per SOA', fatturatoSoaOption, dynamicSoaHeight)} {renderCard('fatturatoCliente', 'Fatturato per Cliente', fatturatoClienteOption, dynamicPieHeight)} {renderCard('apertoCliente', 'Aperto per Cliente', apertoClienteOption, dynamicApertoHeight)} {renderCard('chiusoCliente', 'Chiuso per Cliente', chiusoClienteOption, dynamicChiusoHeight)} {renderCard('marginalitaMediaSoa', 'Marginalità media per SOA', marginalitaMediaSoaOption, dynamicMarginalitaHeight)} {renderCard('marginalitaCategoriaSoa', 'Marginalità % di categoria SOA', marginalitaCategoriaOption, dynamicMarginalitaCatHeight)} {renderCard('pesoFatturatoSoa', 'Peso fatturato SOA', pesoFatturatoSoaOption, dynamicPesoSoaHeight)} ); }