From af765fc5ab1d894e0f25a07742d762d53d24f400 Mon Sep 17 00:00:00 2001 From: Diogo Date: Wed, 4 Mar 2026 00:46:21 +0000 Subject: [PATCH] nao repetir o numero e mais algo tou com sono --- lib/pages/PlacarPage.dart | 24 ++-- lib/screens/team_stats_page.dart | 201 ++++++++++++++++++++++++++++--- lib/widgets/game_widgets.dart | 2 +- 3 files changed, 195 insertions(+), 32 deletions(-) diff --git a/lib/pages/PlacarPage.dart b/lib/pages/PlacarPage.dart index b064d81..f542ab7 100644 --- a/lib/pages/PlacarPage.dart +++ b/lib/pages/PlacarPage.dart @@ -1,4 +1,3 @@ - import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -424,8 +423,9 @@ class _PlacarPageState extends State { } List _buildBenchPlayers(List bench, bool isOpponent) { - final teamColor = isOpponent ? const Color(0xFF8B1A1A) : const Color(0xFF1E5BB2); - final prefix = isOpponent ? "sub_opp_" : "sub_my_"; + final teamColor = isOpponent ? const Color(0xFFD92C2C) : const Color(0xFF1E5BB2); + // CORREÇÃO: Utilização do prefixo 'bench_' em vez de 'sub_' + final prefix = isOpponent ? "bench_opp_" : "bench_my_"; return bench.map((playerName) { final num = _playerNumbers[playerName]!; @@ -489,12 +489,13 @@ class _PlacarPageState extends State { final action = details.data; if (action.startsWith("add_") || action.startsWith("sub_") || action.startsWith("miss_")) { - _handleActionDrag(action, "$prefix$name"); // CHAMA A NOVA LÓGICA DE INTERCEÇÃO + _handleActionDrag(action, "$prefix$name"); } - else { + // CORREÇÃO: Nova lógica que processa apenas ações que comecem por 'bench_' para substituições + else if (action.startsWith("bench_")) { setState(() { - if (action.startsWith("sub_my_") && !isOpponent) { - String benchPlayer = action.replaceAll("sub_my_", ""); + if (action.startsWith("bench_my_") && !isOpponent) { + String benchPlayer = action.replaceAll("bench_my_", ""); if (_playerStats[benchPlayer]!["fls"]! >= 5) return; int courtIndex = _myCourt.indexOf(name); @@ -504,8 +505,8 @@ class _PlacarPageState extends State { _showMyBench = false; ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Sai $name, Entra $benchPlayer'))); } - if (action.startsWith("sub_opp_") && isOpponent) { - String benchPlayer = action.replaceAll("sub_opp_", ""); + if (action.startsWith("bench_opp_") && isOpponent) { + String benchPlayer = action.replaceAll("bench_opp_", ""); if (_playerStats[benchPlayer]!["fls"]! >= 5) return; int courtIndex = _oppCourt.indexOf(name); @@ -519,7 +520,8 @@ class _PlacarPageState extends State { } }, builder: (context, candidateData, rejectedData) { - bool isSubbing = candidateData.any((data) => data != null && data.startsWith("sub_my_") || data != null && data.startsWith("sub_opp_")); + // CORREÇÃO: Atualização da verificação de hover com base no novo prefixo + bool isSubbing = candidateData.any((data) => data != null && (data.startsWith("bench_my_") || data.startsWith("bench_opp_"))); bool isActionHover = candidateData.any((data) => data != null && (data.startsWith("add_") || data.startsWith("sub_") || data.startsWith("miss_"))); return _playerCardUI(number, name, stats, teamColor, isSubbing, isActionHover); }, @@ -870,4 +872,4 @@ Widget _circle(String label, Color color, IconData? icon, bool isFeed, {double f ], ); } -} +} \ No newline at end of file diff --git a/lib/screens/team_stats_page.dart b/lib/screens/team_stats_page.dart index f8f9f35..4fdb314 100644 --- a/lib/screens/team_stats_page.dart +++ b/lib/screens/team_stats_page.dart @@ -3,8 +3,171 @@ import 'package:flutter/material.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import '../models/team_model.dart'; import '../models/person_model.dart'; -import '../widgets/team_widgets.dart'; -import '../widgets/stats_widgets.dart'; + +// ========================================== +// 1. WIDGETS +// ========================================== + +// --- CABEÇALHO --- +class StatsHeader extends StatelessWidget { + final Team team; + + const StatsHeader({super.key, required this.team}); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.only(top: 50, left: 20, right: 20, bottom: 20), + decoration: const BoxDecoration( + color: Color(0xFF2C3E50), + borderRadius: BorderRadius.only(bottomLeft: Radius.circular(30), bottomRight: Radius.circular(30)), + ), + child: Row( + children: [ + IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () => Navigator.pop(context), + ), + const SizedBox(width: 10), + + // IMAGEM OU EMOJI DA EQUIPA AQUI! + CircleAvatar( + radius: 24, + backgroundColor: Colors.white24, + backgroundImage: (team.imageUrl.isNotEmpty && team.imageUrl.startsWith('http')) + ? NetworkImage(team.imageUrl) + : null, + child: (team.imageUrl.isEmpty || !team.imageUrl.startsWith('http')) + ? Text( + team.imageUrl.isEmpty ? "🛡️" : team.imageUrl, + style: const TextStyle(fontSize: 20), + ) + : null, + ), + + const SizedBox(width: 15), + Expanded( // Expanded evita overflow se o nome for muito longo + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(team.name, style: const TextStyle(color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold), overflow: TextOverflow.ellipsis), + Text(team.season, style: const TextStyle(color: Colors.white70, fontSize: 14)), + ], + ), + ), + ], + ), + ); + } +} + +// --- CARD DE RESUMO --- +class StatsSummaryCard extends StatelessWidget { + final int total; + + const StatsSummaryCard({super.key, required this.total}); + + @override + Widget build(BuildContext context) { + return Card( + elevation: 4, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), + child: Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + gradient: LinearGradient(colors: [Colors.blue.shade700, Colors.blue.shade400]), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text("Total de Membros", style: TextStyle(color: Colors.white, fontSize: 16)), + Text("$total", style: const TextStyle(color: Colors.white, fontSize: 28, fontWeight: FontWeight.bold)), + ], + ), + ), + ); + } +} + +// --- TÍTULO DE SECÇÃO --- +class StatsSectionTitle extends StatelessWidget { + final String title; + + const StatsSectionTitle({super.key, required this.title}); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(title, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Color(0xFF2C3E50))), + const Divider(), + ], + ); + } +} + +// --- CARD DA PESSOA (JOGADOR/TREINADOR) --- +class PersonCard extends StatelessWidget { + final Person person; + final bool isCoach; + final VoidCallback onEdit; + final VoidCallback onDelete; + + const PersonCard({ + super.key, + required this.person, + required this.isCoach, + required this.onEdit, + required this.onDelete, + }); + + @override + Widget build(BuildContext context) { + return Card( + margin: const EdgeInsets.only(top: 12), + elevation: 2, + color: isCoach ? const Color(0xFFFFF9C4) : Colors.white, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), + child: ListTile( + leading: isCoach + ? const CircleAvatar(backgroundColor: Colors.orange, child: Icon(Icons.person, color: Colors.white)) + : Container( + width: 45, + height: 45, + alignment: Alignment.center, + decoration: BoxDecoration(color: Colors.blue.withOpacity(0.1), borderRadius: BorderRadius.circular(10)), + child: Text(person.number ?? "J", style: const TextStyle(color: Colors.blue, fontWeight: FontWeight.bold, fontSize: 16)), + ), + title: Text(person.name, style: const TextStyle(fontWeight: FontWeight.bold)), + + // --- CANTO DIREITO (Trailing) --- + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + // IMAGEM DA EQUIPA NO CARD DO JOGADOR + + const SizedBox(width: 5), // Espaço + + IconButton( + icon: const Icon(Icons.edit_outlined, color: Colors.blue), + onPressed: onEdit, + ), + IconButton( + icon: const Icon(Icons.delete_outline, color: Colors.red), + onPressed: onDelete, + ), + ], + ), + ), + ); + } +} + +// ========================================== +// 2. PÁGINA PRINCIPAL +// ========================================== class TeamStatsPage extends StatefulWidget { final Team team; @@ -24,12 +187,11 @@ class _TeamStatsPageState extends State { backgroundColor: const Color(0xFFF5F7FA), body: Column( children: [ - // Cabeçalho com informações da equipa + // Cabeçalho StatsHeader(team: widget.team), Expanded( child: StreamBuilder>( - // O StreamBuilder reconstrói a UI automaticamente sempre que o Supabase envia novos dados stream: _controller.getMembers(widget.team.id), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { @@ -42,12 +204,11 @@ class _TeamStatsPageState extends State { final members = snapshot.data ?? []; - // Filtros para organizar a lista final coaches = members.where((m) => m.type == 'Treinador').toList(); final players = members.where((m) => m.type == 'Jogador').toList(); return RefreshIndicator( - onRefresh: () async => setState(() {}), // Pull to refresh como backup + onRefresh: () async => setState(() {}), child: SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), padding: const EdgeInsets.all(16.0), @@ -57,25 +218,25 @@ class _TeamStatsPageState extends State { StatsSummaryCard(total: members.length), const SizedBox(height: 30), - // SECÇÃO TREINADORES + // TREINADORES if (coaches.isNotEmpty) ...[ const StatsSectionTitle(title: "Treinadores"), ...coaches.map((c) => PersonCard( person: c, isCoach: true, + onEdit: () => _controller.showEditPersonDialog(context, widget.team.id, c), onDelete: () => _confirmDelete(context, c), )), const SizedBox(height: 30), ], - // SECÇÃO JOGADORES + // JOGADORES const StatsSectionTitle(title: "Jogadores"), if (players.isEmpty) const Padding( padding: EdgeInsets.only(top: 20), - child: Text("Nenhum jogador nesta equipa.", - style: TextStyle(color: Colors.grey, fontSize: 16)), + child: Text("Nenhum jogador nesta equipa.", style: TextStyle(color: Colors.grey, fontSize: 16)), ) else ...players.map((p) => PersonCard( @@ -84,7 +245,7 @@ class _TeamStatsPageState extends State { onEdit: () => _controller.showEditPersonDialog(context, widget.team.id, p), onDelete: () => _confirmDelete(context, p), )), - const SizedBox(height: 80), // Espaço para o FAB não tapar o último card + const SizedBox(height: 80), ], ), ), @@ -95,7 +256,6 @@ class _TeamStatsPageState extends State { ], ), floatingActionButton: FloatingActionButton( - // Hero tag única para evitar o erro de tags duplicadas heroTag: 'fab_team_${widget.team.id}', onPressed: () => _controller.showAddPersonDialog(context, widget.team.id), backgroundColor: const Color(0xFF00C853), @@ -125,22 +285,22 @@ class _TeamStatsPageState extends State { } } -// --- CONTROLLER SUPABASE --- +// ========================================== +// 3. CONTROLLER +// ========================================== class StatsController { final _supabase = Supabase.instance.client; - // 1. LER (A escuta em tempo real) Stream> getMembers(String teamId) { return _supabase .from('members') - .stream(primaryKey: ['id']) // Garante que a PK na tabela é 'id' + .stream(primaryKey: ['id']) .eq('team_id', teamId) .order('name', ascending: true) .map((data) => data.map((json) => Person.fromMap(json)).toList()); } - // 2. APAGAR Future deletePerson(String personId) async { try { await _supabase.from('members').delete().eq('id', personId); @@ -149,7 +309,6 @@ class StatsController { } } - // 3. DIÁLOGOS void showAddPersonDialog(BuildContext context, String teamId) { _showForm(context, teamId: teamId); } @@ -237,8 +396,12 @@ class StatsController { } catch (e) { debugPrint("Erro Supabase: $e"); if (ctx.mounted) { + String errorMsg = "Erro ao guardar: $e"; + if (e.toString().contains('unique')) { + errorMsg = "Já existe um membro com este nome na equipa."; + } ScaffoldMessenger.of(ctx).showSnackBar( - SnackBar(content: Text("Erro ao guardar: $e"), backgroundColor: Colors.red) + SnackBar(content: Text(errorMsg), backgroundColor: Colors.red) ); } } @@ -250,6 +413,4 @@ class StatsController { ), ); } - - void dispose() {} } \ No newline at end of file diff --git a/lib/widgets/game_widgets.dart b/lib/widgets/game_widgets.dart index cc8cd37..9ea61fd 100644 --- a/lib/widgets/game_widgets.dart +++ b/lib/widgets/game_widgets.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:playmaker/pages/PlacarPage.dart'; // Garante que o import está correto import '../controllers/team_controller.dart'; -import '../controllers/game_controller.dart'; // Import necessário +import '../controllers/game_controller.dart'; // --- CARD DE EXIBIÇÃO DO JOGO (Mantém-se quase igual) --- class GameResultCard extends StatelessWidget {