Files
PlayMaker/lib/pages/excel_export_service.dart
2026-05-06 12:47:17 +01:00

375 lines
21 KiB
Dart

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<void> 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<String, Map<String, dynamic>> memberInfo = {
for (var m in membersResponse) m['id'].toString(): m
};
final teamsResponse = await supabase.from('teams').select('id, name');
final Map<String, String> 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<String> 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<dynamic> 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<dynamic> 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<String, List<dynamic>> 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<dynamic> 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<Map<String, dynamic>> 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<String> colsT1 = ["", "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<CellValue> 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<CellValue> 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<String> colsT2 = ["", "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<CellValue> 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<CellValue> 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');
}
}
}