fix(logs): Ereignisprotokoll chronologisch nach Uhrzeit sortieren

Einträge werden beim Laden, Speichern und Export älteste-oben angezeigt (sortLogEventsByTime).

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-30 19:05:46 +02:00
co-authored by Cursor
parent 2e83f1c6bb
commit 258fee31ab
4 changed files with 15 additions and 8 deletions
+5 -5
View File
@@ -20,7 +20,7 @@ import {
hasAnySignature hasAnySignature
} from '../utils/signatures.js' } from '../utils/signatures.js'
import type { SignatureValue } from '../types/signatures.js' import type { SignatureValue } from '../types/signatures.js'
import { buildLogEntryPayload, type LogEventPayload } from '../utils/logEntryPayload.js' import { buildLogEntryPayload, sortLogEventsByTime, type LogEventPayload } from '../utils/logEntryPayload.js'
import { hashEntryForSigning } from '../utils/entryCanonicalHash.js' import { hashEntryForSigning } from '../utils/entryCanonicalHash.js'
import { signLogEntry } from '../services/entrySigning.js' import { signLogEntry } from '../services/entrySigning.js'
import { getLogbookAccess } from '../services/logbookAccess.js' import { getLogbookAccess } from '../services/logbookAccess.js'
@@ -483,7 +483,7 @@ export default function LogEntryEditor({
setSignSkipper(normalizeSignature(preloadedEntry.signSkipper) || '') setSignSkipper(normalizeSignature(preloadedEntry.signSkipper) || '')
setSignCrew(normalizeSignature(preloadedEntry.signCrew) || '') setSignCrew(normalizeSignature(preloadedEntry.signCrew) || '')
loadTrackStatsFromEntry(preloadedEntry) loadTrackStatsFromEntry(preloadedEntry)
setEvents(preloadedEntry.events || []) setEvents(sortLogEventsByTime(preloadedEntry.events || []))
setSavedFingerprint(fingerprintFromStoredEntry(preloadedEntry)) setSavedFingerprint(fingerprintFromStoredEntry(preloadedEntry))
return return
} }
@@ -516,7 +516,7 @@ export default function LogEntryEditor({
setSignSkipper(normalizeSignature(decrypted.signSkipper) || '') setSignSkipper(normalizeSignature(decrypted.signSkipper) || '')
setSignCrew(normalizeSignature(decrypted.signCrew) || '') setSignCrew(normalizeSignature(decrypted.signCrew) || '')
loadTrackStatsFromEntry(decrypted) loadTrackStatsFromEntry(decrypted)
setEvents(decrypted.events || []) setEvents(sortLogEventsByTime(decrypted.events || []))
setSavedFingerprint(fingerprintFromStoredEntry(decrypted)) setSavedFingerprint(fingerprintFromStoredEntry(decrypted))
} }
} }
@@ -871,7 +871,7 @@ export default function LogEntryEditor({
if (editingEventIndex !== null) { if (editingEventIndex !== null) {
const hadSkipperSignature = !!signSkipper const hadSkipperSignature = !!signSkipper
markSkipperSignatureClearedForEventChange() markSkipperSignatureClearedForEventChange()
nextEvents = events.map((ev, idx) => (idx === editingEventIndex ? eventData : ev)) nextEvents = sortLogEventsByTime(events.map((ev, idx) => (idx === editingEventIndex ? eventData : ev)))
if (hadSkipperSignature) { if (hadSkipperSignature) {
void showAlertRef.current( void showAlertRef.current(
t('logs.sign_cleared_skipper_re_sign'), t('logs.sign_cleared_skipper_re_sign'),
@@ -879,7 +879,7 @@ export default function LogEntryEditor({
) )
} }
} else { } else {
nextEvents = [...events, eventData] nextEvents = sortLogEventsByTime([...events, eventData])
} }
setEvents(nextEvents) setEvents(nextEvents)
+2 -1
View File
@@ -3,6 +3,7 @@ import { getActiveMasterKey } from './auth.js'
import { getLogbookKey } from './logbookKeys.js' import { getLogbookKey } from './logbookKeys.js'
import { decryptJson } from './crypto.js' import { decryptJson } from './crypto.js'
import { formatSignatureForExport, normalizeSignature } from '../utils/signatures.js' import { formatSignatureForExport, normalizeSignature } from '../utils/signatures.js'
import { sortLogEventsByTime } from '../utils/logEntryPayload.js'
import i18n from '../i18n/index.js' import i18n from '../i18n/index.js'
function escapeCsvValue(val: string | number | undefined | null): string { function escapeCsvValue(val: string | number | undefined | null): string {
@@ -134,7 +135,7 @@ export async function exportLogbookToCsv(logbookId: string, preloadedData?: { ya
].map(escapeCsvValue)); ].map(escapeCsvValue));
} else { } else {
// Sort events chronologically by time // Sort events chronologically by time
const sortedEvents = [...eventsList].sort((a, b) => (a.time || '').localeCompare(b.time || '')); const sortedEvents = sortLogEventsByTime(eventsList);
for (const ev of sortedEvents) { for (const ev of sortedEvents) {
rows.push([ rows.push([
dateVal, travelDay, dep, dest, dateVal, travelDay, dep, dest,
+2 -1
View File
@@ -4,6 +4,7 @@ import { getActiveMasterKey } from './auth.js'
import { getLogbookKey } from './logbookKeys.js' import { getLogbookKey } from './logbookKeys.js'
import { decryptJson } from './crypto.js' import { decryptJson } from './crypto.js'
import { isSignatureImage, isPasskeySignature } from '../utils/signatures.js' import { isSignatureImage, isPasskeySignature } from '../utils/signatures.js'
import { sortLogEventsByTime } from '../utils/logEntryPayload.js'
import i18n from '../i18n/index.js' import i18n from '../i18n/index.js'
function formatPasskeySignDate(signedAt: string): string { function formatPasskeySignDate(signedAt: string): string {
@@ -132,7 +133,7 @@ export async function generateLogbookPagePdf(logbookId: string, entryId: string,
// Draw Data Rows // Draw Data Rows
const events = entry.events || []; const events = entry.events || [];
const maxRows = 16; const maxRows = 16;
const sortedEvents = [...events].sort((a: any, b: any) => (a.time || '').localeCompare(b.time || '')); const sortedEvents = sortLogEventsByTime(events);
doc.setFont('Helvetica', 'normal'); doc.setFont('Helvetica', 'normal');
+6 -1
View File
@@ -17,6 +17,11 @@ export interface LogEventPayload {
remarks: string remarks: string
} }
/** Chronological order: earliest time first (HH:MM). */
export function sortLogEventsByTime<T extends Pick<LogEventPayload, 'time'>>(events: T[]): T[] {
return [...events].sort((a, b) => (a.time || '').localeCompare(b.time || ''))
}
export interface LogEntryPayloadInput { export interface LogEntryPayloadInput {
date: string date: string
dayOfTravel: string dayOfTravel: string
@@ -38,7 +43,7 @@ export function buildLogEntryPayload(input: LogEntryPayloadInput): Record<string
destination: input.destination.trim(), destination: input.destination.trim(),
freshwater: { ...input.freshwater }, freshwater: { ...input.freshwater },
fuel: { ...input.fuel }, fuel: { ...input.fuel },
events: input.events.map((e) => ({ ...e })) events: sortLogEventsByTime(input.events.map((e) => ({ ...e })))
} }
if (input.trackDistanceNm !== undefined) payload.trackDistanceNm = input.trackDistanceNm if (input.trackDistanceNm !== undefined) payload.trackDistanceNm = input.trackDistanceNm