import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../../core/providers/auth_provider.dart'; import '../../../core/theme/app_theme.dart'; import '../../../core/widgets/gradient_app_bar.dart'; import '../../../models/patient.dart'; import 'clinic_patients_repository.dart'; enum _PatientSort { nameAZ, nameZA, byCode } const _kSortLabels = [ 'Ada Göre (A → Z)', 'Ada Göre (Z → A)', 'Hasta Koduna Göre', ]; void _showAdaptive(BuildContext context, Widget content) { final isDesktop = MediaQuery.sizeOf(context).width > AppLayout.sidebarBreakpoint; if (isDesktop) { showDialog( context: context, builder: (_) => Dialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 560), child: content, ), ), ); } else { showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (_) => content, ); } } class ClinicPatientsScreen extends ConsumerStatefulWidget { const ClinicPatientsScreen({super.key}); @override ConsumerState createState() => _ClinicPatientsScreenState(); } class _ClinicPatientsScreenState extends ConsumerState { late Future> _future; final _searchController = TextEditingController(); String _searchQuery = ''; _PatientSort _sort = _PatientSort.nameAZ; @override void initState() { super.initState(); _load(); } @override void dispose() { _searchController.dispose(); super.dispose(); } void _load([String? search]) { final tenantId = ref.read(authProvider).activeTenant!.tenant.id; setState(() { _future = ClinicPatientsRepository.instance.listPatients( tenantId, search: search?.trim().isNotEmpty == true ? search : null, limit: 100, ); }); } void _onSearchChanged(String value) { setState(() => _searchQuery = value); _load(value); } Future _showSortOptions() async { final result = await showSortSheet( context, title: 'Sıralama', options: _kSortLabels, current: _sort.index, ); if (result != null) { setState(() => _sort = _PatientSort.values[result]); } } List _sorted(List patients) { final list = List.from(patients); switch (_sort) { case _PatientSort.nameAZ: list.sort((a, b) => a.displayName.compareTo(b.displayName)); case _PatientSort.nameZA: list.sort((a, b) => b.displayName.compareTo(a.displayName)); case _PatientSort.byCode: list.sort((a, b) => a.patientCode.compareTo(b.patientCode)); } return list; } void _showNewPatientSheet() { _showAdaptive( context, _NewPatientSheet( onCreated: () { Navigator.of(context).pop(); _load(_searchQuery); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Hasta oluşturuldu.')), ); }, ), ); } @override Widget build(BuildContext context) { final isSortActive = _sort != _PatientSort.nameAZ; return Scaffold( backgroundColor: AppColors.background, appBar: GradientAppBar( title: 'Hastalar', category: 'KLİNİK', searchController: _searchController, onSearchChanged: _onSearchChanged, searchHint: 'Ad, soyad veya kod ile arayın...', actions: [ IconButton( onPressed: _showSortOptions, tooltip: 'Sırala', icon: Badge( isLabelVisible: isSortActive, smallSize: 8, backgroundColor: AppColors.accent, child: const Icon(Icons.sort_rounded), ), ), IconButton( onPressed: _showNewPatientSheet, tooltip: 'Yeni Hasta', icon: const Icon(Icons.person_add_outlined), ), ], ), body: Column( children: [ Expanded( child: RefreshIndicator( color: AppColors.accent, onRefresh: () async => _load(_searchQuery), child: FutureBuilder>( future: _future, builder: (ctx, snap) { if (snap.connectionState == ConnectionState.waiting) { return const Center( child: CircularProgressIndicator( color: AppColors.accent)); } if (snap.hasError) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( width: 64, height: 64, decoration: BoxDecoration( color: AppColors.cancelledBg, borderRadius: BorderRadius.circular(16)), child: const Icon(Icons.wifi_off_rounded, color: AppColors.cancelled, size: 30), ), const SizedBox(height: 16), Text('Hata: ${snap.error}', style: const TextStyle( color: AppColors.textSecondary)), const SizedBox(height: 12), FilledButton.icon( onPressed: () => _load(_searchQuery), icon: const Icon(Icons.refresh_rounded, size: 18), label: const Text('Tekrar Dene'), ), ], ), ); } final patients = _sorted(snap.data!); if (patients.isEmpty) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( width: 72, height: 72, decoration: BoxDecoration( color: AppColors.inProgressBg, borderRadius: BorderRadius.circular(20)), child: const Icon(Icons.people_outline, color: AppColors.inProgress, size: 32), ), const SizedBox(height: 16), Text( _searchQuery.isNotEmpty ? 'Sonuç bulunamadı' : 'Henüz hasta yok', style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.textPrimary), ), if (_searchQuery.isEmpty) ...[ const SizedBox(height: 8), const Text( 'Yeni hasta eklemek için + düğmesine dokunun', style: TextStyle( fontSize: 13, color: AppColors.textSecondary), ), ], ], ), ); } return ListView.builder( padding: const EdgeInsets.fromLTRB(16, 12, 16, 24), itemCount: patients.length, itemBuilder: (context, index) { final patient = patients[index]; return Padding( padding: const EdgeInsets.only(bottom: 10), child: _PatientCard( patient: patient, onTap: () => context .push('/clinic/patients/${patient.id}'), ), ); }, ); }, ), ), ), ], ), ); } } class _PatientCard extends StatelessWidget { const _PatientCard({required this.patient, required this.onTap}); final Patient patient; final VoidCallback onTap; String get _initials { final name = patient.displayName; if (name.isEmpty) return '?'; final parts = name.trim().split(' '); if (parts.length >= 2) { return '${parts.first[0]}${parts.last[0]}'.toUpperCase(); } return name[0].toUpperCase(); } @override Widget build(BuildContext context) { return Material( color: AppColors.surface, borderRadius: BorderRadius.circular(14), child: InkWell( onTap: onTap, borderRadius: BorderRadius.circular(14), child: Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( borderRadius: BorderRadius.circular(14), border: Border.all(color: AppColors.border), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.04), blurRadius: 8, offset: const Offset(0, 2)) ]), child: Row( children: [ Container( width: 46, height: 46, decoration: BoxDecoration( gradient: const LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Color(0xFF1E3A5F), Color(0xFF0369A1)], ), borderRadius: BorderRadius.circular(13), ), child: Center( child: Text( _initials, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w700, color: Colors.white, ), ), ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( patient.displayName.isNotEmpty ? patient.displayName : patient.patientCode, style: const TextStyle( fontSize: 15, fontWeight: FontWeight.w600, color: AppColors.textPrimary), ), const SizedBox(height: 2), Row( children: [ Container( padding: const EdgeInsets.symmetric( horizontal: 6, vertical: 2), decoration: BoxDecoration( color: AppColors.surfaceVariant, borderRadius: BorderRadius.circular(6), ), child: Text( patient.patientCode, style: const TextStyle( fontSize: 11, color: AppColors.textSecondary, fontWeight: FontWeight.w500, ), ), ), if (patient.phone != null && patient.phone!.isNotEmpty) ...[ const SizedBox(width: 8), const Icon(Icons.phone_outlined, size: 11, color: AppColors.textMuted), const SizedBox(width: 3), Text( patient.phone!, style: const TextStyle( fontSize: 11, color: AppColors.textMuted), ), ], ], ), ], ), ), const Icon(Icons.chevron_right, color: AppColors.textMuted, size: 20), ], ), ), ), ); } } // ── New Patient Sheet ───────────────────────────────────────────────────────── class _NewPatientSheet extends ConsumerStatefulWidget { const _NewPatientSheet({required this.onCreated}); final VoidCallback onCreated; @override ConsumerState<_NewPatientSheet> createState() => _NewPatientSheetState(); } class _NewPatientSheetState extends ConsumerState<_NewPatientSheet> { final _formKey = GlobalKey(); final _patientCodeController = TextEditingController(); final _firstNameController = TextEditingController(); final _lastNameController = TextEditingController(); final _phoneController = TextEditingController(); final _notesController = TextEditingController(); String? _birthDate; bool _isSubmitting = false; @override void dispose() { _patientCodeController.dispose(); _firstNameController.dispose(); _lastNameController.dispose(); _phoneController.dispose(); _notesController.dispose(); super.dispose(); } Future _pickBirthDate() async { final picked = await showDatePicker( context: context, initialDate: DateTime(1990), firstDate: DateTime(1900), lastDate: DateTime.now(), ); if (picked != null) { setState(() { _birthDate = picked.toIso8601String().split('T').first; }); } } Future _submit() async { if (!_formKey.currentState!.validate()) return; setState(() => _isSubmitting = true); try { final tenantId = ref.read(authProvider).activeTenant!.tenant.id; await ClinicPatientsRepository.instance.createPatient( tenantId: tenantId, patientCode: _patientCodeController.text.trim(), firstName: _firstNameController.text.trim().isNotEmpty ? _firstNameController.text.trim() : null, lastName: _lastNameController.text.trim().isNotEmpty ? _lastNameController.text.trim() : null, phone: _phoneController.text.trim().isNotEmpty ? _phoneController.text.trim() : null, birthDate: _birthDate, notes: _notesController.text.trim().isNotEmpty ? _notesController.text.trim() : null, ); widget.onCreated(); } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Hata: $e')), ); } } finally { if (mounted) setState(() => _isSubmitting = false); } } @override Widget build(BuildContext context) { final isDesktop = MediaQuery.sizeOf(context).width > AppLayout.sidebarBreakpoint; return Container( decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.vertical( top: isDesktop ? Radius.zero : const Radius.circular(20), ), ), padding: EdgeInsets.only( bottom: isDesktop ? 0 : MediaQuery.of(context).viewInsets.bottom, ), child: SingleChildScrollView( padding: const EdgeInsets.all(20), child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Row( children: [ Expanded( child: Text( 'Yeni Hasta', style: Theme.of(context) .textTheme .titleLarge ?.copyWith(fontWeight: FontWeight.bold), ), ), IconButton( icon: const Icon(Icons.close), onPressed: () => Navigator.of(context).pop(), ), ], ), const SizedBox(height: 16), TextFormField( controller: _patientCodeController, decoration: const InputDecoration( labelText: 'Hasta Kodu *', hintText: 'Ör: P-001', ), validator: (val) => (val == null || val.trim().isEmpty) ? 'Hasta kodu zorunludur' : null, ), const SizedBox(height: 12), Row( children: [ Expanded( child: TextFormField( controller: _firstNameController, decoration: const InputDecoration(labelText: 'Ad'), ), ), const SizedBox(width: 12), Expanded( child: TextFormField( controller: _lastNameController, decoration: const InputDecoration(labelText: 'Soyad'), ), ), ], ), const SizedBox(height: 12), TextFormField( controller: _phoneController, decoration: const InputDecoration(labelText: 'Telefon'), keyboardType: TextInputType.phone, ), const SizedBox(height: 12), InkWell( onTap: _pickBirthDate, child: InputDecorator( decoration: const InputDecoration( labelText: 'Doğum Tarihi', suffixIcon: Icon(Icons.calendar_today), ), child: Text( _birthDate ?? 'Tarih seçin', style: _birthDate != null ? null : const TextStyle(color: AppColors.textMuted), ), ), ), const SizedBox(height: 12), TextFormField( controller: _notesController, decoration: const InputDecoration(labelText: 'Notlar'), minLines: 2, maxLines: 3, ), const SizedBox(height: 20), if (_isSubmitting) const Center( child: CircularProgressIndicator(color: AppColors.accent)) else FilledButton( onPressed: _submit, style: FilledButton.styleFrom( minimumSize: const Size(double.infinity, 48), ), child: const Text('Hasta Oluştur'), ), const SizedBox(height: 8), ], ), ), ), ); } }