import React, { useState, useEffect } from 'react' import { useTranslation } from 'react-i18next' import { db } from '../services/db.js' import { getActiveMasterKey } from '../services/auth.js' import { getLogbookKey } from '../services/logbookKeys.js' import { encryptJson, decryptJson } from '../services/crypto.js' import { syncLogbook } from '../services/sync.js' import { Compass, Save, Check } from 'lucide-react' interface DeviationFormProps { logbookId: string readOnly?: boolean preloadedData?: any } export default function DeviationForm({ logbookId, readOnly = false, preloadedData }: DeviationFormProps) { const { t } = useTranslation() // Generate headings: 0, 10, 20, ..., 360 (37 items) const headings = Array.from({ length: 37 }, (_, i) => i * 10) // Map representation: heading -> deviation input string (e.g., "1.5", "-2") const [deviations, setDeviations] = useState>({}) const [loading, setLoading] = useState(false) const [saving, setSaving] = useState(false) const [success, setSuccess] = useState(false) const [error, setError] = useState(null) useEffect(() => { async function loadDeviationTable() { setLoading(true) setError(null) try { if (readOnly && preloadedData) { const map: Record = {} if (preloadedData.deviations) { Object.entries(preloadedData.deviations).forEach(([k, v]) => { map[Number(k)] = String(v) }) } setDeviations(map) return } const masterKey = await getLogbookKey(logbookId) || getActiveMasterKey() if (!masterKey) throw new Error('Encryption key not found. Please log in.') const local = await db.deviations.get(logbookId) if (local) { const decrypted = await decryptJson(local.encryptedData, local.iv, local.tag, masterKey) if (decrypted && decrypted.deviations) { // Map keys back to numbers and set state const map: Record = {} Object.entries(decrypted.deviations).forEach(([k, v]) => { map[Number(k)] = String(v) }) setDeviations(map) } } else { // Initialize empty map const map: Record = {} headings.forEach((h) => { map[h] = '' }) setDeviations(map) } } catch (err: any) { console.error('Failed to load deviation data:', err) setError(err.message || 'Decryption failed. Could not load deviation table.') } finally { setLoading(false) } } loadDeviationTable() }, [logbookId]) const handleInputChange = (heading: number, val: string) => { setDeviations((prev) => ({ ...prev, [heading]: val })) } const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() if (readOnly) return setSaving(true) setError(null) setSuccess(false) try { const masterKey = await getLogbookKey(logbookId) || getActiveMasterKey() if (!masterKey) throw new Error('Encryption key not found. Please log in.') // Parse values, substituting 0 if empty const sanitizedDeviations: Record = {} headings.forEach((h) => { const val = deviations[h] || '' const parsed = parseFloat(val.replace('+', '').trim()) sanitizedDeviations[h] = isNaN(parsed) ? 0 : parsed }) const dataToSave = { deviations: sanitizedDeviations } // E2E encrypt const encrypted = await encryptJson(dataToSave, masterKey) const now = new Date().toISOString() // Save locally await db.deviations.put({ logbookId, encryptedData: encrypted.ciphertext, iv: encrypted.iv, tag: encrypted.tag, updatedAt: now }) // Queue for background sync await db.syncQueue.put({ action: 'update', type: 'deviation', payloadId: logbookId, logbookId, data: JSON.stringify(encrypted), updatedAt: now }) setSuccess(true) setTimeout(() => setSuccess(false), 3000) syncLogbook(logbookId).catch((err) => console.warn('Background sync failed:', err)) } catch (err: any) { console.error('Failed to save deviation table:', err) setError(err.message || 'Failed to save deviation table.') } finally { setSaving(false) } } if (loading) { return (

{t('deviation.loading')}

) } return (

{t('deviation.title')}

{t('deviation.subtitle')}

{error &&
{error}
}
{headings.map((h) => { const paddedLabel = String(h).padStart(3, '0') + '°' return (
{paddedLabel} handleInputChange(h, e.target.value)} disabled={saving || readOnly} />
) })}
{!readOnly && (
{success && (
{t('deviation.saved')}
)}
)}
) }