import React, { useState, useEffect } from 'react' import { useTranslation } from 'react-i18next' import { db } from '../services/db.js' import { getActiveMasterKey } from '../services/auth.js' import { encryptJson, decryptJson } from '../services/crypto.js' import { syncLogbook } from '../services/sync.js' import { FileText, Save, ChevronLeft, Check, Compass, Plus, Trash2, MapPin, CloudSun, Clock } from 'lucide-react' interface LogEntryEditorProps { entryId: string logbookId: string onBack: () => void } interface LogEvent { time: string mgk: string rwk: string windPressure: string windDirection: string windStrength: string seaState: string weatherIcon: string current: string heel: string sailsOrMotor: string logReading: string distance: string gpsLat: string gpsLng: string remarks: string } export default function LogEntryEditor({ entryId, logbookId, onBack }: LogEntryEditorProps) { const { t } = useTranslation() // General details state const [date, setDate] = useState('') const [dayOfTravel, setDayOfTravel] = useState('') const [departure, setDeparture] = useState('') const [destination, setDestination] = useState('') // Freshwater state const [fwMorning, setFwMorning] = useState('0') const [fwRefilled, setFwRefilled] = useState('0') const [fwEvening, setFwEvening] = useState('0') const [fwConsumption, setFwConsumption] = useState('0') // Fuel state const [fuelMorning, setFuelMorning] = useState('0') const [fuelRefilled, setFuelRefilled] = useState('0') const [fuelEvening, setFuelEvening] = useState('0') const [fuelConsumption, setFuelConsumption] = useState('0') // Signatures const [signSkipper, setSignSkipper] = useState('') const [signCrew, setSignCrew] = useState('') // Events list state const [events, setEvents] = useState([]) // Add Event Form State const [evTime, setEvTime] = useState('') const [evMgk, setEvMgk] = useState('') const [evRwk, setEvRwk] = useState('') const [evWindPressure, setEvWindPressure] = useState('') const [evWindDirection, setEvWindDirection] = useState('') const [evWindStrength, setEvWindStrength] = useState('') const [evSeaState, setEvSeaState] = useState('') const [evWeatherIcon, setEvWeatherIcon] = useState('') const [evCurrent, setEvCurrent] = useState('') const [evHeel, setEvHeel] = useState('') const [evSailsOrMotor, setEvSailsOrMotor] = useState('') const [evLogReading, setEvLogReading] = useState('') const [evDistance, setEvDistance] = useState('') const [evGpsLat, setEvGpsLat] = useState('') const [evGpsLng, setEvGpsLng] = useState('') const [evRemarks, setEvRemarks] = useState('') const [loading, setLoading] = useState(false) const [saving, setSaving] = useState(false) const [success, setSuccess] = useState(false) const [error, setError] = useState(null) const [weatherLoading, setWeatherLoading] = useState(false) // Auto-calculate Freshwater Consumption useEffect(() => { const morning = parseFloat(fwMorning) || 0 const refilled = parseFloat(fwRefilled) || 0 const evening = parseFloat(fwEvening) || 0 const cons = morning + refilled - evening setFwConsumption(cons >= 0 ? String(cons) : '0') }, [fwMorning, fwRefilled, fwEvening]) // Auto-calculate Fuel Consumption useEffect(() => { const morning = parseFloat(fuelMorning) || 0 const refilled = parseFloat(fuelRefilled) || 0 const evening = parseFloat(fuelEvening) || 0 const cons = morning + refilled - evening setFuelConsumption(cons >= 0 ? String(cons) : '0') }, [fuelMorning, fuelRefilled, fuelEvening]) // Load entry details useEffect(() => { async function loadEntry() { setLoading(true) setError(null) try { const masterKey = getActiveMasterKey() if (!masterKey) throw new Error('Master key not found. Please log in.') const local = await db.entries.get(entryId) if (local) { const decrypted = await decryptJson(local.encryptedData, local.iv, local.tag, masterKey) if (decrypted) { setDate(decrypted.date || '') setDayOfTravel(decrypted.dayOfTravel || '') setDeparture(decrypted.departure || '') setDestination(decrypted.destination || '') if (decrypted.freshwater) { setFwMorning(String(decrypted.freshwater.morning || 0)) setFwRefilled(String(decrypted.freshwater.refilled || 0)) setFwEvening(String(decrypted.freshwater.evening || 0)) } if (decrypted.fuel) { setFuelMorning(String(decrypted.fuel.morning || 0)) setFuelRefilled(String(decrypted.fuel.refilled || 0)) setFuelEvening(String(decrypted.fuel.evening || 0)) } setSignSkipper(decrypted.signSkipper || '') setSignCrew(decrypted.signCrew || '') setEvents(decrypted.events || []) } } } catch (err: any) { console.error('Failed to load entry details:', err) setError(err.message || 'Decryption failed. Could not load entry details.') } finally { setLoading(false) } } loadEntry() }, [entryId]) const handleGetGps = () => { if (!navigator.geolocation) { alert('Geolocation is not supported by your browser') return } navigator.geolocation.getCurrentPosition( (pos) => { setEvGpsLat(pos.coords.latitude.toFixed(6)) setEvGpsLng(pos.coords.longitude.toFixed(6)) }, (err) => { console.error('GPS capturing failed:', err) alert(`Failed to retrieve coordinates: ${err.message}`) } ) } const handleFetchWeather = async () => { if (!evGpsLat || !evGpsLng) { alert(t('settings.gps_error')) return } const apiKey = localStorage.getItem('owm_api_key') if (!apiKey) { alert(t('settings.no_key')) return } setWeatherLoading(true) try { const res = await fetch( `https://api.openweathermap.org/data/2.5/weather?lat=${evGpsLat}&lon=${evGpsLng}&appid=${apiKey}&units=metric` ) if (!res.ok) throw new Error('Weather API rejected the request') const data = await res.json() // Convert wind speed m/s to Beaufort scale const mps = data.wind.speed || 0 let bft = 0 if (mps < 0.3) bft = 0 else if (mps < 1.6) bft = 1 else if (mps < 3.4) bft = 2 else if (mps < 5.5) bft = 3 else if (mps < 8.0) bft = 4 else if (mps < 10.8) bft = 5 else if (mps < 13.9) bft = 6 else if (mps < 17.2) bft = 7 else if (mps < 20.8) bft = 8 else if (mps < 24.5) bft = 9 else if (mps < 28.5) bft = 10 else if (mps < 32.7) bft = 11 else bft = 12 setEvWindStrength(`${bft} Bft (${mps.toFixed(1)} m/s)`) setEvWindPressure(String(data.main.pressure || '')) // Calculate wind compass direction sector if (data.wind.deg !== undefined) { const deg = data.wind.deg const sectors = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW'] const index = Math.round(deg / 22.5) % 16 setEvWindDirection(sectors[index]) } if (data.weather && data.weather[0]) { setEvWeatherIcon(data.weather[0].icon) } alert(t('settings.weather_success')) } catch (err) { console.error('Weather prefilling failed:', err) alert(t('settings.weather_error')) } finally { setWeatherLoading(false) } } const handleAddEvent = (e: React.FormEvent) => { e.preventDefault() if (!evTime) return const newEvent: LogEvent = { time: evTime, mgk: evMgk.trim(), rwk: evRwk.trim(), windPressure: evWindPressure.trim(), windDirection: evWindDirection.trim(), windStrength: evWindStrength.trim(), seaState: evSeaState.trim(), weatherIcon: evWeatherIcon.trim(), current: evCurrent.trim(), heel: evHeel.trim(), sailsOrMotor: evSailsOrMotor.trim(), logReading: evLogReading.trim(), distance: evDistance.trim(), gpsLat: evGpsLat.trim(), gpsLng: evGpsLng.trim(), remarks: evRemarks.trim() } setEvents((prev) => [...prev, newEvent]) // Clear event form fields setEvTime('') setEvMgk('') setEvRwk('') setEvWindPressure('') setEvWindDirection('') setEvWindStrength('') setEvSeaState('') setEvWeatherIcon('') setEvCurrent('') setEvHeel('') setEvSailsOrMotor('') setEvLogReading('') setEvDistance('') setEvGpsLat('') setEvGpsLng('') setEvRemarks('') } const handleDeleteEvent = (index: number) => { setEvents((prev) => prev.filter((_, idx) => idx !== index)) } const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() setSaving(true) setError(null) setSuccess(false) try { const masterKey = getActiveMasterKey() if (!masterKey) throw new Error('Master key not found. Please log in.') const entryData = { date, dayOfTravel: dayOfTravel.trim(), departure: departure.trim(), destination: destination.trim(), freshwater: { morning: parseFloat(fwMorning) || 0, refilled: parseFloat(fwRefilled) || 0, evening: parseFloat(fwEvening) || 0, consumption: parseFloat(fwConsumption) || 0 }, fuel: { morning: parseFloat(fuelMorning) || 0, refilled: parseFloat(fuelRefilled) || 0, evening: parseFloat(fuelEvening) || 0, consumption: parseFloat(fuelConsumption) || 0 }, signSkipper: signSkipper.trim(), signCrew: signCrew.trim(), events } // E2E encrypt const encrypted = await encryptJson(entryData, masterKey) const now = new Date().toISOString() // Save locally await db.entries.put({ payloadId: entryId, logbookId, encryptedData: encrypted.ciphertext, iv: encrypted.iv, tag: encrypted.tag, updatedAt: now }) // Queue for background sync await db.syncQueue.put({ action: 'update', type: 'entry', payloadId: entryId, logbookId, data: JSON.stringify(encrypted), updatedAt: now }) setSuccess(true) setTimeout(() => { setSuccess(false) onBack() }, 1500) syncLogbook(logbookId).catch((err) => console.warn('Background sync failed:', err)) } catch (err: any) { console.error('Failed to save entry details:', err) setError(err.message || 'Failed to save entry details.') } finally { setSaving(false) } } if (loading) { return (

{t('logs.loading')}

) } return (
{/* Top Header Controls */}

{t('logs.route')}: {departure || '...'} → {destination || '...'} (Tag {dayOfTravel})

{error &&
{error}
} {/* Main Journal Data Forms */}
{/* Section 1: Travel Day Headers */}

Travel Details

setDate(e.target.value)} disabled={saving} required />
setDayOfTravel(e.target.value)} disabled={saving} required />
setDeparture(e.target.value)} disabled={saving} />
setDestination(e.target.value)} disabled={saving} />
{/* Section 2: Freshwater and Fuel Consumption */}
{/* Freshwater card */}

