feat: Tankstände vom Vortag bei neuem Reisetag mit Bestätigung übernehmen.
Abendstände werden als Morgenstände vorgeschlagen; der Nutzer kann übernehmen oder mit 0 starten. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1564,6 +1564,7 @@ body:has(.theme-cupertino) {
|
|||||||
color: #e2e8f0;
|
color: #e2e8f0;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
margin: 0 0 24px 0;
|
margin: 0 0 24px 0;
|
||||||
|
white-space: pre-line;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-dialog-actions {
|
.custom-dialog-actions {
|
||||||
|
|||||||
@@ -10,6 +10,15 @@ import { downloadLogbookPagePdf } from '../services/pdfExport.js'
|
|||||||
import LogEntryEditor from './LogEntryEditor.tsx'
|
import LogEntryEditor from './LogEntryEditor.tsx'
|
||||||
import { useDialog } from './ModalDialog.tsx'
|
import { useDialog } from './ModalDialog.tsx'
|
||||||
import { FileText, Plus, Trash2, ChevronRight, Calendar, Download, Share2 } from 'lucide-react'
|
import { FileText, Plus, Trash2, ChevronRight, Calendar, Download, Share2 } from 'lucide-react'
|
||||||
|
import {
|
||||||
|
carryOverTankLevelsFromPreviousDay,
|
||||||
|
compareTravelDaysChronological,
|
||||||
|
emptyTankLevels,
|
||||||
|
formatTankLiters,
|
||||||
|
getNextTravelDayNumber,
|
||||||
|
type LogEntryTankSource,
|
||||||
|
type TravelDaySortable
|
||||||
|
} from '../utils/logEntryTankLevels.js'
|
||||||
|
|
||||||
interface LogEntriesListProps {
|
interface LogEntriesListProps {
|
||||||
logbookId: string
|
logbookId: string
|
||||||
@@ -179,26 +188,52 @@ export default function LogEntriesList({
|
|||||||
|
|
||||||
const handleCreate = async () => {
|
const handleCreate = async () => {
|
||||||
if (readOnly) return
|
if (readOnly) return
|
||||||
setLoading(true)
|
|
||||||
setError(null)
|
setError(null)
|
||||||
try {
|
try {
|
||||||
const masterKey = await getLogbookKey(logbookId) || getActiveMasterKey()
|
const masterKey = await getLogbookKey(logbookId) || getActiveMasterKey()
|
||||||
if (!masterKey) throw new Error('Encryption key not found. Please log in.')
|
if (!masterKey) throw new Error('Encryption key not found. Please log in.')
|
||||||
|
|
||||||
|
const localEntries = await db.entries.where({ logbookId }).toArray()
|
||||||
|
const decryptedEntries: Array<LogEntryTankSource & TravelDaySortable> = []
|
||||||
|
|
||||||
|
for (const entry of localEntries) {
|
||||||
|
const decrypted = await decryptJson(entry.encryptedData, entry.iv, entry.tag, masterKey)
|
||||||
|
if (decrypted) decryptedEntries.push(decrypted as LogEntryTankSource & TravelDaySortable)
|
||||||
|
}
|
||||||
|
|
||||||
|
decryptedEntries.sort(compareTravelDaysChronological)
|
||||||
|
const previousEntry = decryptedEntries.at(-1) ?? null
|
||||||
|
let { freshwater, fuel } = carryOverTankLevelsFromPreviousDay(previousEntry)
|
||||||
|
|
||||||
|
if (previousEntry && (freshwater.morning > 0 || fuel.morning > 0)) {
|
||||||
|
const confirmed = await showConfirm(
|
||||||
|
t('logs.carry_over_tanks_confirm', {
|
||||||
|
fw: formatTankLiters(freshwater.morning),
|
||||||
|
fuel: formatTankLiters(fuel.morning)
|
||||||
|
}),
|
||||||
|
t('logs.carry_over_tanks_title'),
|
||||||
|
t('logs.carry_over_tanks_yes'),
|
||||||
|
t('logs.carry_over_tanks_no')
|
||||||
|
)
|
||||||
|
if (!confirmed) {
|
||||||
|
freshwater = emptyTankLevels()
|
||||||
|
fuel = emptyTankLevels()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true)
|
||||||
|
|
||||||
const localId = window.crypto.randomUUID()
|
const localId = window.crypto.randomUUID()
|
||||||
const nowStr = new Date().toISOString()
|
const nowStr = new Date().toISOString()
|
||||||
const todayStr = nowStr.substring(0, 10)
|
const todayStr = nowStr.substring(0, 10)
|
||||||
|
|
||||||
// Calculate next travel day number
|
|
||||||
const nextDayNum = String(entries.length + 1)
|
|
||||||
|
|
||||||
const initialPayload = {
|
const initialPayload = {
|
||||||
date: todayStr,
|
date: todayStr,
|
||||||
dayOfTravel: nextDayNum,
|
dayOfTravel: getNextTravelDayNumber(decryptedEntries),
|
||||||
departure: '',
|
departure: '',
|
||||||
destination: '',
|
destination: '',
|
||||||
freshwater: { morning: 0, refilled: 0, evening: 0, consumption: 0 },
|
freshwater,
|
||||||
fuel: { morning: 0, refilled: 0, evening: 0, consumption: 0 },
|
fuel,
|
||||||
signSkipper: '',
|
signSkipper: '',
|
||||||
signCrew: '',
|
signCrew: '',
|
||||||
events: []
|
events: []
|
||||||
|
|||||||
@@ -125,6 +125,10 @@
|
|||||||
"loading": "Journal wird geladen...",
|
"loading": "Journal wird geladen...",
|
||||||
"delete_entry": "Tag löschen",
|
"delete_entry": "Tag löschen",
|
||||||
"delete_confirm": "Sind Sie sicher, dass Sie diesen Reisetag unwiderruflich löschen möchten?",
|
"delete_confirm": "Sind Sie sicher, dass Sie diesen Reisetag unwiderruflich löschen möchten?",
|
||||||
|
"carry_over_tanks_title": "Tankstände übernehmen?",
|
||||||
|
"carry_over_tanks_confirm": "Morgenstände vom letzten Reisetag als Startwerte übernehmen?\n\nFrischwasser: {{fw}} L\nKraftstoff: {{fuel}} L",
|
||||||
|
"carry_over_tanks_yes": "Übernehmen",
|
||||||
|
"carry_over_tanks_no": "Mit 0 starten",
|
||||||
"event_title": "Chronologisches Ereignisprotokoll",
|
"event_title": "Chronologisches Ereignisprotokoll",
|
||||||
"no_events": "Noch keine Ereignisse für diesen Reisetag eingetragen.",
|
"no_events": "Noch keine Ereignisse für diesen Reisetag eingetragen.",
|
||||||
"event_time": "Uhrzeit",
|
"event_time": "Uhrzeit",
|
||||||
|
|||||||
@@ -125,6 +125,10 @@
|
|||||||
"loading": "Loading journal...",
|
"loading": "Loading journal...",
|
||||||
"delete_entry": "Delete Day",
|
"delete_entry": "Delete Day",
|
||||||
"delete_confirm": "Are you sure you want to permanently delete this travel day?",
|
"delete_confirm": "Are you sure you want to permanently delete this travel day?",
|
||||||
|
"carry_over_tanks_title": "Carry over tank levels?",
|
||||||
|
"carry_over_tanks_confirm": "Use the previous travel day's closing levels as morning levels?\n\nFreshwater: {{fw}} L\nFuel: {{fuel}} L",
|
||||||
|
"carry_over_tanks_yes": "Carry over",
|
||||||
|
"carry_over_tanks_no": "Start at 0",
|
||||||
"event_title": "Chronological Event Logbook",
|
"event_title": "Chronological Event Logbook",
|
||||||
"no_events": "No events logged for this travel day yet.",
|
"no_events": "No events logged for this travel day yet.",
|
||||||
"event_time": "Time",
|
"event_time": "Time",
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
export interface TankLevels {
|
||||||
|
morning: number
|
||||||
|
refilled: number
|
||||||
|
evening: number
|
||||||
|
consumption: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TravelDaySortable {
|
||||||
|
date?: string
|
||||||
|
dayOfTravel?: string | number
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Chronological order: date ascending, then day of travel ascending. */
|
||||||
|
export function compareTravelDaysChronological(a: TravelDaySortable, b: TravelDaySortable): number {
|
||||||
|
const dateCompare = new Date(a.date || 0).getTime() - new Date(b.date || 0).getTime()
|
||||||
|
if (dateCompare !== 0) return dateCompare
|
||||||
|
return Number(a.dayOfTravel || 0) - Number(b.dayOfTravel || 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getNextTravelDayNumber(entries: TravelDaySortable[]): string {
|
||||||
|
const maxDay = entries.reduce((max, entry) => Math.max(max, Number(entry.dayOfTravel) || 0), 0)
|
||||||
|
return String(maxDay + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Closing level at end of travel day: evening stand, else calculated balance, else morning. */
|
||||||
|
export function getClosingTankLevel(tank?: Partial<TankLevels> | null): number {
|
||||||
|
if (!tank) return 0
|
||||||
|
|
||||||
|
const evening = Number(tank.evening) || 0
|
||||||
|
if (evening > 0) return evening
|
||||||
|
|
||||||
|
const morning = Number(tank.morning) || 0
|
||||||
|
const refilled = Number(tank.refilled) || 0
|
||||||
|
const consumption = Number(tank.consumption) || 0
|
||||||
|
const fromBalance = morning + refilled - consumption
|
||||||
|
if (fromBalance > 0) return fromBalance
|
||||||
|
|
||||||
|
return morning
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LogEntryTankSource {
|
||||||
|
freshwater?: Partial<TankLevels>
|
||||||
|
fuel?: Partial<TankLevels>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function emptyTankLevels(morning = 0): TankLevels {
|
||||||
|
return { morning, refilled: 0, evening: 0, consumption: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatTankLiters(liters: number): string {
|
||||||
|
if (!Number.isFinite(liters) || liters <= 0) return '0'
|
||||||
|
return Number.isInteger(liters) ? String(liters) : liters.toFixed(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function carryOverTankLevelsFromPreviousDay(previousEntry?: LogEntryTankSource | null): { freshwater: TankLevels; fuel: TankLevels } {
|
||||||
|
if (!previousEntry) {
|
||||||
|
return { freshwater: emptyTankLevels(), fuel: emptyTankLevels() }
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
freshwater: emptyTankLevels(getClosingTankLevel(previousEntry.freshwater)),
|
||||||
|
fuel: emptyTankLevels(getClosingTankLevel(previousEntry.fuel))
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user