@@ -8,7 +8,10 @@ import 'package:share_plus/share_plus.dart';
88import '../models/geofence_zone.dart' ;
99import '../providers.dart' ;
1010import '../services/geofence_event_queue.dart' ;
11+ import '../services/geofence_sync_service.dart' ;
12+ import '../models/work_entry.dart' ;
1113import '../theme/theme_colors.dart' ;
14+ import 'package:hive/hive.dart' ;
1215
1316class GeofenceDebugScreen extends ConsumerStatefulWidget {
1417 const GeofenceDebugScreen ({super .key});
@@ -42,6 +45,10 @@ class _GeofenceDebugScreenState extends ConsumerState<GeofenceDebugScreen> {
4245 // Logs
4346 final List <_LogEntry > _logs = [];
4447
48+ // Work Entry Status
49+ WorkEntry ? _runningEntry;
50+ int ? _lastSyncResult;
51+
4552 @override
4653 void initState () {
4754 super .initState ();
@@ -61,10 +68,45 @@ class _GeofenceDebugScreenState extends ConsumerState<GeofenceDebugScreen> {
6168 _loadPermissions (),
6269 _loadPosition (),
6370 _loadEventQueue (),
71+ _loadWorkEntryStatus (),
6472 ]);
6573 setState (() => _lastRefresh = DateTime .now ());
6674 }
6775
76+ Future <void > _loadWorkEntryStatus () async {
77+ try {
78+ final workBox = Hive .box <WorkEntry >('work' );
79+ final running = workBox.values.where ((e) => e.stop == null ).toList ();
80+ if (mounted) {
81+ setState (() {
82+ _runningEntry = running.isNotEmpty ? running.last : null ;
83+ });
84+ }
85+ } catch (e) {
86+ _addLog ('Fehler beim Laden des WorkEntry-Status: $e ' , isError: true );
87+ }
88+ }
89+
90+ Future <void > _forceSyncNow () async {
91+ _addLog ('Force Sync gestartet...' );
92+ try {
93+ final workBox = Hive .box <WorkEntry >('work' );
94+ final syncService = GeofenceSyncService (workBox);
95+ final processedCount = await syncService.syncPendingEvents ();
96+
97+ setState (() => _lastSyncResult = processedCount);
98+ _addLog ('Sync abgeschlossen: $processedCount Events verarbeitet' );
99+
100+ if (processedCount > 0 ) {
101+ ref.invalidate (workListProvider);
102+ }
103+
104+ await _loadAll ();
105+ } catch (e) {
106+ _addLog ('Sync Fehler: $e ' , isError: true );
107+ }
108+ }
109+
68110 Future <void > _loadPermissions () async {
69111 final results = await Future .wait ([
70112 Permission .location.status,
@@ -194,6 +236,10 @@ class _GeofenceDebugScreenState extends ConsumerState<GeofenceDebugScreen> {
194236 _buildZonesCard (zones),
195237 const SizedBox (height: 16 ),
196238
239+ // Work Entry Status Section
240+ _buildWorkEntryCard (),
241+ const SizedBox (height: 16 ),
242+
197243 // Event Queue Section
198244 _buildEventQueueCard (),
199245 const SizedBox (height: 16 ),
@@ -602,6 +648,88 @@ class _GeofenceDebugScreenState extends ConsumerState<GeofenceDebugScreen> {
602648 );
603649 }
604650
651+ Widget _buildWorkEntryCard () {
652+ return Card (
653+ color: _runningEntry != null ? context.successBackground : null ,
654+ child: Padding (
655+ padding: const EdgeInsets .all (16 ),
656+ child: Column (
657+ crossAxisAlignment: CrossAxisAlignment .start,
658+ children: [
659+ Row (
660+ children: [
661+ Icon (
662+ _runningEntry != null ? Icons .play_circle : Icons .stop_circle,
663+ color: _runningEntry != null ? context.successForeground : Colors .grey,
664+ ),
665+ const SizedBox (width: 8 ),
666+ const Text (
667+ 'Arbeitszeit-Status' ,
668+ style: TextStyle (fontSize: 16 , fontWeight: FontWeight .bold),
669+ ),
670+ const Spacer (),
671+ Container (
672+ padding: const EdgeInsets .symmetric (horizontal: 8 , vertical: 2 ),
673+ decoration: BoxDecoration (
674+ color: _runningEntry != null
675+ ? Colors .green.withAlpha (51 )
676+ : Colors .grey.withAlpha (51 ),
677+ borderRadius: BorderRadius .circular (8 ),
678+ ),
679+ child: Text (
680+ _runningEntry != null ? 'Läuft' : 'Gestoppt' ,
681+ style: TextStyle (
682+ fontSize: 12 ,
683+ color: _runningEntry != null ? Colors .green : Colors .grey,
684+ fontWeight: FontWeight .bold,
685+ ),
686+ ),
687+ ),
688+ ],
689+ ),
690+ const Divider (),
691+ if (_runningEntry != null ) ...[
692+ _buildInfoRow ('Gestartet' , _formatDateTime (_runningEntry! .start)),
693+ _buildInfoRow ('Laufzeit' , _formatDuration (DateTime .now ().difference (_runningEntry! .start))),
694+ if (_runningEntry! .pauses.isNotEmpty)
695+ _buildInfoRow ('Pausen' , '${_runningEntry !.pauses .length }' ),
696+ ] else ...[
697+ const Text (
698+ 'Keine laufende Arbeitszeit' ,
699+ style: TextStyle (color: Colors .grey),
700+ ),
701+ const SizedBox (height: 8 ),
702+ Container (
703+ padding: const EdgeInsets .all (8 ),
704+ decoration: BoxDecoration (
705+ color: context.warningBackground,
706+ borderRadius: BorderRadius .circular (8 ),
707+ ),
708+ child: Row (
709+ children: [
710+ Icon (Icons .info, size: 16 , color: context.warningForeground),
711+ const SizedBox (width: 8 ),
712+ Expanded (
713+ child: Text (
714+ 'Bei ENTER-Event wird neue Arbeitszeit gestartet, '
715+ 'aber nur wenn keine bereits läuft!' ,
716+ style: TextStyle (fontSize: 11 , color: context.warningForeground),
717+ ),
718+ ),
719+ ],
720+ ),
721+ ),
722+ ],
723+ if (_lastSyncResult != null ) ...[
724+ const Divider (),
725+ _buildInfoRow ('Letzter Sync' , '$_lastSyncResult Events verarbeitet' ),
726+ ],
727+ ],
728+ ),
729+ ),
730+ );
731+ }
732+
605733 Widget _buildEventQueueCard () {
606734 return Card (
607735 child: Padding (
@@ -850,6 +978,16 @@ class _GeofenceDebugScreenState extends ConsumerState<GeofenceDebugScreen> {
850978 ],
851979 ),
852980 const SizedBox (height: 12 ),
981+ // Primary Action: Force Sync
982+ FilledButton .icon (
983+ onPressed: _forceSyncNow,
984+ icon: const Icon (Icons .sync ),
985+ label: const Text ('Events jetzt verarbeiten' ),
986+ style: FilledButton .styleFrom (
987+ minimumSize: const Size (double .infinity, 44 ),
988+ ),
989+ ),
990+ const SizedBox (height: 12 ),
853991 Wrap (
854992 spacing: 8 ,
855993 runSpacing: 8 ,
@@ -866,12 +1004,14 @@ class _GeofenceDebugScreenState extends ConsumerState<GeofenceDebugScreen> {
8661004 OutlinedButton .icon (
8671005 onPressed: () async {
8681006 // Simulate ENTER event
1007+ final zones = ref.read (geofenceZonesProvider);
1008+ final zoneId = zones.isNotEmpty ? zones.first.id : 'debug_test' ;
8691009 await GeofenceEventQueue .enqueue (GeofenceEventData (
870- zoneId: 'debug_test' ,
1010+ zoneId: zoneId ,
8711011 event: GeofenceEvent .enter,
8721012 timestamp: DateTime .now (),
8731013 ));
874- _addLog ('Test ENTER Event erstellt' );
1014+ _addLog ('Test ENTER Event erstellt (Zone: $ zoneId ) ' );
8751015 await _loadEventQueue ();
8761016 },
8771017 icon: const Icon (Icons .login, size: 18 ),
@@ -880,12 +1020,14 @@ class _GeofenceDebugScreenState extends ConsumerState<GeofenceDebugScreen> {
8801020 OutlinedButton .icon (
8811021 onPressed: () async {
8821022 // Simulate EXIT event
1023+ final zones = ref.read (geofenceZonesProvider);
1024+ final zoneId = zones.isNotEmpty ? zones.first.id : 'debug_test' ;
8831025 await GeofenceEventQueue .enqueue (GeofenceEventData (
884- zoneId: 'debug_test' ,
1026+ zoneId: zoneId ,
8851027 event: GeofenceEvent .exit,
8861028 timestamp: DateTime .now (),
8871029 ));
888- _addLog ('Test EXIT Event erstellt' );
1030+ _addLog ('Test EXIT Event erstellt (Zone: $ zoneId ) ' );
8891031 await _loadEventQueue ();
8901032 },
8911033 icon: const Icon (Icons .logout, size: 18 ),
@@ -983,6 +1125,15 @@ class _GeofenceDebugScreenState extends ConsumerState<GeofenceDebugScreen> {
9831125 }
9841126 return '${(meters / 1000 ).toStringAsFixed (1 )}km' ;
9851127 }
1128+
1129+ String _formatDuration (Duration duration) {
1130+ final hours = duration.inHours;
1131+ final minutes = duration.inMinutes.remainder (60 );
1132+ if (hours > 0 ) {
1133+ return '${hours }h ${minutes }m' ;
1134+ }
1135+ return '${minutes }m' ;
1136+ }
9861137}
9871138
9881139class _LogEntry {
0 commit comments