import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:intl/intl.dart'; import '../../models/project.dart'; import '../../services/project_service.dart'; // ─── Segéd: hex string → Flutter Color ─────────────────────────────────────── Color _hexColor(String hex) { final h = hex.replaceFirst('#', ''); return Color(int.parse(h.length == 6 ? 'FF$h' : h, radix: 16)); } // ─── ProjectPickerView ──────────────────────────────────────────────────────── class ProjectPickerView extends StatelessWidget { const ProjectPickerView({super.key}); @override Widget build(BuildContext context) { final svc = ProjectService.to; return Scaffold( appBar: AppBar( title: const Text('Projekt választó'), actions: [ IconButton( icon: const Icon(Icons.add), tooltip: 'Új projekt', onPressed: () => ProjectCreateDialog.show(), ), ], ), body: Obx(() { if (svc.projects.isEmpty) { return _EmptyState(onCreateTap: ProjectCreateDialog.show); } return RefreshIndicator( onRefresh: svc.reloadProjects, child: ListView.separated( padding: const EdgeInsets.symmetric(vertical: 8), itemCount: svc.projects.length, separatorBuilder: (_, __) => const Divider(height: 1), itemBuilder: (_, i) => _ProjectTile(project: svc.projects[i]), ), ); }), ); } } // ─── Projekt sor ────────────────────────────────────────────────────────────── class _ProjectTile extends StatefulWidget { final Project project; const _ProjectTile({required this.project}); @override State<_ProjectTile> createState() => _ProjectTileState(); } class _ProjectTileState extends State<_ProjectTile> { Map _stats = {'points': 0, 'tracks': 0, 'notes': 0}; @override void initState() { super.initState(); _loadStats(); } Future _loadStats() async { if (widget.project.id == null) return; final s = await ProjectService.to.getStats(widget.project.id!); if (mounted) setState(() => _stats = s); } @override Widget build(BuildContext context) { final svc = ProjectService.to; final project = widget.project; final color = _hexColor(project.color); return Obx(() { final isActive = svc.activeProject.value?.id == project.id; return ListTile( selected: isActive, selectedTileColor: color.withOpacity(0.08), leading: Stack(children: [ CircleAvatar( radius: 22, backgroundColor: color.withOpacity(0.15), child: Text( project.name.substring(0, 1).toUpperCase(), style: TextStyle( color: color, fontWeight: FontWeight.w700, fontSize: 16, ), ), ), if (isActive) Positioned( right: 0, bottom: 0, child: Container( width: 12, height: 12, decoration: BoxDecoration( color: Colors.green, shape: BoxShape.circle, border: Border.all( color: Theme.of(context).colorScheme.surface, width: 1.5, ), ), ), ), ]), title: Row(children: [ Expanded( child: Text( project.name, style: TextStyle( fontWeight: FontWeight.w600, color: isActive ? color : null, ), ), ), _SyncBadge(isLocalOnly: project.isLocalOnly), ]), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (project.client.isNotEmpty) ...[ Text(project.client, style: const TextStyle(fontSize: 12)), const SizedBox(height: 2), ], Row(children: [ _StatBadge( Icons.location_on_outlined, _stats['points'] ?? 0, 'pont'), const SizedBox(width: 8), _StatBadge(Icons.route, _stats['tracks'] ?? 0, 'track'), const SizedBox(width: 8), _StatBadge(Icons.sticky_note_2_outlined, _stats['notes'] ?? 0, 'jegyzet'), ]), const SizedBox(height: 2), Text( 'Létrehozva: ${DateFormat('yyyy.MM.dd').format(project.createdAt)}' ' · ${project.crs.name.toUpperCase()}', style: const TextStyle(fontSize: 11, color: Colors.grey), ), ], ), isThreeLine: true, trailing: PopupMenuButton<_Action>( icon: const Icon(Icons.more_vert, size: 20), onSelected: (a) => _onAction(a), itemBuilder: (_) => [ const PopupMenuItem( value: _Action.edit, child: ListTile( leading: Icon(Icons.edit_outlined), title: Text('Szerkesztés'), dense: true, ), ), const PopupMenuDivider(), const PopupMenuItem( value: _Action.archive, child: ListTile( leading: Icon(Icons.archive_outlined, color: Colors.orange), title: Text('Archiválás', style: TextStyle(color: Colors.orange)), dense: true, ), ), ], ), onTap: () async { await svc.setActiveProject(project); Get.back(); }, ); }); } void _onAction(_Action action) { switch (action) { case _Action.edit: ProjectCreateDialog.show(existing: widget.project); case _Action.archive: _confirmArchive(); } } void _confirmArchive() { Get.dialog(AlertDialog( title: const Text('Projekt archiválása'), content: Text( '"${widget.project.name}" archiválásra kerül.\n' 'Az adatok megmaradnak, de a projekt nem lesz aktívan elérhető.', ), actions: [ TextButton(onPressed: Get.back, child: const Text('Mégse')), FilledButton( style: FilledButton.styleFrom(backgroundColor: Colors.orange), onPressed: () { Get.back(); ProjectService.to.archiveProject(widget.project.id!); }, child: const Text('Archiválás'), ), ], )); } } enum _Action { edit, archive } // ─── Üres állapot ───────────────────────────────────────────────────────────── class _EmptyState extends StatelessWidget { final VoidCallback onCreateTap; const _EmptyState({required this.onCreateTap}); @override Widget build(BuildContext context) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.folder_outlined, size: 64, color: Colors.grey.shade400), const SizedBox(height: 16), Text('Nincs még projekt', style: Theme.of(context) .textTheme .titleMedium ?.copyWith(color: Colors.grey)), const SizedBox(height: 8), Text('Hozz létre egy projektet a kezdéshez.', style: TextStyle(color: Colors.grey.shade500)), const SizedBox(height: 24), FilledButton.icon( icon: const Icon(Icons.add), label: const Text('Új projekt létrehozása'), onPressed: onCreateTap, ), ], ), ); } } // ─── Segéd widgetek ─────────────────────────────────────────────────────────── class _SyncBadge extends StatelessWidget { final bool isLocalOnly; const _SyncBadge({required this.isLocalOnly}); @override Widget build(BuildContext context) { final color = isLocalOnly ? Colors.grey : Colors.blue; return Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(4), border: Border.all(color: color.withOpacity(0.3)), ), child: Row(mainAxisSize: MainAxisSize.min, children: [ Icon( isLocalOnly ? Icons.phone_android : Icons.cloud_outlined, size: 10, color: color, ), const SizedBox(width: 3), Text(isLocalOnly ? 'Lokális' : 'Felhős', style: TextStyle(fontSize: 10, color: color)), ]), ); } } class _StatBadge extends StatelessWidget { final IconData icon; final int count; final String label; const _StatBadge(this.icon, this.count, this.label); @override Widget build(BuildContext context) => Row( mainAxisSize: MainAxisSize.min, children: [ Icon(icon, size: 11, color: Colors.grey), const SizedBox(width: 2), Text('$count $label', style: const TextStyle(fontSize: 11, color: Colors.grey)), ], ); } // ─── ProjectCreateDialog ────────────────────────────────────────────────────── class ProjectCreateDialog extends StatefulWidget { final Project? existing; const ProjectCreateDialog({super.key, this.existing}); static void show({Project? existing}) => Get.dialog( ProjectCreateDialog(existing: existing), barrierDismissible: false, ); @override State createState() => _ProjectCreateDialogState(); } class _ProjectCreateDialogState extends State { late final TextEditingController _nameCtrl; late final TextEditingController _clientCtrl; late final TextEditingController _descCtrl; late ProjectCrs _crs; late String _colorHex; late bool _isLocalOnly; bool _isSubmitting = false; bool get _isEditing => widget.existing != null; static const _colorOptions = [ '#185FA5', '#2E7D32', '#C62828', '#E65100', '#6A1B9A', '#00695C', '#283593', '#4E342E', ]; @override void initState() { super.initState(); final p = widget.existing; _nameCtrl = TextEditingController(text: p?.name ?? ''); _clientCtrl = TextEditingController(text: p?.client ?? ''); _descCtrl = TextEditingController(text: p?.description ?? ''); _crs = p?.crs ?? ProjectCrs.eov; _colorHex = p?.color ?? _colorOptions.first; _isLocalOnly = p?.isLocalOnly ?? true; } @override void dispose() { _nameCtrl.dispose(); _clientCtrl.dispose(); _descCtrl.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Dialog( insetPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 24), child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 480), child: SingleChildScrollView( padding: const EdgeInsets.all(24), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ // Fejléc Row(children: [ Text( _isEditing ? 'Projekt szerkesztése' : 'Új projekt', style: Theme.of(context).textTheme.titleLarge, ), const Spacer(), IconButton(icon: const Icon(Icons.close), onPressed: Get.back), ]), const SizedBox(height: 20), // Név TextField( controller: _nameCtrl, autofocus: true, decoration: const InputDecoration( labelText: 'Projekt neve *', hintText: 'pl. Dunántúli 2D vonal', border: OutlineInputBorder(), prefixIcon: Icon(Icons.folder_outlined), ), textCapitalization: TextCapitalization.sentences, ), const SizedBox(height: 12), // // Megrendelő // TextField( // controller: _clientCtrl, // decoration: const InputDecoration( // labelText: 'Megrendelő', // hintText: 'pl. MOL Nyrt.', // border: OutlineInputBorder(), // prefixIcon: Icon(Icons.business_outlined), // ), // ), // const SizedBox(height: 12), // Leírás TextField( controller: _descCtrl, maxLines: 2, decoration: const InputDecoration( labelText: 'Megjegyzés', border: OutlineInputBorder(), prefixIcon: Icon(Icons.notes), ), ), const SizedBox(height: 16), // Koordináta-rendszer const _Label('Koordináta-rendszer'), const SizedBox(height: 6), SegmentedButton( segments: const [ ButtonSegment( value: ProjectCrs.eov, label: Text('EOV (HD72)'), icon: Icon(Icons.straighten, size: 14), ), ButtonSegment( value: ProjectCrs.wgs84, label: Text('WGS84'), icon: Icon(Icons.public, size: 14), ), ], selected: {_crs}, onSelectionChanged: (s) => setState(() => _crs = s.first), ), // Tárolás — csak új projektnél if (!_isEditing) ...[ const SizedBox(height: 16), const _Label('Adattárolás'), const SizedBox(height: 6), SegmentedButton( segments: const [ ButtonSegment( value: true, label: Text('Csak lokális'), icon: Icon(Icons.phone_android, size: 14), ), ButtonSegment( value: false, label: Text('Felhős mentés'), icon: Icon(Icons.cloud_outlined, size: 14), ), ], selected: {_isLocalOnly}, onSelectionChanged: (s) => setState(() => _isLocalOnly = s.first), ), const SizedBox(height: 6), Text( _isLocalOnly ? '📱 Az adatok csak ezen a készüléken maradnak.' : '☁ Az adatok szinkronizálódnak a szerverre.', style: TextStyle(fontSize: 12, color: Colors.grey.shade600), ), ], const SizedBox(height: 16), // Szín const _Label('Projekt szín'), const SizedBox(height: 8), Wrap( spacing: 8, runSpacing: 6, children: _colorOptions.map((hex) { final selected = _colorHex == hex; return GestureDetector( onTap: () => setState(() => _colorHex = hex), child: AnimatedContainer( duration: const Duration(milliseconds: 150), width: 32, height: 32, decoration: BoxDecoration( color: _hexColor(hex), shape: BoxShape.circle, border: selected ? Border.all( color: Theme.of(context).colorScheme.onSurface, width: 2.5) : null, boxShadow: selected ? [ BoxShadow( color: _hexColor(hex).withOpacity(0.5), blurRadius: 6, spreadRadius: 1) ] : null, ), child: selected ? const Icon(Icons.check, color: Colors.white, size: 16) : null, ), ); }).toList(), ), const SizedBox(height: 24), // Gombok Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( onPressed: _isSubmitting ? null : Get.back, child: const Text('Mégse'), ), const SizedBox(width: 12), FilledButton( onPressed: _isSubmitting ? null : _submit, child: _isSubmitting ? const SizedBox( width: 18, height: 18, child: CircularProgressIndicator(strokeWidth: 2)) : Text(_isEditing ? 'Mentés' : 'Létrehozás'), ), ], ), ], ), ), ), ); } Future _submit() async { final name = _nameCtrl.text.trim(); if (name.isEmpty) { Get.snackbar('Hiányzó adat', 'A projekt neve kötelező.', snackPosition: SnackPosition.TOP); return; } setState(() => _isSubmitting = true); try { final svc = ProjectService.to; if (_isEditing) { await svc.updateProject(widget.existing!.copyWith( name: name, client: _clientCtrl.text.trim(), description: _descCtrl.text.trim(), crs: _crs, color: _colorHex, )); Get.back(); Get.snackbar('Projekt frissítve', name, snackPosition: SnackPosition.BOTTOM); } else { final Project created; if (_isLocalOnly) { created = await svc.createLocalProject( name: name, client: _clientCtrl.text.trim(), crs: _crs, color: _colorHex, ); } else { created = await svc.createOnlineProject( name: name, client: _clientCtrl.text.trim(), crs: _crs, color: _colorHex, ); } await svc.setActiveProject(created); Get.snackbar( 'Projekt létrehozva', '$name — aktív projektnek beállítva.', snackPosition: SnackPosition.BOTTOM, duration: const Duration(seconds: 3), ); } } catch (e) { Get.snackbar('Hiba', e.toString(), backgroundColor: Colors.red, colorText: Colors.white, snackPosition: SnackPosition.TOP); } finally { if (mounted) setState(() => _isSubmitting = false); } } } class _Label extends StatelessWidget { final String text; const _Label(this.text); @override Widget build(BuildContext context) => Text( text, style: const TextStyle(fontSize: 12, color: Colors.grey), ); }