diff --git a/lib/components/pages/loco_timeline/timeline_grid.dart b/lib/components/pages/loco_timeline/timeline_grid.dart index 044ad46..8f083a4 100644 --- a/lib/components/pages/loco_timeline/timeline_grid.dart +++ b/lib/components/pages/loco_timeline/timeline_grid.dart @@ -716,9 +716,14 @@ bool _isOverlappingStart(LocoAttrVersion entry, Set approvedStartKeys) { List<_ValueSegment> _segmentsForEntries( List items, - DateTime now, -) { + DateTime now, { + bool? clampToNextStart, +}) { if (items.isEmpty) return const []; + final hasPending = items.any((e) => e.isPending); + final hasApproved = items.any((e) => !e.isPending); + final shouldClamp = + clampToNextStart ?? (hasPending && hasApproved); final sorted = [...items]; sorted.sort( (a, b) => (_effectiveStart(a) ?? now) @@ -731,7 +736,13 @@ List<_ValueSegment> _segmentsForEntries( final nextStart = i < sorted.length - 1 ? _effectiveStart(sorted[i + 1]) : null; - final rawEnd = entry.validTo ?? nextStart ?? now; + DateTime? rawEnd = entry.validTo; + if (nextStart != null) { + if (rawEnd == null || (shouldClamp && nextStart.isBefore(rawEnd))) { + rawEnd = nextStart; + } + } + rawEnd ??= now; final end = _safeEnd(start, rawEnd); segments.add( _ValueSegment( @@ -745,6 +756,53 @@ List<_ValueSegment> _segmentsForEntries( return segments; } +List _applyPendingOverrides( + List approved, + List pending, +) { + if (pending.isEmpty) return approved; + final pendingByStart = {}; + final extraPending = []; + for (final entry in pending) { + final start = _effectiveStart(entry); + if (start == null) continue; + final key = _startKey(start); + pendingByStart[key] = entry; + } + + final applied = []; + final seenKeys = {}; + for (final entry in approved) { + final start = _effectiveStart(entry); + if (start == null) continue; + final key = _startKey(start); + if (pendingByStart.containsKey(key)) { + if (!seenKeys.contains(key)) { + applied.add(pendingByStart[key]!); + seenKeys.add(key); + } + } else { + applied.add(entry); + seenKeys.add(key); + } + } + + for (final entry in pendingByStart.values) { + final start = _effectiveStart(entry); + if (start == null) continue; + final key = _startKey(start); + if (!seenKeys.contains(key)) { + extraPending.add(entry); + seenKeys.add(key); + } + } + + if (extraPending.isNotEmpty) { + applied.addAll(extraPending); + } + return applied; +} + List _buildBoundaries( List<_ValueSegment> segments, DateTime now, @@ -904,14 +962,17 @@ class _TimelineModel { } final hasOverlap = overlapByUser.isNotEmpty; - final canToggle = pending.length > 1 && !hasOverlap; + final canToggle = pending.isNotEmpty && !hasOverlap; final isExpanded = expandedAttrCodes.contains(attr); + final shouldShowPendingRows = isExpanded || hasOverlap; final nonOverlapPending = pending.where((e) => !_isOverlappingStart(e, approvedStartKeys)).toList(); - final baseEntries = isExpanded ? approved : [...approved, ...nonOverlapPending]; - final baseSegments = - isExpanded ? approvedSegments : _segmentsForEntries(baseEntries, now); + final baseEntries = + shouldShowPendingRows ? approved : [...approved, ...nonOverlapPending]; + final baseSegments = shouldShowPendingRows + ? approvedSegments + : _segmentsForEntries(baseEntries, now); rowSpecs.add( _TimelineRowSpec.primary( @@ -923,7 +984,6 @@ class _TimelineModel { ); allSegments.addAll(baseSegments); - final shouldShowPendingRows = isExpanded || hasOverlap; if (shouldShowPendingRows) { final users = isExpanded ? pendingByUser.keys.toList() @@ -934,11 +994,9 @@ class _TimelineModel { ? (pendingByUser[user] ?? const []) : (overlapByUser[user] ?? const []); if (pendingEntries.isEmpty) continue; - final userPendingSegments = _segmentsForEntries(pendingEntries, now); - final combinedSegments = [ - ...approvedSegments, - ...userPendingSegments, - ]; + final appliedEntries = + _applyPendingOverrides(approved, pendingEntries); + final combinedSegments = _segmentsForEntries(appliedEntries, now); rowSpecs.add( _TimelineRowSpec.pending( attrCode: attr,