import 'dart:io'; import 'package:excel/excel.dart'; import 'package:path_provider/path_provider.dart'; import 'package:share_plus/share_plus.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:flutter/material.dart' hide Border, BorderStyle; class ExcelExportService { static Future generateAndPrintBoxScoreExcel({ required String gameId, required String myTeam, required String opponentTeam, required String myScore, required String opponentScore, required String season, required String targetTeam, }) async { try { final supabase = Supabase.instance.client; // ── 1. DADOS DO JOGO ─────────────────────────────────────────────────── final gameData = await supabase.from('games').select().eq('id', gameId).maybeSingle(); String dateStr = "---"; if (gameData != null && gameData['game_date'] != null) { String rawDate = gameData['game_date'].toString(); dateStr = rawDate.length >= 10 ? rawDate.substring(0, 10) : rawDate; } // ── 2. ESTATÍSTICAS DOS JOGADORES ────────────────────────────────────── final statsResponse = await supabase.from('player_stats').select().eq('game_id', gameId); if (statsResponse.isEmpty) return; // ── 3. NOMES E NÚMEROS DAS EQUIPAS E JOGADORES ─────────────────────── final membersResponse = await supabase.from('members').select('id, name, number'); final Map> memberInfo = { for (var m in membersResponse) m['id'].toString(): m }; final teamsResponse = await supabase.from('teams').select('id, name'); final Map teamNames = { for (var t in teamsResponse) t['id'].toString(): t['name'].toString() }; // ── 4. CONFIGURAÇÃO DO EXCEL ─────────────────────────────────────────── var excel = Excel.createExcel(); String sheetName = 'Estatísticas'; Sheet sheet = excel[sheetName]; excel.setDefaultSheet(sheetName); if (excel.tables.keys.contains('Sheet1')) excel.delete('Sheet1'); // ── ESTILOS E CORES PREMIUM ─────────────────────────────────────────── final corPrincipal = ExcelColor.fromHexString('#A00000'); // Vermelho escuro final corFundoCinza = ExcelColor.fromHexString('#F5F5F5'); final corFundoCinzaEscuro = ExcelColor.fromHexString('#E0E0E0'); final cor2P = ExcelColor.fromHexString('#E3F2FD'); // Azul claro final cor3P = ExcelColor.fromHexString('#E8F5E9'); // Verde claro final corGlobal = ExcelColor.fromHexString('#FFF9C4');// Amarelo claro final corLL = ExcelColor.fromHexString('#FFF3E0'); // Laranja claro final corReb = ExcelColor.fromHexString('#F3E5F5'); // Roxo claro final borderGrey = ExcelColor.fromHexString('#BDBDBD'); CellStyle styleTituloJogo = CellStyle(bold: true, fontSize: 16); CellStyle styleNomeEquipa = CellStyle(bold: true, fontSize: 14, fontColorHex: ExcelColor.white, backgroundColorHex: corPrincipal, horizontalAlign: HorizontalAlign.Center, verticalAlign: VerticalAlign.Center); CellStyle styleTituloSecundario = CellStyle(bold: true, fontSize: 12, fontColorHex: ExcelColor.black, backgroundColorHex: corFundoCinzaEscuro, horizontalAlign: HorizontalAlign.Center, verticalAlign: VerticalAlign.Center); CellStyle styleGrelha(ExcelColor bgCol, {bool isBold = false}) { return CellStyle( bold: isBold, backgroundColorHex: bgCol, horizontalAlign: HorizontalAlign.Center, verticalAlign: VerticalAlign.Center, leftBorder: Border(borderStyle: BorderStyle.Thin, borderColorHex: borderGrey), rightBorder: Border(borderStyle: BorderStyle.Thin, borderColorHex: borderGrey), topBorder: Border(borderStyle: BorderStyle.Thin, borderColorHex: borderGrey), bottomBorder: Border(borderStyle: BorderStyle.Thin, borderColorHex: borderGrey), ); } final styleGeral = styleGrelha(ExcelColor.white); final styleGeralBold = styleGrelha(ExcelColor.white, isBold: true); final styleNome = CellStyle(horizontalAlign: HorizontalAlign.Left, verticalAlign: VerticalAlign.Center, leftBorder: Border(borderStyle: BorderStyle.Thin, borderColorHex: borderGrey), rightBorder: Border(borderStyle: BorderStyle.Thin, borderColorHex: borderGrey), topBorder: Border(borderStyle: BorderStyle.Thin, borderColorHex: borderGrey), bottomBorder: Border(borderStyle: BorderStyle.Thin, borderColorHex: borderGrey)); // ── CABEÇALHO DO JOGO ──────────────────────────────────────────────── sheet.cell(CellIndex.indexByString("A1")).value = TextCellValue("JOGO:"); sheet.cell(CellIndex.indexByString("A1")).cellStyle = CellStyle(bold: true); sheet.cell(CellIndex.indexByString("B1")).value = TextCellValue("$myTeam vs $opponentTeam"); sheet.cell(CellIndex.indexByString("B1")).cellStyle = styleTituloJogo; sheet.cell(CellIndex.indexByString("A2")).value = TextCellValue("COMPETIÇÃO:"); sheet.cell(CellIndex.indexByString("A2")).cellStyle = CellStyle(bold: true); sheet.cell(CellIndex.indexByString("B2")).value = TextCellValue(season); sheet.cell(CellIndex.indexByString("A3")).value = TextCellValue("DATA:"); sheet.cell(CellIndex.indexByString("A3")).cellStyle = CellStyle(bold: true); sheet.cell(CellIndex.indexByString("B3")).value = TextCellValue(dateStr); sheet.cell(CellIndex.indexByString("A4")).value = TextCellValue("RESULTADO:"); sheet.cell(CellIndex.indexByString("A4")).cellStyle = CellStyle(bold: true); sheet.cell(CellIndex.indexByString("B4")).value = TextCellValue("$myScore - $opponentScore"); sheet.cell(CellIndex.indexByString("B4")).cellStyle = CellStyle(bold: true, fontColorHex: corPrincipal); // ── TOTAIS POR PERÍODO (NOVA SECÇÃO) ───────────────────────────────── sheet.cell(CellIndex.indexByString("A6")).value = TextCellValue("PONTUAÇÃO POR PERÍODO"); sheet.cell(CellIndex.indexByString("A6")).cellStyle = CellStyle(bold: true, fontColorHex: corPrincipal); List periodHeaders = ["EQUIPA", "1º Q", "2º Q", "3º Q", "4º Q", "TOTAL"]; for (int i = 0; i < periodHeaders.length; i++) { var cell = sheet.cell(CellIndex.indexByColumnRow(columnIndex: i, rowIndex: 6)); cell.value = TextCellValue(periodHeaders[i]); cell.cellStyle = styleGrelha(corFundoCinza, isBold: true); } // Linha Minha Equipa List myRow = [ myTeam, gameData?['my_q1']?.toString() ?? '-', gameData?['my_q2']?.toString() ?? '-', gameData?['my_q3']?.toString() ?? '-', gameData?['my_q4']?.toString() ?? '-', myScore ]; for (int i = 0; i < myRow.length; i++) { var cell = sheet.cell(CellIndex.indexByColumnRow(columnIndex: i, rowIndex: 7)); cell.value = TextCellValue(myRow[i].toString()); cell.cellStyle = i == 0 ? styleNome : styleGeralBold; } // Linha Adversário List oppRow = [ opponentTeam, gameData?['opp_q1']?.toString() ?? '-', gameData?['opp_q2']?.toString() ?? '-', gameData?['opp_q3']?.toString() ?? '-', gameData?['opp_q4']?.toString() ?? '-', opponentScore ]; for (int i = 0; i < oppRow.length; i++) { var cell = sheet.cell(CellIndex.indexByColumnRow(columnIndex: i, rowIndex: 8)); cell.value = TextCellValue(oppRow[i].toString()); cell.cellStyle = i == 0 ? styleNome : styleGeralBold; } int r = 11; // 👈 AS TABELAS PRINCIPAIS AGORA COMEÇAM MAIS ABAIXO (Linha 12 no Excel) // Agrupar estatísticas por equipa Map> statsByTeam = {}; for(var s in statsResponse) { String tId = s['team_id'].toString(); statsByTeam.putIfAbsent(tId, () => []).add(s); } // ── CONSTRUÇÃO DAS TABELAS DE CADA EQUIPA ──────────────────────────── for (var entry in statsByTeam.entries) { String tId = entry.key; List tStats = entry.value; String tName = teamNames[tId] ?? "Equipa $tId"; if (targetTeam != 'Ambas' && tName != targetTeam) continue; tStats.sort((a, b) { var mInfoA = memberInfo[a['member_id'].toString()]; var mInfoB = memberInfo[b['member_id'].toString()]; int numA = int.tryParse(mInfoA?['number']?.toString() ?? '0') ?? 0; int numB = int.tryParse(mInfoB?['number']?.toString() ?? '0') ?? 0; return numA.compareTo(numB); }); List> processedPlayers = []; int tMin=0, tPts=0, t2m=0, t2a=0, t3m=0, t3a=0, tFgm=0, tFga=0, tftm=0, tfta=0; int torb=0, tdrb=0, tStl=0, tAst=0, tTov=0, tBlk=0, tFls=0; int tSo=0, tIl=0, tLi=0, tPa=0, tTresS=0, tDr=0; for(var stat in tStats) { var mInfo = memberInfo[stat['member_id'].toString()]; String pNum = mInfo != null ? (mInfo['number']?.toString() ?? "-") : "-"; String pName = mInfo != null ? (mInfo['name']?.toString() ?? "Desconhecido") : "Desconhecido"; int minSecs = stat['minutos_jogados'] ?? 0; int pts = stat['pts'] ?? 0; int p2m = stat['p2m'] ?? 0; int p2a = stat['p2a'] ?? 0; int p3m = stat['p3m'] ?? 0; int p3a = stat['p3a'] ?? 0; int fgm = stat['fgm'] ?? 0; int fga = stat['fga'] ?? 0; int ftm = stat['ftm'] ?? 0; int fta = stat['fta'] ?? 0; int orb = stat['orb'] ?? 0; int drb = stat['drb'] ?? 0; int tr = orb + drb; int stl = stat['stl'] ?? 0; int ast = stat['ast'] ?? 0; int tov = stat['tov'] ?? 0; int blk = stat['blk'] ?? 0; int fls = stat['fls'] ?? 0; int so = stat['so'] ?? 0; int il = stat['il'] ?? 0; int li = stat['li'] ?? 0; int pa = stat['pa'] ?? 0; int tresS = stat['tres_seg'] ?? 0; int dr = stat['dr'] ?? 0; tMin+=minSecs; tPts+=pts; t2m+=p2m; t2a+=p2a; t3m+=p3m; t3a+=p3a; tFgm+=fgm; tFga+=fga; tftm+=ftm; tfta+=fta; torb+=orb; tdrb+=drb; tStl+=stl; tAst+=ast; tTov+=tov; tBlk+=blk; tFls+=fls; tSo+=so; tIl+=il; tLi+=li; tPa+=pa; tTresS+=tresS; tDr+=dr; processedPlayers.add({ 'num': pNum, 'name': pName, 'minSecs': minSecs, 'pts': pts, 'p2m': p2m, 'p2a': p2a, 'p3m': p3m, 'p3a': p3a, 'fgm': fgm, 'fga': fga, 'ftm': ftm, 'fta': fta, 'orb': orb, 'drb': drb, 'tr': tr, 'stl': stl, 'ast': ast, 'tov': tov, 'blk': blk, 'fls': fls, 'so': so, 'il': il, 'li': li, 'pa': pa, '3s': tresS, 'dr': dr }); } // TABELA 1: LANÇAMENTOS E RESSALTOS var teamStart = CellIndex.indexByColumnRow(columnIndex: 0, rowIndex: r); var teamEnd = CellIndex.indexByColumnRow(columnIndex: 18, rowIndex: r); sheet.merge(teamStart, teamEnd, customValue: TextCellValue("ESTATÍSTICAS DA EQUIPA: ${tName.toUpperCase()} (Lançamentos e Ressaltos)")); for(int i=0; i<=18; i++) sheet.cell(CellIndex.indexByColumnRow(columnIndex: i, rowIndex: r)).cellStyle = styleNomeEquipa; r++; void criarCategoria(int colStart, int colEnd, String texto, CellStyle estilo) { sheet.merge(CellIndex.indexByColumnRow(columnIndex: colStart, rowIndex: r), CellIndex.indexByColumnRow(columnIndex: colEnd, rowIndex: r), customValue: TextCellValue(texto)); for(int i=colStart; i<=colEnd; i++) sheet.cell(CellIndex.indexByColumnRow(columnIndex: i, rowIndex: r)).cellStyle = estilo; } for(int i=0; i<=3; i++) sheet.cell(CellIndex.indexByColumnRow(columnIndex: i, rowIndex: r)).cellStyle = styleGrelha(corFundoCinza); criarCategoria(4, 6, "2 PONTOS", styleGrelha(cor2P, isBold: true)); criarCategoria(7, 9, "3 PONTOS", styleGrelha(cor3P, isBold: true)); criarCategoria(10, 12, "GLOBAL", styleGrelha(corGlobal, isBold: true)); criarCategoria(13, 15, "L. LIVRES", styleGrelha(corLL, isBold: true)); criarCategoria(16, 18, "RESSALTOS", styleGrelha(corReb, isBold: true)); r++; List colsT1 = ["Nº", "NOME", "MIN", "PTS", "C", "T", "%", "C", "T", "%", "C", "T", "%", "C", "T", "%", "RO", "RD", "TR"]; for(int i = 0; i < colsT1.length; i++) { var cell = sheet.cell(CellIndex.indexByColumnRow(columnIndex: i, rowIndex: r)); cell.value = TextCellValue(colsT1[i]); if (i >= 4 && i <= 6) cell.cellStyle = styleGrelha(cor2P, isBold: true); else if (i >= 7 && i <= 9) cell.cellStyle = styleGrelha(cor3P, isBold: true); else if (i >= 10 && i <= 12) cell.cellStyle = styleGrelha(corGlobal, isBold: true); else if (i >= 13 && i <= 15) cell.cellStyle = styleGrelha(corLL, isBold: true); else if (i >= 16 && i <= 18) cell.cellStyle = styleGrelha(corReb, isBold: true); else cell.cellStyle = styleGrelha(corFundoCinza, isBold: true); } r++; for(var p in processedPlayers) { String minStr = '${p['minSecs'] ~/ 60}:${(p['minSecs'] % 60).toString().padLeft(2, '0')}'; String p2Pct = p['p2a'] > 0 ? '${((p['p2m'] / p['p2a']) * 100).toStringAsFixed(0)}%' : '-'; String p3Pct = p['p3a'] > 0 ? '${((p['p3m'] / p['p3a']) * 100).toStringAsFixed(0)}%' : '-'; String fgPct = p['fga'] > 0 ? '${((p['fgm'] / p['fga']) * 100).toStringAsFixed(0)}%' : '-'; String ftPct = p['fta'] > 0 ? '${((p['ftm'] / p['fta']) * 100).toStringAsFixed(0)}%' : '-'; List rowData = [ TextCellValue(p['num']), TextCellValue(p['name']), TextCellValue(minStr), IntCellValue(p['pts']), IntCellValue(p['p2m']), IntCellValue(p['p2a']), TextCellValue(p2Pct), IntCellValue(p['p3m']), IntCellValue(p['p3a']), TextCellValue(p3Pct), IntCellValue(p['fgm']), IntCellValue(p['fga']), TextCellValue(fgPct), IntCellValue(p['ftm']), IntCellValue(p['fta']), TextCellValue(ftPct), IntCellValue(p['orb']), IntCellValue(p['drb']), IntCellValue(p['tr']) ]; for(int i = 0; i < rowData.length; i++) { var cell = sheet.cell(CellIndex.indexByColumnRow(columnIndex: i, rowIndex: r)); cell.value = rowData[i]; if (i == 1) cell.cellStyle = styleNome; else if (i == 3) cell.cellStyle = styleGeralBold; else cell.cellStyle = styleGeral; } r++; } String t2Pct = t2a > 0 ? '${((t2m / t2a) * 100).toStringAsFixed(0)}%' : '-'; String t3Pct = t3a > 0 ? '${((t3m / t3a) * 100).toStringAsFixed(0)}%' : '-'; String tFgPct = tFga > 0 ? '${((tFgm / tFga) * 100).toStringAsFixed(0)}%' : '-'; String tftPct = tfta > 0 ? '${((tftm / tfta) * 100).toStringAsFixed(0)}%' : '-'; String tMinStr = '${tMin ~/ 60}:${(tMin % 60).toString().padLeft(2, '0')}'; List totalRowT1 = [ TextCellValue(""), TextCellValue("TOTAL EQUIPA"), TextCellValue(tMinStr), IntCellValue(tPts), IntCellValue(t2m), IntCellValue(t2a), TextCellValue(t2Pct), IntCellValue(t3m), IntCellValue(t3a), TextCellValue(t3Pct), IntCellValue(tFgm), IntCellValue(tFga), TextCellValue(tFgPct), IntCellValue(tftm), IntCellValue(tfta), TextCellValue(tftPct), IntCellValue(torb), IntCellValue(tdrb), IntCellValue(torb + tdrb) ]; for(int i = 0; i < totalRowT1.length; i++) { var cell = sheet.cell(CellIndex.indexByColumnRow(columnIndex: i, rowIndex: r)); cell.value = totalRowT1[i]; cell.cellStyle = styleGrelha(corFundoCinza, isBold: true); if (i >= 4 && i <= 6) cell.cellStyle = styleGrelha(cor2P, isBold: true); else if (i >= 7 && i <= 9) cell.cellStyle = styleGrelha(cor3P, isBold: true); else if (i >= 10 && i <= 12) cell.cellStyle = styleGrelha(corGlobal, isBold: true); else if (i >= 13 && i <= 15) cell.cellStyle = styleGrelha(corLL, isBold: true); else if (i >= 16 && i <= 18) cell.cellStyle = styleGrelha(corReb, isBold: true); } r += 3; // TABELA 2: OUTRAS ESTATÍSTICAS var secStart = CellIndex.indexByColumnRow(columnIndex: 0, rowIndex: r); var secEnd = CellIndex.indexByColumnRow(columnIndex: 12, rowIndex: r); sheet.merge(secStart, secEnd, customValue: TextCellValue("OUTRAS ESTATÍSTICAS: ${tName.toUpperCase()}")); for(int i=0; i<=12; i++) sheet.cell(CellIndex.indexByColumnRow(columnIndex: i, rowIndex: r)).cellStyle = styleTituloSecundario; r++; List colsT2 = ["Nº", "NOME", "BR", "AS", "BP", "BLK", "FLS", "SO", "IL", "LI", "PA", "3S", "DR"]; for(int i = 0; i < colsT2.length; i++) { var cell = sheet.cell(CellIndex.indexByColumnRow(columnIndex: i, rowIndex: r)); cell.value = TextCellValue(colsT2[i]); cell.cellStyle = styleGrelha(corFundoCinza, isBold: true); } r++; for(var p in processedPlayers) { List rowData2 = [ TextCellValue(p['num']), TextCellValue(p['name']), IntCellValue(p['stl']), IntCellValue(p['ast']), IntCellValue(p['tov']), IntCellValue(p['blk']), IntCellValue(p['fls']), IntCellValue(p['so']), IntCellValue(p['il']), IntCellValue(p['li']), IntCellValue(p['pa']), IntCellValue(p['3s']), IntCellValue(p['dr']) ]; for(int i = 0; i < rowData2.length; i++) { var cell = sheet.cell(CellIndex.indexByColumnRow(columnIndex: i, rowIndex: r)); cell.value = rowData2[i]; cell.cellStyle = (i == 1) ? styleNome : styleGeral; } r++; } List totalRowT2 = [ TextCellValue(""), TextCellValue("TOTAL EQUIPA"), IntCellValue(tStl), IntCellValue(tAst), IntCellValue(tTov), IntCellValue(tBlk), IntCellValue(tFls), IntCellValue(tSo), IntCellValue(tIl), IntCellValue(tLi), IntCellValue(tPa), IntCellValue(tTresS), IntCellValue(tDr) ]; for(int i = 0; i < totalRowT2.length; i++) { var cell = sheet.cell(CellIndex.indexByColumnRow(columnIndex: i, rowIndex: r)); cell.value = totalRowT2[i]; cell.cellStyle = styleGrelha(corFundoCinza, isBold: true); } r += 4; } // ── DESTAQUES DO JOGO ─────────────────────────────────────── if (gameData != null) { var startD = CellIndex.indexByColumnRow(columnIndex: 0, rowIndex: r); var endD = CellIndex.indexByColumnRow(columnIndex: 3, rowIndex: r); sheet.merge(startD, endD, customValue: TextCellValue("DESTAQUES DO JOGO")); for(int i=0; i<=3; i++) sheet.cell(CellIndex.indexByColumnRow(columnIndex: i, rowIndex: r)).cellStyle = styleNomeEquipa; r++; void adicionarDestaque(String titulo, String valor) { sheet.cell(CellIndex.indexByColumnRow(columnIndex: 0, rowIndex: r)).value = TextCellValue(titulo); sheet.cell(CellIndex.indexByColumnRow(columnIndex: 0, rowIndex: r)).cellStyle = CellStyle(bold: true); var sV = CellIndex.indexByColumnRow(columnIndex: 1, rowIndex: r); var eV = CellIndex.indexByColumnRow(columnIndex: 3, rowIndex: r); sheet.merge(sV, eV, customValue: TextCellValue(valor)); r++; } adicionarDestaque("Melhor Marcador:", gameData['top_pts_name'] ?? '---'); adicionarDestaque("Melhor Ressaltador:", gameData['top_rbs_name'] ?? '---'); adicionarDestaque("Melhor Passador:", gameData['top_ast_name'] ?? '---'); adicionarDestaque("MVP da Partida:", gameData['mvp_name'] ?? '---'); } sheet.setColumnWidth(0, 18.0); sheet.setColumnWidth(1, 26.0); sheet.setColumnWidth(2, 8.0); sheet.setColumnWidth(3, 6.0); for(int i=4; i<=18; i++) sheet.setColumnWidth(i, 5.5); var fileBytes = excel.save(); if (fileBytes != null) { final directory = await getTemporaryDirectory(); String safeName = targetTeam == 'Ambas' ? '${myTeam}_vs_${opponentTeam}'.replaceAll(' ', '_') : targetTeam.replaceAll(' ', '_'); final filePath = '${directory.path}/BoxScore_$safeName.xlsx'; File(filePath)..createSync(recursive: true)..writeAsBytesSync(fileBytes); await Share.shareXFiles([XFile(filePath)], text: 'Estatísticas do Jogo: $myTeam vs $opponentTeam'); } } catch (e) { debugPrint('Erro ao gerar Excel: $e'); } } }