{t('logs.freshwater')}

setFwMorning(e.target.value)} disabled={saving} />
setFwRefilled(e.target.value)} disabled={saving} />
setFwEvening(e.target.value)} disabled={saving} />
{/* Fuel card */}

{t('logs.fuel')}

setFuelMorning(e.target.value)} disabled={saving} />
setFuelRefilled(e.target.value)} disabled={saving} />
setFuelEvening(e.target.value)} disabled={saving} />
{/* Section 3: Event Journal Entries */}

{t('logs.event_title')}

{/* List existing events */} {events.length === 0 ? (
{t('logs.no_events')}
) : (
{events.map((ev, idx) => ( ))}
{t('logs.event_time')} {t('logs.event_mgk')} {t('logs.event_rwk')} {t('logs.event_wind_direction')} {t('logs.event_wind_strength')} {t('logs.event_sea_state')} {t('logs.event_weather')} {t('logs.event_log')} {t('logs.event_gps')} {t('logs.event_remarks')}
{ev.time} {ev.mgk ? `${ev.mgk}°` : '—'} {ev.rwk ? `${ev.rwk}°` : '—'} {ev.windDirection || '—'} {ev.windStrength || '—'} {ev.seaState || '—'} {ev.weatherIcon ? ( Weather ) : ( '—' )} {ev.logReading ? `${ev.logReading} nm` : '—'} {ev.gpsLat && ev.gpsLng ? `${ev.gpsLat}, ${ev.gpsLng}` : '—'} {ev.remarks}
)} {/* Add New Event Form Sub-Card */}

Add Event Log Record

setEvTime(e.target.value)} disabled={saving} />
setEvMgk(e.target.value)} disabled={saving} />
setEvRwk(e.target.value)} disabled={saving} />
setEvLogReading(e.target.value)} disabled={saving} />
setEvGpsLat(e.target.value)} disabled={saving} /> setEvGpsLng(e.target.value)} disabled={saving} />
setEvWindDirection(e.target.value)} disabled={saving || weatherLoading} />
setEvWindStrength(e.target.value)} disabled={saving || weatherLoading} />
setEvWindPressure(e.target.value)} disabled={saving || weatherLoading} />
setEvSeaState(e.target.value)} disabled={saving} />
setEvHeel(e.target.value)} disabled={saving} />
setEvSailsOrMotor(e.target.value)} disabled={saving} />
setEvDistance(e.target.value)} disabled={saving} />
setEvRemarks(e.target.value)} disabled={saving} />
{/* Section 4: Sign-Off Signatures */}

{t('logs.signatures')}

setSignSkipper(e.target.value)} disabled={saving} required />
setSignCrew(e.target.value)} disabled={saving} required />
{/* Save Controls */}
{success && (
{t('logs.saved')}
)}
) }