Files
PlayMaker/lib/pages/status_page.dart
2026-03-13 18:08:15 +00:00

296 lines
14 KiB
Dart

import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import '../controllers/team_controller.dart';
import '../utils/size_extension.dart';
import 'dart:math' as math;
import '../controllers/placar_controller.dart'; // Para a classe ShotRecord
import '../pages/heatmap_page.dart'; // Para abrir a página do mapa
class StatusPage extends StatefulWidget {
const StatusPage({super.key});
@override
State<StatusPage> createState() => _StatusPageState();
}
class _StatusPageState extends State<StatusPage> {
final TeamController _teamController = TeamController();
final _supabase = Supabase.instance.client;
String? _selectedTeamId;
String _selectedTeamName = "Selecionar Equipa";
String _sortColumn = 'pts';
bool _isAscending = false;
// 👇 NOVA FUNÇÃO: BUSCA OS LANÇAMENTOS DO JOGADOR NO SUPABASE E ABRE O MAPA
Future<void> _openPlayerHeatmap(String playerName) async {
if (_selectedTeamId == null) return;
// Mostra um loading rápido
showDialog(
context: context,
barrierDismissible: false,
builder: (_) => const Center(child: CircularProgressIndicator(color: Color(0xFFE74C3C)))
);
try {
final response = await _supabase
.from('game_shots')
.select()
.eq('team_id', _selectedTeamId!)
.eq('player_name', playerName);
if (mounted) Navigator.pop(context); // Fecha o loading
if (response == null || (response as List).isEmpty) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('O $playerName ainda não tem lançamentos registados!'))
);
}
return;
}
final List<ShotRecord> shots = (response as List).map((s) => ShotRecord(
relativeX: (s['relative_x'] as num).toDouble(),
relativeY: (s['relative_y'] as num).toDouble(),
isMake: s['is_make'] as bool,
playerName: s['player_name'],
)).toList();
if (mounted) {
Navigator.push(context, MaterialPageRoute(
builder: (_) => HeatmapPage(shots: shots, teamName: "Mapa de: $playerName")
));
}
} catch (e) {
if (mounted) Navigator.pop(context);
debugPrint("Erro ao carregar heatmap: $e");
}
}
@override
Widget build(BuildContext context) {
final double safeSf = math.min(context.sf, 1.15);
final double screenWidth = MediaQuery.of(context).size.width;
return Column(
children: [
// --- SELETOR DE EQUIPA ---
Padding(
padding: EdgeInsets.all(16.0 * safeSf),
child: InkWell(
onTap: () => _showTeamSelector(context, safeSf),
child: Container(
padding: EdgeInsets.all(12 * safeSf),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15 * safeSf),
border: Border.all(color: Colors.grey.shade300),
boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 5)]
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(children: [
Icon(Icons.shield, color: const Color(0xFFE74C3C), size: 24 * safeSf),
SizedBox(width: 10 * safeSf),
Text(_selectedTeamName, style: TextStyle(fontSize: 16 * safeSf, fontWeight: FontWeight.bold))
]),
const Icon(Icons.arrow_drop_down),
],
),
),
),
),
// --- TABELA DE ESTATÍSTICAS ---
Expanded(
child: _selectedTeamId == null
? Center(child: Text("Seleciona uma equipa acima.", style: TextStyle(color: Colors.grey, fontSize: 14 * safeSf)))
: StreamBuilder<List<Map<String, dynamic>>>(
stream: _supabase.from('player_stats_with_names').stream(primaryKey: ['id']).eq('team_id', _selectedTeamId!),
builder: (context, statsSnapshot) {
return StreamBuilder<List<Map<String, dynamic>>>(
stream: _supabase.from('games').stream(primaryKey: ['id']).eq('my_team', _selectedTeamName),
builder: (context, gamesSnapshot) {
return StreamBuilder<List<Map<String, dynamic>>>(
stream: _supabase.from('members').stream(primaryKey: ['id']).eq('team_id', _selectedTeamId!),
builder: (context, membersSnapshot) {
if (statsSnapshot.connectionState == ConnectionState.waiting || gamesSnapshot.connectionState == ConnectionState.waiting || membersSnapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator(color: Color(0xFFE74C3C)));
}
final membersData = membersSnapshot.data ?? [];
if (membersData.isEmpty) return Center(child: Text("Esta equipa não tem jogadores registados.", style: TextStyle(color: Colors.grey, fontSize: 14 * safeSf)));
final statsData = statsSnapshot.data ?? [];
final gamesData = gamesSnapshot.data ?? [];
final totalGamesPlayedByTeam = gamesData.where((g) => g['status'] == 'Terminado').length;
final List<Map<String, dynamic>> playerTotals = _aggregateStats(statsData, gamesData, membersData);
final teamTotals = _calculateTeamTotals(playerTotals, totalGamesPlayedByTeam);
playerTotals.sort((a, b) {
var valA = a[_sortColumn] ?? 0;
var valB = b[_sortColumn] ?? 0;
return _isAscending ? valA.compareTo(valB) : valB.compareTo(valA);
});
return _buildStatsGrid(context, playerTotals, teamTotals, safeSf, screenWidth);
}
);
}
);
}
),
),
],
);
}
// (Lógica de _aggregateStats e _calculateTeamTotals continua igual...)
List<Map<String, dynamic>> _aggregateStats(List<dynamic> stats, List<dynamic> games, List<dynamic> members) {
Map<String, Map<String, dynamic>> aggregated = {};
for (var member in members) {
String name = member['name']?.toString() ?? "Desconhecido";
aggregated[name] = {'name': name, 'j': 0, 'pts': 0, 'ast': 0, 'rbs': 0, 'stl': 0, 'blk': 0, 'mvp': 0, 'def': 0};
}
for (var row in stats) {
String name = row['player_name']?.toString() ?? "Desconhecido";
if (!aggregated.containsKey(name)) aggregated[name] = {'name': name, 'j': 0, 'pts': 0, 'ast': 0, 'rbs': 0, 'stl': 0, 'blk': 0, 'mvp': 0, 'def': 0};
aggregated[name]!['j'] += 1; aggregated[name]!['pts'] += (row['pts'] ?? 0); aggregated[name]!['ast'] += (row['ast'] ?? 0);
aggregated[name]!['rbs'] += (row['rbs'] ?? 0); aggregated[name]!['stl'] += (row['stl'] ?? 0); aggregated[name]!['blk'] += (row['blk'] ?? 0);
}
for (var game in games) {
String? mvp = game['mvp_name']; String? defRaw = game['top_def_name'];
if (mvp != null && aggregated.containsKey(mvp)) aggregated[mvp]!['mvp'] += 1;
if (defRaw != null) {
String defName = defRaw.split(' (')[0].trim();
if (aggregated.containsKey(defName)) aggregated[defName]!['def'] += 1;
}
}
return aggregated.values.toList();
}
Map<String, dynamic> _calculateTeamTotals(List<Map<String, dynamic>> players, int teamGames) {
int tPts = 0, tAst = 0, tRbs = 0, tStl = 0, tBlk = 0, tMvp = 0, tDef = 0;
for (var p in players) {
tPts += (p['pts'] as int); tAst += (p['ast'] as int); tRbs += (p['rbs'] as int); tStl += (p['stl'] as int); tBlk += (p['blk'] as int); tMvp += (p['mvp'] as int); tDef += (p['def'] as int);
}
return {'name': 'TOTAL EQUIPA', 'j': teamGames, 'pts': tPts, 'ast': tAst, 'rbs': tRbs, 'stl': tStl, 'blk': tBlk, 'mvp': tMvp, 'def': tDef};
}
Widget _buildStatsGrid(BuildContext context, List<Map<String, dynamic>> players, Map<String, dynamic> teamTotals, double safeSf, double screenWidth) {
double dynamicSpacing = math.max(15 * safeSf, (screenWidth - (180 * safeSf)) / 8);
return Container(
color: Colors.white,
width: double.infinity,
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: ConstrainedBox(
constraints: BoxConstraints(minWidth: screenWidth),
child: DataTable(
columnSpacing: dynamicSpacing,
horizontalMargin: 20 * safeSf,
headingRowColor: MaterialStateProperty.all(Colors.grey.shade100),
dataRowHeight: 60 * safeSf,
columns: [
DataColumn(label: const Text('JOGADOR')),
_buildSortableColumn('J', 'j', safeSf),
_buildSortableColumn('PTS', 'pts', safeSf),
_buildSortableColumn('AST', 'ast', safeSf),
_buildSortableColumn('RBS', 'rbs', safeSf),
_buildSortableColumn('STL', 'stl', safeSf),
_buildSortableColumn('BLK', 'blk', safeSf),
_buildSortableColumn('DEF 🛡️', 'def', safeSf),
_buildSortableColumn('MVP 🏆', 'mvp', safeSf),
],
rows: [
...players.map((player) => DataRow(cells: [
DataCell(
// 👇 TORNEI O NOME CLICÁVEL PARA ABRIR O MAPA
InkWell(
onTap: () => _openPlayerHeatmap(player['name']),
child: Row(children: [
CircleAvatar(radius: 15 * safeSf, backgroundColor: Colors.grey.shade200, child: Icon(Icons.person, size: 18 * safeSf)),
SizedBox(width: 10 * safeSf),
Text(player['name'], style: TextStyle(fontWeight: FontWeight.bold, fontSize: 13 * safeSf, color: Colors.blue.shade700))
]),
)
),
DataCell(Center(child: Text(player['j'].toString()))),
_buildStatCell(player['pts'], safeSf, isHighlight: true),
_buildStatCell(player['ast'], safeSf),
_buildStatCell(player['rbs'], safeSf),
_buildStatCell(player['stl'], safeSf),
_buildStatCell(player['blk'], safeSf),
_buildStatCell(player['def'], safeSf, isBlue: true),
_buildStatCell(player['mvp'], safeSf, isGold: true),
])),
DataRow(
color: MaterialStateProperty.all(Colors.grey.shade50),
cells: [
DataCell(Text('TOTAL EQUIPA', style: TextStyle(fontWeight: FontWeight.w900, color: Colors.black, fontSize: 12 * safeSf))),
DataCell(Center(child: Text(teamTotals['j'].toString(), style: const TextStyle(fontWeight: FontWeight.bold)))),
_buildStatCell(teamTotals['pts'], safeSf, isHighlight: true),
_buildStatCell(teamTotals['ast'], safeSf),
_buildStatCell(teamTotals['rbs'], safeSf),
_buildStatCell(teamTotals['stl'], safeSf),
_buildStatCell(teamTotals['blk'], safeSf),
_buildStatCell(teamTotals['def'], safeSf, isBlue: true),
_buildStatCell(teamTotals['mvp'], safeSf, isGold: true),
]
)
],
),
),
),
),
);
}
// (Outras funções de build continuam igual...)
DataColumn _buildSortableColumn(String title, String sortKey, double safeSf) {
return DataColumn(label: InkWell(
onTap: () => setState(() {
if (_sortColumn == sortKey) _isAscending = !_isAscending;
else { _sortColumn = sortKey; _isAscending = false; }
}),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(title, style: TextStyle(fontSize: 12 * safeSf, fontWeight: FontWeight.bold)),
if (_sortColumn == sortKey) Icon(_isAscending ? Icons.arrow_drop_up : Icons.arrow_drop_down, size: 18 * safeSf, color: const Color(0xFFE74C3C)),
]
),
));
}
DataCell _buildStatCell(int value, double safeSf, {bool isHighlight = false, bool isGold = false, bool isBlue = false}) {
return DataCell(Center(child: Container(
padding: EdgeInsets.symmetric(horizontal: 8 * safeSf, vertical: 4 * safeSf),
decoration: BoxDecoration(color: isGold && value > 0 ? Colors.amber.withOpacity(0.2) : (isBlue && value > 0 ? Colors.blue.withOpacity(0.1) : Colors.transparent), borderRadius: BorderRadius.circular(6)),
child: Text(value == 0 ? "-" : value.toString(), style: TextStyle(
fontWeight: (isHighlight || isGold || isBlue) ? FontWeight.w900 : FontWeight.w600,
fontSize: 14 * safeSf, color: isGold && value > 0 ? Colors.orange.shade900 : (isBlue && value > 0 ? Colors.blue.shade800 : (isHighlight ? Colors.green.shade700 : Colors.black87))
)),
)));
}
void _showTeamSelector(BuildContext context, double safeSf) {
showModalBottomSheet(context: context, builder: (context) => StreamBuilder<List<Map<String, dynamic>>>(
stream: _teamController.teamsStream,
builder: (context, snapshot) {
final teams = snapshot.data ?? [];
return ListView.builder(itemCount: teams.length, itemBuilder: (context, i) => ListTile(
title: Text(teams[i]['name'], style: TextStyle(fontSize: 15 * safeSf)),
onTap: () { setState(() { _selectedTeamId = teams[i]['id']; _selectedTeamName = teams[i]['name']; }); Navigator.pop(context); },
));
},
));
}
}