Files
Finlytic/FinlyticApp/lib/features/admin/widgets/pipeline_settings_widget.dart
T

303 lines
11 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../core/network/api_client.dart';
import '../../../core/theme/app_theme.dart';
import '../repositories/admin_repository.dart';
import 'service_settings_form.dart';
class ServiceConfigMeta {
final String key;
final String displayName;
final String description;
final IconData icon;
final Color accentColor;
const ServiceConfigMeta({
required this.key,
required this.displayName,
required this.description,
required this.icon,
required this.accentColor,
});
}
class PipelineSettingsWidget extends StatefulWidget {
final ApiClient? apiClient;
final AdminRepository? repository;
const PipelineSettingsWidget({super.key, this.apiClient, this.repository});
@override
State<PipelineSettingsWidget> createState() => _PipelineSettingsWidgetState();
}
class _PipelineSettingsWidgetState extends State<PipelineSettingsWidget> {
late final AdminRepository _repository;
String _selectedServiceKey = 'FinlyticAssets';
bool _isLoading = false;
bool _isSaving = false;
static const List<ServiceConfigMeta> _services = [
ServiceConfigMeta(
key: 'FinlyticAssets',
displayName: 'Asset Katalog & Logos',
description: 'Verwaltet ISIN Asset-Stammdaten & Trade Republic Logo Fetcher',
icon: Icons.inventory_2_outlined,
accentColor: Color(0xFF00E5FF),
),
ServiceConfigMeta(
key: 'FinlyticNews',
displayName: 'News Scraper & AI',
description: 'RSS Web Scraper Intervall & Entwurf-Retention',
icon: Icons.newspaper_outlined,
accentColor: Color(0xFF3B82F6),
),
ServiceConfigMeta(
key: 'FinlyticTechnicalAnalysis',
displayName: 'Technische Analyse',
description: 'EMA/SMA Perioden, RSI Grenzwerte & Supertrend Multiplikator',
icon: Icons.show_chart_outlined,
accentColor: Color(0xFF8B5CF6),
),
ServiceConfigMeta(
key: 'FinlyticSentiment',
displayName: 'Sentiment NLP',
description: 'NLP Vertrauens-Schwellenwerte & Text-Batching',
icon: Icons.psychology_outlined,
accentColor: Color(0xFFEC4899),
),
ServiceConfigMeta(
key: 'FinlyticAnalyzer',
displayName: 'Analyzer Signal Engine',
description: 'Scraper Cron-Schedule & Minimaler Signal-Score',
icon: Icons.analytics_outlined,
accentColor: Color(0xFFF59E0B),
),
ServiceConfigMeta(
key: 'FinlyticTrades',
displayName: 'Trade Manager',
description: 'ATR Stop-Loss Multiplikator, Risiko-Prozente & Positionen',
icon: Icons.candlestick_chart_outlined,
accentColor: Color(0xFF10B981),
),
ServiceConfigMeta(
key: 'FinlyticFundamentals',
displayName: 'Fundamentaldaten',
description: 'Cache TTL Dauer & Yahoo Finance Fallback',
icon: Icons.corporate_fare_outlined,
accentColor: Color(0xFF06B6D4),
),
];
final Map<String, Map<String, TextEditingController>> _controllers = {
'FinlyticAssets': {
'TradeRepublicMaxRequestPageSize': TextEditingController(text: '100'),
'AssetUpdateTypeDelay': TextEditingController(text: '0'),
'BatchAssetUpdateDelay': TextEditingController(text: '5'),
},
'FinlyticNews': {
'ScrapingIntervalMinutes': TextEditingController(text: '15'),
'PollingFrequencyMinutes': TextEditingController(text: '15'),
'ArticleRetentionDays': TextEditingController(text: '90'),
'DefaultPageSize': TextEditingController(text: '20'),
},
'FinlyticTechnicalAnalysis': {
'EmaShortPeriod': TextEditingController(text: '20'),
'SmaMediumPeriod': TextEditingController(text: '50'),
'SmaLongPeriod': TextEditingController(text: '200'),
'RsiOverboughtLimit': TextEditingController(text: '70'),
'RsiOversoldLimit': TextEditingController(text: '30'),
'SupertrendMultiplier': TextEditingController(text: '3.0'),
},
'FinlyticSentiment': {
'MinConfidenceScore': TextEditingController(text: '0.70'),
'MaxBatchSize': TextEditingController(text: '50'),
},
'FinlyticAnalyzer': {
'ScanCronSchedule': TextEditingController(text: '0 */1 * * *'),
'MinSignalScore': TextEditingController(text: '75'),
'EnableLog_MqttHealthPing': TextEditingController(text: 'false'),
'EnableLog_MqttGeneral': TextEditingController(text: 'true'),
'EnableLog_AnalyzerAuto': TextEditingController(text: 'true'),
'EnableLog_AnalyzerManual': TextEditingController(text: 'true'),
'EnableLog_DatabaseOps': TextEditingController(text: 'true'),
},
'FinlyticTrades': {
'AtrStopLossMultiplier': TextEditingController(text: '1.5'),
'RiskPerTradePercentage': TextEditingController(text: '1.0'),
'MaxOpenPositions': TextEditingController(text: '5'),
},
'FinlyticFundamentals': {
'CacheTtlHours': TextEditingController(text: '24'),
'EnableYahooFallback': TextEditingController(text: 'true'),
},
};
bool _initialized = false;
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (!_initialized) {
_initialized = true;
final client = widget.apiClient ?? context.read<ApiClient>();
_repository = widget.repository ?? AdminRepository(apiClient: client);
_fetchSettings();
}
}
Future<void> _fetchSettings() async {
setState(() => _isLoading = true);
try {
final settings = await _repository.fetchSettings();
settings.forEach((svc, items) {
_controllers.putIfAbsent(svc, () => {});
for (final s in items) {
if (_controllers[svc]!.containsKey(s.key)) {
_controllers[svc]![s.key]!.text = s.value;
} else {
_controllers[svc]![s.key] = TextEditingController(text: s.value);
}
}
});
} catch (_) {
} finally {
if (mounted) setState(() => _isLoading = false);
}
}
Future<void> _saveSettings() async {
setState(() => _isSaving = true);
try {
final currentSvcControllers = _controllers[_selectedServiceKey] ?? {};
final payload = <String, String>{};
currentSvcControllers.forEach((k, v) {
payload[k] = v.text;
});
await _repository.updateServiceSettings(_selectedServiceKey, payload);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Row(
children: [
const Icon(Icons.check_circle_outline, color: Colors.black),
const SizedBox(width: 8),
Expanded(
child: Text(
'Einstellungen für $_selectedServiceKey gespeichert & synchronisiert.',
style: const TextStyle(color: Colors.black, fontWeight: FontWeight.w600),
),
),
],
),
backgroundColor: AppTheme.primaryEmerald,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
),
);
}
} catch (ex) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Fehler beim Speichern: $ex'),
backgroundColor: AppTheme.accentRed,
behavior: SnackBarBehavior.floating,
),
);
}
} finally {
if (mounted) setState(() => _isSaving = false);
}
}
@override
Widget build(BuildContext context) {
final activeService = _services.firstWhere((s) => s.key == _selectedServiceKey, orElse: () => _services.first);
final activeControllers = _controllers[_selectedServiceKey] ?? {};
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: _services.map((svc) {
final isSelected = svc.key == _selectedServiceKey;
return Padding(
padding: const EdgeInsets.only(right: 8, bottom: 12),
child: InkWell(
onTap: () => setState(() => _selectedServiceKey = svc.key),
borderRadius: BorderRadius.circular(12),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
decoration: BoxDecoration(
color: isSelected ? svc.accentColor.withValues(alpha: 0.15) : AppTheme.glassSurface,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: isSelected ? svc.accentColor : AppTheme.glassBorder,
width: isSelected ? 1.5 : 1,
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(svc.icon, size: 16, color: isSelected ? svc.accentColor : AppTheme.textMuted),
const SizedBox(width: 8),
Text(
svc.displayName,
style: TextStyle(
color: isSelected ? Colors.white : AppTheme.textMuted,
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
fontSize: 12,
),
),
],
),
),
),
);
}).toList(),
),
),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(activeService.displayName, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white)),
const SizedBox(height: 2),
Text(activeService.description, style: TextStyle(fontSize: 12, color: AppTheme.textMuted)),
],
),
ElevatedButton.icon(
onPressed: _isSaving ? null : _saveSettings,
icon: _isSaving
? const SizedBox(width: 14, height: 14, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.black))
: const Icon(Icons.save_outlined, size: 16),
label: Text(_isSaving ? 'Speichere...' : 'Speichern'),
style: ElevatedButton.styleFrom(
backgroundColor: AppTheme.primaryEmerald,
foregroundColor: Colors.black,
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
),
),
],
),
const SizedBox(height: 14),
if (_isLoading)
Center(child: CircularProgressIndicator(color: AppTheme.primaryEmerald))
else
ServiceSettingsForm(
controllers: activeControllers,
accentColor: activeService.accentColor,
),
],
);
}
}