import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../../services/project_service.dart'; /// „Csatlakozás közös projekthez" — a Supabase-en elérhető közös /// projektek listája, egy koppintásos csatlakozással. /// /// A lista a ts_shared_projects view-ból jön (név, tulajdonos, tagszám, /// és hogy a felhasználó már tag-e). Hálózat nélkül a lista nem érhető el — /// erről barátságos üzenet szól; a MÁR csatlakozott projektek viszont /// offline is teljes értékűen használhatók, ez csak az ÚJ csatlakozáshoz /// kell. class JoinProjectSheet extends StatefulWidget { const JoinProjectSheet({super.key}); static Future show() { return Get.bottomSheet( const JoinProjectSheet(), isScrollControlled: true, ); } @override State createState() => _JoinProjectSheetState(); } class _JoinProjectSheetState extends State { late Future>> _future; String? _joiningId; @override void initState() { super.initState(); // TODO // _future = ProjectService.to.fetchSharedProjects(); } void _reload() { setState(() { // TODO //_future = ProjectService.to.fetchSharedProjects(); }); } Future _join(Map row) async { setState(() => _joiningId = row['id'] as String); try { // TODO //final project = await ProjectService.to.joinSharedProject(row); Get.back(); // Get.snackbar( // 'Csatlakozva', // '„${project.name}" — az adatok letöltése a háttérben fut.', // snackPosition: SnackPosition.BOTTOM, // ); } catch (e) { Get.snackbar( 'Hiba a csatlakozáskor', e.toString(), snackPosition: SnackPosition.BOTTOM, backgroundColor: const Color(0xFFB71C1C), colorText: const Color(0xFFFFFFFF), ); } finally { if (mounted) setState(() => _joiningId = null); } } @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; return SafeArea( top: false, child: Material( color: colorScheme.surface, borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), clipBehavior: Clip.antiAlias, child: ConstrainedBox( constraints: BoxConstraints( maxHeight: MediaQuery.sizeOf(context).height * 0.75, ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( width: 42, height: 4, margin: const EdgeInsets.only(top: 12, bottom: 10), decoration: BoxDecoration( color: colorScheme.outlineVariant, borderRadius: BorderRadius.circular(999), ), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( children: [ const Icon(Icons.groups_outlined), const SizedBox(width: 8), Text('Közös projektek', style: Theme.of(context).textTheme.titleLarge), const Spacer(), IconButton( icon: const Icon(Icons.refresh), tooltip: 'Frissítés', onPressed: _reload, ), ], ), ), const Divider(height: 16), Flexible( child: FutureBuilder>>( future: _future, builder: (context, snap) { if (snap.connectionState == ConnectionState.waiting) { return const Padding( padding: EdgeInsets.all(40), child: Center(child: CircularProgressIndicator()), ); } if (snap.hasError) { return _Message( icon: Icons.cloud_off, text: 'A közös projektlista most nem érhető el.\n' 'Ellenőrizd a hálózatot, majd frissíts.\n\n' 'A már csatlakozott projektjeid offline is\n' 'teljes értékűen használhatók.', onRetry: _reload, ); } final rows = snap.data ?? []; if (rows.isEmpty) { return const _Message( icon: Icons.folder_off_outlined, text: 'Még nincs közös projekt.\n' 'Hozz létre egyet „Közös projekt" típussal,\n' 'és a csapat többi tagja itt fogja látni.', ); } return ListView.separated( shrinkWrap: true, itemCount: rows.length, separatorBuilder: (_, __) => const Divider(height: 1), itemBuilder: (context, i) { final r = rows[i]; final isMember = r['is_member'] == true; final joining = _joiningId == r['id']; return ListTile( leading: Icon(Icons.folder_shared_outlined, color: _hexColor(r['color'] as String?)), title: Text(r['name'] as String? ?? ''), subtitle: Text( [ if ((r['owner_name'] as String?)?.isNotEmpty ?? false) r['owner_name'], '${r['member_count'] ?? 0} tag', if ((r['client'] as String?)?.isNotEmpty ?? false) r['client'], ].join(' · '), style: const TextStyle(fontSize: 12), ), trailing: isMember ? const Chip( label: Text('Tag vagy'), visualDensity: VisualDensity.compact, ) : joining ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator( strokeWidth: 2), ) : FilledButton.tonal( onPressed: () => _join(r), child: const Text('Csatlakozás'), ), ); }, ); }, ), ), const SizedBox(height: 8), ], ), ), ), ); } } class _Message extends StatelessWidget { final IconData icon; final String text; final VoidCallback? onRetry; const _Message({required this.icon, required this.text, this.onRetry}); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(32), child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(icon, size: 40, color: Colors.grey.shade400), const SizedBox(height: 12), Text(text, textAlign: TextAlign.center, style: TextStyle(color: Colors.grey.shade600)), if (onRetry != null) ...[ const SizedBox(height: 12), TextButton(onPressed: onRetry, child: const Text('Újra')), ], ], ), ); } } Color _hexColor(String? hex) { if (hex == null || hex.isEmpty) return const Color(0xFF185FA5); final h = hex.replaceFirst('#', ''); return Color(int.parse(h.length == 6 ? 'FF$h' : h, radix: 16)); }