import 'package:flutter/material.dart'; import '../../../../core/theme/app_theme.dart'; import '../models/trade_model.dart'; import '../models/close_trade_request_dto.dart'; class TradeClosingCockpit extends StatefulWidget { final TradeModel trade; final String defaultSymbol; final void Function(CloseTradeRequestDto) onClose; const TradeClosingCockpit({ super.key, required this.trade, required this.defaultSymbol, required this.onClose, }); static Future show( BuildContext context, { required TradeModel trade, required String defaultSymbol, required void Function(CloseTradeRequestDto) onClose, }) { return showDialog( context: context, barrierDismissible: true, builder: (dialogContext) => TradeClosingCockpit( trade: trade, defaultSymbol: defaultSymbol, onClose: onClose, ), ); } @override State createState() => _TradeClosingCockpitState(); } class _TradeClosingCockpitState extends State { late TextEditingController _exitPriceCtrl; late TextEditingController _exitFeeCtrl; late TextEditingController _notesCtrl; DateTime _exitTimestamp = DateTime.now(); String _selectedReasonTag = 'Manuell in TR verkauft'; @override void initState() { super.initState(); final defaultPrice = widget.trade.currentPrice > 0 ? widget.trade.currentPrice : (widget.trade.actualEntryPrice > 0 ? widget.trade.actualEntryPrice : widget.trade.entryPrice); _exitPriceCtrl = TextEditingController(text: defaultPrice.toStringAsFixed(2)); _exitFeeCtrl = TextEditingController(text: '1.00'); _notesCtrl = TextEditingController(); _exitPriceCtrl.addListener(() => setState(() {})); _exitFeeCtrl.addListener(() => setState(() {})); } @override void dispose() { _exitPriceCtrl.dispose(); _exitFeeCtrl.dispose(); _notesCtrl.dispose(); super.dispose(); } double _parse(TextEditingController ctrl, double fallback) { final clean = ctrl.text.replaceAll(',', '.').trim(); return double.tryParse(clean) ?? fallback; } double get _exitPrice => _parse(_exitPriceCtrl, widget.trade.entryPrice); double get _exitFee => _parse(_exitFeeCtrl, 1.0); double get _entryPrice => widget.trade.actualEntryPrice > 0 ? widget.trade.actualEntryPrice : (widget.trade.entryPrice > 0 ? widget.trade.entryPrice : 1.0); double get _posSize => widget.trade.positionSize > 0 ? widget.trade.positionSize : 1000.0; double get _quantity => widget.trade.quantity > 0 ? widget.trade.quantity : (_posSize / _entryPrice); double get _calculatedProceeds { if (_exitPrice <= 0 || _quantity <= 0) return 0.0; return _quantity * _exitPrice; } double get _calculatedPnlAbs { if (_entryPrice <= 0 || _exitPrice <= 0) return 0.0; final isShort = widget.trade.signalType.toUpperCase() == 'SELL' || widget.trade.signalType.toUpperCase() == 'SHORT'; final movePct = isShort ? ((_entryPrice - _exitPrice) / _entryPrice) : ((_exitPrice - _entryPrice) / _entryPrice); final lev = (widget.trade.instrumentType.toLowerCase().contains('knock') || widget.trade.instrumentType.toLowerCase().contains('option')) ? 1.0 : (widget.trade.leverageUsed > 0 ? widget.trade.leverageUsed : 1.0); final totalFees = (widget.trade.entryFee > 0 ? widget.trade.entryFee : 1.0) + _exitFee; return (movePct * _posSize * lev) - totalFees; } double get _calculatedPnlPct { if (_posSize <= 0) return 0.0; return (_calculatedPnlAbs / _posSize) * 100.0; } void _selectTimeOption(String option) { final now = DateTime.now(); setState(() { if (option == 'now') { _exitTimestamp = now; } else if (option == 'today_morning') { _exitTimestamp = DateTime(now.year, now.month, now.day, 9, 15); } else if (option == 'today_noon') { _exitTimestamp = DateTime(now.year, now.month, now.day, 13, 0); } else if (option == 'yesterday') { final yest = now.subtract(const Duration(days: 1)); _exitTimestamp = DateTime(yest.year, yest.month, yest.day, 17, 30); } }); } Future _pickCustomDateTime() async { final pickedDate = await showDatePicker( context: context, initialDate: _exitTimestamp, firstDate: DateTime.now().subtract(const Duration(days: 90)), lastDate: DateTime.now(), ); if (pickedDate != null && mounted) { final pickedTime = await showTimePicker( context: context, initialTime: TimeOfDay.fromDateTime(_exitTimestamp), ); if (pickedTime != null && mounted) { setState(() { _exitTimestamp = DateTime( pickedDate.year, pickedDate.month, pickedDate.day, pickedTime.hour, pickedTime.minute, ); }); } } } @override Widget build(BuildContext context) { final isWin = _calculatedPnlAbs >= 0; final pnlColor = isWin ? AppTheme.primaryEmerald : AppTheme.accentRed; return Dialog( backgroundColor: Colors.transparent, insetPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), child: Container( width: 580, constraints: const BoxConstraints(maxHeight: 780), decoration: BoxDecoration( color: AppTheme.cardSurface, borderRadius: BorderRadius.circular(24), border: Border.all(color: AppTheme.glassBorder), boxShadow: [ BoxShadow(color: Colors.black.withValues(alpha: 0.6), blurRadius: 30, offset: const Offset(0, 10)), ], ), child: Column( children: [ // Header Padding( padding: const EdgeInsets.fromLTRB(20, 18, 16, 14), child: Row( children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: AppTheme.accentRed.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(10), ), child: Icon(Icons.flag_outlined, color: AppTheme.accentRed, size: 22), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Position Schließen & Nacherfassen', style: TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold), ), Text( 'Trade #${widget.trade.id} • ${widget.trade.companyName.isNotEmpty ? widget.trade.companyName : widget.defaultSymbol}', style: TextStyle(color: AppTheme.textMuted, fontSize: 12), ), ], ), ), IconButton( onPressed: () => Navigator.of(context).pop(), icon: const Icon(Icons.close, color: Colors.white54), ), ], ), ), const Divider(color: Colors.white12, height: 1), // Body Expanded( child: SingleChildScrollView( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Entry Recap Box Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppTheme.glassSurface, borderRadius: BorderRadius.circular(12), border: Border.all(color: AppTheme.glassBorder), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ _summaryCol('Einstiegskurs', '€${_entryPrice.toStringAsFixed(2)}'), _summaryCol('Investition', '€${_posSize.toStringAsFixed(0)}'), _summaryCol('Stückzahl', '${_quantity.toStringAsFixed(2)} Stk.'), _summaryCol('Instrument', widget.trade.instrumentType), ], ), ), const SizedBox(height: 18), // SECTION 1: VERKAUFSKURS const Text('1. TATSÄCHLICHER VERKAUFSKURS (€)', style: TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.w900, letterSpacing: 0.5)), const SizedBox(height: 8), Row( children: [ Expanded( child: TextField( controller: _exitPriceCtrl, keyboardType: const TextInputType.numberWithOptions(decimal: true), style: const TextStyle(color: Colors.white, fontSize: 15, fontWeight: FontWeight.bold), decoration: InputDecoration( prefixIcon: const Icon(Icons.sell_outlined, size: 18, color: Colors.white54), labelText: 'Verkaufskurs in Trade Republic', filled: true, fillColor: AppTheme.glassSurface, contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), border: OutlineInputBorder(borderRadius: BorderRadius.circular(10), borderSide: BorderSide(color: AppTheme.glassBorder)), enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(10), borderSide: BorderSide(color: AppTheme.glassBorder)), ), ), ), const SizedBox(width: 12), Expanded( child: TextField( controller: _exitFeeCtrl, keyboardType: const TextInputType.numberWithOptions(decimal: true), style: const TextStyle(color: Colors.white, fontSize: 14), decoration: InputDecoration( prefixIcon: const Icon(Icons.receipt_long, size: 16, color: Colors.white54), labelText: 'Ausstiegsgebühr (€)', filled: true, fillColor: AppTheme.glassSurface, contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), border: OutlineInputBorder(borderRadius: BorderRadius.circular(10), borderSide: BorderSide(color: AppTheme.glassBorder)), enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(10), borderSide: BorderSide(color: AppTheme.glassBorder)), ), ), ), ], ), const SizedBox(height: 18), // SECTION 2: ZEITPUNKT const Text('2. WANN WURDE DER TRADE GESCHLOSSEN?', style: TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.w900, letterSpacing: 0.5)), const SizedBox(height: 8), SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: [ _timeChip('Jetzt', 'now'), _timeChip('Heute Morgen (09:15)', 'today_morning'), _timeChip('Heute Mittag (13:00)', 'today_noon'), _timeChip('Gestern (17:30)', 'yesterday'), ], ), ), const SizedBox(height: 8), // Custom Date/Time Picker Trigger GestureDetector( onTap: _pickCustomDateTime, child: Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), decoration: BoxDecoration( color: AppTheme.glassSurface, borderRadius: BorderRadius.circular(10), border: Border.all(color: AppTheme.glassBorder), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ Icon(Icons.calendar_today, size: 16, color: AppTheme.accentCyan), const SizedBox(width: 8), Text( 'Ausführungszeit: ${_formatDateTime(_exitTimestamp)}', style: const TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold), ), ], ), Text('Ändern', style: TextStyle(color: AppTheme.accentCyan, fontSize: 12, fontWeight: FontWeight.bold)), ], ), ), ), const SizedBox(height: 18), // SECTION 3: GRUND / NOTIZ const Text('3. AUSSTIEGSGRUND', style: TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.w900, letterSpacing: 0.5)), const SizedBox(height: 8), Wrap( spacing: 8, runSpacing: 8, children: [ _reasonChip('🎯 Take-Profit gegriffen'), _reasonChip('📱 Manuell in TR verkauft'), _reasonChip('🛑 Stop-Loss ausgelöst'), _reasonChip('🕒 Vor Wochenende / Time-Stop'), _reasonChip('⚠️ Risiko minimiert'), ], ), const SizedBox(height: 20), // LIVE REALISIERTER PNL VORSCHAU Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: pnlColor.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(16), border: Border.all(color: pnlColor.withValues(alpha: 0.4)), ), child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('Realisierter Gewinn / Verlust (PnL):', style: const TextStyle(color: Colors.white70, fontSize: 13, fontWeight: FontWeight.bold)), Text( '${(isWin ? "+€" : "-€")}${_calculatedPnlAbs.abs().toStringAsFixed(2)} (${isWin ? "+" : ""}${_calculatedPnlPct.toStringAsFixed(2)}%)', style: TextStyle(color: pnlColor, fontSize: 17, fontWeight: FontWeight.w900), ), ], ), const Divider(color: Colors.white12, height: 16), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('Verkaufserlös (Gesamt): €${_calculatedProceeds.toStringAsFixed(2)}', style: TextStyle(color: AppTheme.textMuted, fontSize: 11)), Text('Netto nach Gebühren: €${(_posSize + _calculatedPnlAbs).toStringAsFixed(2)}', style: TextStyle(color: AppTheme.textMuted, fontSize: 11)), ], ), ], ), ), ], ), ), ), // Actions Footer Padding( padding: const EdgeInsets.all(16), child: Row( children: [ TextButton( onPressed: () => Navigator.pop(context), child: Text('Abbrechen', style: TextStyle(color: AppTheme.textMuted)), ), const Spacer(), ElevatedButton.icon( onPressed: () { final req = CloseTradeRequestDto( userExitPrice: _exitPrice, userExitTimestamp: _exitTimestamp, exitFee: _exitFee, closeReason: _selectedReasonTag, ); Navigator.pop(context); widget.onClose(req); }, icon: const Icon(Icons.check_circle, size: 18), label: const Text('Position Exakt So Buchen', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), style: ElevatedButton.styleFrom( backgroundColor: pnlColor, foregroundColor: isWin ? Colors.black : Colors.white, padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), ), ], ), ), ], ), ), ); } Widget _summaryCol(String label, String val) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(label, style: TextStyle(color: AppTheme.textMuted, fontSize: 10)), const SizedBox(height: 2), Text(val, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 12)), ], ); } Widget _timeChip(String label, String option) { return GestureDetector( onTap: () => _selectTimeOption(option), child: Container( margin: const EdgeInsets.only(right: 8), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 7), decoration: BoxDecoration( color: AppTheme.glassSurface, borderRadius: BorderRadius.circular(8), border: Border.all(color: AppTheme.glassBorder), ), child: Text(label, style: const TextStyle(color: Colors.white70, fontSize: 11, fontWeight: FontWeight.bold)), ), ); } Widget _reasonChip(String label) { final isSelected = _selectedReasonTag == label; return GestureDetector( onTap: () => setState(() => _selectedReasonTag = label), child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: isSelected ? AppTheme.accentCyan.withValues(alpha: 0.2) : AppTheme.glassSurface, borderRadius: BorderRadius.circular(8), border: Border.all(color: isSelected ? AppTheme.accentCyan : AppTheme.glassBorder), ), child: Text( label, style: TextStyle( color: isSelected ? AppTheme.accentCyan : Colors.white70, fontSize: 11, fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, ), ), ), ); } String _formatDateTime(DateTime dt) { final d = dt.day.toString().padLeft(2, '0'); final m = dt.month.toString().padLeft(2, '0'); final y = dt.year; final h = dt.hour.toString().padLeft(2, '0'); final min = dt.minute.toString().padLeft(2, '0'); return '$d.$m.$y um $h:$min Uhr'; } }