import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../core/api/pocketbase_client.dart'; import '../../../core/providers/auth_provider.dart'; import '../../../core/theme/app_theme.dart'; import '../../../core/widgets/gradient_app_bar.dart'; import '../../../models/clinic_discount.dart'; import 'discount_repository.dart'; // Simple local record for clinic picker class _ClinicOption { const _ClinicOption({required this.id, required this.name}); final String id; final String name; } class DiscountsScreen extends ConsumerStatefulWidget { const DiscountsScreen({super.key}); @override ConsumerState createState() => _DiscountsScreenState(); } class _DiscountsScreenState extends ConsumerState { late Future> _future; String _searchQuery = ''; final _searchController = TextEditingController(); @override void initState() { super.initState(); _load(); } @override void dispose() { _searchController.dispose(); super.dispose(); } void _load() { final tenantId = ref.read(authProvider).activeTenant!.tenant.id; setState(() { _future = DiscountRepository.instance.listDiscounts(tenantId); }); } Future _showSheet({ClinicDiscount? existing}) async { final tenantId = ref.read(authProvider).activeTenant!.tenant.id; final result = await showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (_) => _DiscountSheet( labTenantId: tenantId, existing: existing, ), ); if (result == true) _load(); } Future _delete(ClinicDiscount discount) async { final confirmed = await showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('İndirimi Sil'), content: Text( '${discount.clinicName ?? 'Tüm Klinikler'} — ${discount.displayValue} indirimi silinsin mi?'), actions: [ TextButton( onPressed: () => Navigator.pop(ctx, false), child: const Text('İptal')), FilledButton( style: FilledButton.styleFrom(backgroundColor: AppColors.cancelled), onPressed: () => Navigator.pop(ctx, true), child: const Text('Sil'), ), ], ), ); if (confirmed != true) return; try { await DiscountRepository.instance.deleteDiscount(discount.id); _load(); } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Hata: $e'), backgroundColor: AppColors.cancelled), ); } } } Future _toggleActive(ClinicDiscount discount) async { try { await DiscountRepository.instance .updateDiscount(discount.id, isActive: !discount.isActive); _load(); } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Hata: $e'), backgroundColor: AppColors.cancelled), ); } } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppColors.background, appBar: GradientAppBar( title: 'İndirimler', category: 'LABORATUVAR', searchController: _searchController, onSearchChanged: (v) => setState(() => _searchQuery = v), searchHint: 'Klinik veya ürün tipi ara...', ), floatingActionButton: FloatingActionButton.extended( onPressed: () => _showSheet(), backgroundColor: AppColors.accent, foregroundColor: Colors.white, icon: const Icon(Icons.add), label: const Text('Yeni İndirim'), ), body: 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: [ const Icon(Icons.wifi_off_rounded, color: AppColors.cancelled, size: 40), const SizedBox(height: 12), Text('Hata: ${snap.error}', style: const TextStyle(color: AppColors.textSecondary)), const SizedBox(height: 12), FilledButton.icon( onPressed: _load, icon: const Icon(Icons.refresh_rounded, size: 16), label: const Text('Tekrar Dene')), ], ), ); } final allDiscounts = snap.data!; final q = _searchQuery.toLowerCase().trim(); final discounts = q.isEmpty ? allDiscounts : allDiscounts .where((d) => (d.clinicName ?? 'tüm klinikler') .toLowerCase() .contains(q) || d.prostheticLabel.toLowerCase().contains(q)) .toList(); if (allDiscounts.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.discount_outlined, size: 32, color: AppColors.inProgress), ), const SizedBox(height: 16), const Text('Henüz indirim tanımlanmadı', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.textPrimary)), const SizedBox(height: 8), const Text( 'Klinik ve ürün bazlı özel indirimler ekleyin.', style: TextStyle( color: AppColors.textSecondary, fontSize: 13), textAlign: TextAlign.center), const SizedBox(height: 20), FilledButton.icon( onPressed: () => _showSheet(), icon: const Icon(Icons.add), label: const Text('İlk İndirimi Ekle'), ), ], ), ); } if (discounts.isEmpty) { return const Center( child: Text('Sonuç bulunamadı', style: TextStyle(color: AppColors.textSecondary)), ); } final active = discounts.where((d) => d.isActive).toList(); final inactive = discounts.where((d) => !d.isActive).toList(); return RefreshIndicator( color: AppColors.accent, onRefresh: () async => _load(), child: ListView( padding: const EdgeInsets.fromLTRB(16, 12, 16, 100), children: [ if (active.isNotEmpty) ...[ _GroupHeader('Aktif (${active.length})'), for (final d in active) _DiscountCard( discount: d, onEdit: () => _showSheet(existing: d), onDelete: () => _delete(d), onToggle: () => _toggleActive(d), ), ], if (inactive.isNotEmpty) ...[ const SizedBox(height: 8), _GroupHeader('Pasif (${inactive.length})'), for (final d in inactive) _DiscountCard( discount: d, onEdit: () => _showSheet(existing: d), onDelete: () => _delete(d), onToggle: () => _toggleActive(d), ), ], ], ), ); }, ), ); } } // ── Group header ────────────────────────────────────────────────────────────── class _GroupHeader extends StatelessWidget { const _GroupHeader(this.text); final String text; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(bottom: 8, top: 4), child: Text(text, style: const TextStyle( fontSize: 12, fontWeight: FontWeight.w700, color: AppColors.textMuted, letterSpacing: 0.5)), ); } } // ── Discount card ───────────────────────────────────────────────────────────── class _DiscountCard extends StatelessWidget { const _DiscountCard({ required this.discount, required this.onEdit, required this.onDelete, required this.onToggle, }); final ClinicDiscount discount; final VoidCallback onEdit; final VoidCallback onDelete; final VoidCallback onToggle; @override Widget build(BuildContext context) { final d = discount; final isActive = d.isActive; return Padding( padding: const EdgeInsets.only(bottom: 10), child: Material( color: isActive ? AppColors.surface : AppColors.surfaceVariant, borderRadius: BorderRadius.circular(14), child: InkWell( onTap: onEdit, borderRadius: BorderRadius.circular(14), child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(14), border: Border.all( color: isActive ? AppColors.border : AppColors.muted), boxShadow: isActive ? [ BoxShadow( color: Colors.black.withValues(alpha: 0.03), blurRadius: 6, offset: const Offset(0, 2)) ] : [], ), child: IntrinsicHeight( child: Row( children: [ Container( width: 4, decoration: BoxDecoration( color: isActive ? AppColors.success : AppColors.border, borderRadius: const BorderRadius.only( topLeft: Radius.circular(14), bottomLeft: Radius.circular(14), ), ), ), Expanded( child: Padding( padding: const EdgeInsets.fromLTRB(12, 12, 4, 12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 4), decoration: BoxDecoration( color: isActive ? AppColors.successBg : AppColors.background, borderRadius: BorderRadius.circular(8), ), child: Text( '${d.displayValue} İndirim', style: TextStyle( fontSize: 15, fontWeight: FontWeight.w800, color: isActive ? AppColors.success : AppColors.textMuted, ), ), ), if (d.minQuantity > 0) ...[ const SizedBox(width: 8), Container( padding: const EdgeInsets.symmetric( horizontal: 7, vertical: 4), decoration: BoxDecoration( color: AppColors.pendingBg, borderRadius: BorderRadius.circular(6), ), child: Text( '≥${d.minQuantity} adet', style: const TextStyle( fontSize: 11, fontWeight: FontWeight.w600, color: AppColors.pending), ), ), ], const Spacer(), Transform.scale( scale: 0.8, child: Switch( value: isActive, onChanged: (_) => onToggle(), activeColor: AppColors.success, ), ), ], ), const SizedBox(height: 8), Row( children: [ _Tag( icon: Icons.local_hospital_outlined, label: d.appliesToAll ? 'Tüm Klinikler' : (d.clinicName ?? 'Klinik'), color: AppColors.inProgress, ), const SizedBox(width: 6), _Tag( icon: Icons.science_outlined, label: d.prostheticLabel, color: AppColors.accent, ), ], ), if (d.notes != null && d.notes!.isNotEmpty) ...[ const SizedBox(height: 6), Text(d.notes!, style: const TextStyle( fontSize: 12, color: AppColors.textMuted), maxLines: 1, overflow: TextOverflow.ellipsis), ], ], ), ), ), IconButton( onPressed: onDelete, icon: const Icon(Icons.delete_outline_rounded, size: 18, color: AppColors.cancelled), tooltip: 'Sil', ), ], ), ), ), ), ), ); } } class _Tag extends StatelessWidget { const _Tag( {required this.icon, required this.label, required this.color}); final IconData icon; final String label; final Color color; @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 3), decoration: BoxDecoration( color: color.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(6), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(icon, size: 11, color: color), const SizedBox(width: 4), Text(label, style: TextStyle( fontSize: 11, fontWeight: FontWeight.w600, color: color)), ], ), ); } } // ── Discount sheet ──────────────────────────────────────────────────────────── class _DiscountSheet extends StatefulWidget { const _DiscountSheet({required this.labTenantId, this.existing}); final String labTenantId; final ClinicDiscount? existing; @override State<_DiscountSheet> createState() => _DiscountSheetState(); } class _DiscountSheetState extends State<_DiscountSheet> { final _valueCtrl = TextEditingController(); final _minQtyCtrl = TextEditingController(); final _notesCtrl = TextEditingController(); DiscountType _discountType = DiscountType.percentage; String? _selectedClinicId; String? _selectedType; bool _isActive = true; bool _saving = false; List<_ClinicOption>? _clinics; @override void initState() { super.initState(); final e = widget.existing; if (e != null) { _valueCtrl.text = e.discountValue.toStringAsFixed( e.discountValue % 1 == 0 ? 0 : 2); _minQtyCtrl.text = e.minQuantity > 0 ? e.minQuantity.toString() : ''; _notesCtrl.text = e.notes ?? ''; _discountType = e.discountType; _selectedClinicId = e.clinicTenantId; _selectedType = e.prostheticType; _isActive = e.isActive; } _loadClinics(); } Future _loadClinics() async { try { final pb = PocketBaseClient.instance.pb; final result = await pb.collection('tenant_connections').getList( filter: 'lab_tenant_id = "${widget.labTenantId}" && status = "approved"', expand: 'clinic_tenant_id', perPage: 100, ); final clinics = result.items.map((r) { final j = r.toJson(); final expand = j['expand'] as Map?; final clinic = expand?['clinic_tenant_id'] as Map?; return _ClinicOption( id: j['clinic_tenant_id'] as String? ?? '', name: clinic?['company_name'] as String? ?? 'Klinik', ); }).where((c) => c.id.isNotEmpty).toList(); if (mounted) setState(() => _clinics = clinics); } catch (_) { if (mounted) setState(() => _clinics = []); } } @override void dispose() { _valueCtrl.dispose(); _minQtyCtrl.dispose(); _notesCtrl.dispose(); super.dispose(); } Future _save() async { final valueStr = _valueCtrl.text.trim().replaceAll(',', '.'); final value = double.tryParse(valueStr); if (value == null || value <= 0) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Geçerli bir indirim değeri girin.'), backgroundColor: AppColors.cancelled), ); return; } if (_discountType == DiscountType.percentage && value > 100) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text("Yüzde indirim 100'ü geçemez."), backgroundColor: AppColors.cancelled), ); return; } final minQty = int.tryParse(_minQtyCtrl.text.trim()) ?? 0; setState(() => _saving = true); final navigator = Navigator.of(context); final messenger = ScaffoldMessenger.of(context); try { if (widget.existing != null) { await DiscountRepository.instance.updateDiscount( widget.existing!.id, clinicTenantId: _selectedClinicId ?? '', prostheticType: _selectedType ?? '', discountType: _discountType, discountValue: value, minQuantity: minQty, isActive: _isActive, notes: _notesCtrl.text.trim(), ); } else { await DiscountRepository.instance.createDiscount( labTenantId: widget.labTenantId, clinicTenantId: _selectedClinicId, prostheticType: _selectedType, discountType: _discountType, discountValue: value, minQuantity: minQty, isActive: _isActive, notes: _notesCtrl.text.trim(), ); } navigator.pop(true); } catch (e) { if (mounted) setState(() => _saving = false); messenger.showSnackBar( SnackBar( content: Text('Hata: $e'), backgroundColor: AppColors.cancelled), ); } } static const _prostheticTypes = [ ('', 'Tüm Türler'), ('metal_porselen', 'Metal Porselen'), ('zirkonyum', 'Zirkonyum'), ('implant_ustu_zirkonyum', 'İmplant Üstü Zirkonyum'), ('gecici', 'Geçici'), ('e_max', 'E-Max'), ('tam_protez', 'Tam Protez'), ('parsiyel', 'Parsiyel Protez'), ('diger', 'Diğer'), ]; @override Widget build(BuildContext context) { final bottom = MediaQuery.paddingOf(context).bottom; return Container( decoration: const BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), padding: EdgeInsets.only(bottom: bottom), child: SingleChildScrollView( padding: EdgeInsets.only( left: 20, right: 20, top: 20, bottom: MediaQuery.viewInsetsOf(context).bottom + 20, ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Center( child: Container( width: 40, height: 4, decoration: BoxDecoration( color: AppColors.border, borderRadius: BorderRadius.circular(2)), ), ), const SizedBox(height: 16), Text( widget.existing != null ? 'İndirimi Düzenle' : 'Yeni İndirim', style: const TextStyle( fontSize: 18, fontWeight: FontWeight.w700, color: AppColors.textPrimary), ), const SizedBox(height: 20), const Text('İndirim Türü', style: TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: AppColors.textSecondary)), const SizedBox(height: 8), Row( children: [ Expanded( child: _TypeButton( label: 'Yüzde (%)', icon: Icons.percent_rounded, selected: _discountType == DiscountType.percentage, onTap: () => setState( () => _discountType = DiscountType.percentage), ), ), const SizedBox(width: 10), Expanded( child: _TypeButton( label: 'Sabit Tutar', icon: Icons.currency_lira_rounded, selected: _discountType == DiscountType.fixed, onTap: () => setState(() => _discountType = DiscountType.fixed), ), ), ], ), const SizedBox(height: 16), const Text('İndirim Değeri', style: TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: AppColors.textSecondary)), const SizedBox(height: 8), TextField( controller: _valueCtrl, keyboardType: const TextInputType.numberWithOptions(decimal: true), decoration: InputDecoration( hintText: _discountType == DiscountType.percentage ? 'Örn: 10' : 'Örn: 150', suffixText: _discountType == DiscountType.percentage ? '%' : 'TL', border: OutlineInputBorder( borderRadius: BorderRadius.circular(12)), ), ), const SizedBox(height: 16), const Text('Klinik', style: TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: AppColors.textSecondary)), const SizedBox(height: 8), _ClinicDropdown( selectedId: _selectedClinicId, clinics: _clinics, onChanged: (id, _) => setState(() { _selectedClinicId = id; }), ), const SizedBox(height: 16), const Text('Ürün Tipi', style: TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: AppColors.textSecondary)), const SizedBox(height: 8), DropdownButtonFormField( value: _selectedType ?? '', decoration: InputDecoration( border: OutlineInputBorder( borderRadius: BorderRadius.circular(12)), contentPadding: const EdgeInsets.symmetric( horizontal: 14, vertical: 12), ), items: _prostheticTypes .map((t) => DropdownMenuItem(value: t.$1, child: Text(t.$2))) .toList(), onChanged: (v) => setState(() => _selectedType = v == '' ? null : v), ), const SizedBox(height: 16), const Text('Minimum Sipariş Adedi (İsteğe Bağlı)', style: TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: AppColors.textSecondary)), const SizedBox(height: 4), const Text( 'Aylık bu adede ulaşılınca indirim devreye girer. 0 = koşulsuz.', style: TextStyle(fontSize: 11, color: AppColors.textMuted)), const SizedBox(height: 8), TextField( controller: _minQtyCtrl, keyboardType: TextInputType.number, decoration: InputDecoration( hintText: '0', suffixText: 'adet', border: OutlineInputBorder( borderRadius: BorderRadius.circular(12)), ), ), const SizedBox(height: 16), const Text('Not (İsteğe Bağlı)', style: TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: AppColors.textSecondary)), const SizedBox(height: 8), TextField( controller: _notesCtrl, maxLines: 2, decoration: InputDecoration( hintText: 'Açıklama...', border: OutlineInputBorder( borderRadius: BorderRadius.circular(12)), ), ), const SizedBox(height: 16), Row( children: [ const Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Aktif', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: AppColors.textPrimary)), Text('Pasif indirimler uygulanmaz.', style: TextStyle( fontSize: 12, color: AppColors.textMuted)), ], ), ), Switch( value: _isActive, onChanged: (v) => setState(() => _isActive = v), activeColor: AppColors.success, ), ], ), const SizedBox(height: 24), SizedBox( width: double.infinity, height: 50, child: FilledButton( onPressed: _saving ? null : _save, style: FilledButton.styleFrom( backgroundColor: AppColors.accent, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14)), ), child: _saving ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white)) : Text( widget.existing != null ? 'Güncelle' : 'Kaydet', style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w600)), ), ), ], ), ), ); } } class _TypeButton extends StatelessWidget { const _TypeButton({ required this.label, required this.icon, required this.selected, required this.onTap, }); final String label; final IconData icon; final bool selected; final VoidCallback onTap; @override Widget build(BuildContext context) { return GestureDetector( onTap: onTap, child: AnimatedContainer( duration: const Duration(milliseconds: 150), padding: const EdgeInsets.symmetric(vertical: 12), decoration: BoxDecoration( color: selected ? AppColors.accent : AppColors.background, borderRadius: BorderRadius.circular(12), border: Border.all( color: selected ? AppColors.accent : AppColors.border), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(icon, size: 16, color: selected ? Colors.white : AppColors.textSecondary), const SizedBox(width: 6), Text(label, style: TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: selected ? Colors.white : AppColors.textSecondary)), ], ), ), ); } } class _ClinicDropdown extends StatelessWidget { const _ClinicDropdown({ required this.selectedId, required this.clinics, required this.onChanged, }); final String? selectedId; final List<_ClinicOption>? clinics; final void Function(String? id, String? name) onChanged; @override Widget build(BuildContext context) { if (clinics == null) { return Container( height: 48, decoration: BoxDecoration( border: Border.all(color: AppColors.border), borderRadius: BorderRadius.circular(12), ), child: const Center( child: SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2))), ); } final items = >[ const DropdownMenuItem(value: '', child: Text('Tüm Klinikler')), for (final c in clinics!) DropdownMenuItem(value: c.id, child: Text(c.name)), ]; return DropdownButtonFormField( value: selectedId ?? '', decoration: InputDecoration( border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), ), items: items, onChanged: (v) { if (v == null || v.isEmpty) { onChanged(null, null); } else { final clinic = clinics!.firstWhere((c) => c.id == v); onChanged(v, clinic.name); } }, ); } }