From 257bca14d1515a0e1c8a6bc486673466c88d96b4 Mon Sep 17 00:00:00 2001 From: elpatron Date: Sun, 31 May 2026 10:48:22 +0200 Subject: [PATCH] feat(dashboard): Logbuch-Titel per Inline-Bearbeitung umbenennen Ersetzt Umbenennen-Button und Modal durch Klick auf den Kartentitel. Co-authored-by: Cursor --- client/src/App.css | 46 ++++----- client/src/components/EditLogbookModal.tsx | 108 --------------------- client/src/components/LogbookDashboard.tsx | 102 ++++++++++++------- 3 files changed, 89 insertions(+), 167 deletions(-) delete mode 100644 client/src/components/EditLogbookModal.tsx diff --git a/client/src/App.css b/client/src/App.css index 9440289..ce17df6 100644 --- a/client/src/App.css +++ b/client/src/App.css @@ -1298,6 +1298,26 @@ html.scheme-dark .themed-select-option.is-selected { margin: 0; } +.logbook-title-editable { + cursor: text; + border-radius: 4px; + transition: background-color 0.15s ease; +} + +.logbook-title-editable:hover { + background: var(--app-accent-bg); +} + +.logbook-title-inline-edit { + flex: 1; + min-width: 0; + margin: 0; + padding: 2px 8px; + font-size: 16px; + font-weight: 600; + line-height: 1.4; +} + .card-icon { background: var(--app-accent-bg); color: var(--app-accent-light); @@ -1418,32 +1438,6 @@ html.scheme-dark .themed-select-option.is-selected { background: rgba(244, 63, 94, 0.1); } -.btn-edit { - background: none; - border: none; - color: #475569; - cursor: pointer; - padding: 6px; - border-radius: 6px; - display: flex; - align-items: center; - justify-content: center; - opacity: 0; - transition: all 0.2s ease; - position: absolute; - top: 12px; - right: 48px; -} - -.logbook-card:hover .btn-edit { - opacity: 1; -} - -.btn-edit:hover { - color: var(--app-accent-light); - background: var(--app-accent-bg); -} - .btn-pdf { background: none; border: none; diff --git a/client/src/components/EditLogbookModal.tsx b/client/src/components/EditLogbookModal.tsx deleted file mode 100644 index f21777d..0000000 --- a/client/src/components/EditLogbookModal.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import React, { useState, useEffect } from 'react' -import { useTranslation } from 'react-i18next' -import { Edit2, X } from 'lucide-react' - -interface EditLogbookModalProps { - open: boolean - onClose: () => void - currentTitle: string - onSave: (newTitle: string) => Promise -} - -export default function EditLogbookModal({ open, onClose, currentTitle, onSave }: EditLogbookModalProps) { - const { t } = useTranslation() - const [title, setTitle] = useState(currentTitle) - const [loading, setLoading] = useState(false) - const [error, setError] = useState(null) - - useEffect(() => { - if (open) { - setTitle(currentTitle) - setError(null) - } - }, [open, currentTitle]) - - if (!open) return null - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault() - const trimmedTitle = title.trim() - if (!trimmedTitle) return - - if (trimmedTitle === currentTitle.trim()) { - onClose() - return - } - - setLoading(true) - setError(null) - try { - await onSave(trimmedTitle) - onClose() - } catch (err: any) { - setError(err.message || 'Failed to rename logbook') - } finally { - setLoading(false) - } - } - - return ( -
-
e.stopPropagation()}> - - -

- - {t('dashboard.edit_title')} -

- -
-
- setTitle(e.target.value)} - disabled={loading} - required - autoFocus - style={{ width: '100%', textAlign: 'left' }} - /> -
- - {error &&
{error}
} - -
- - -
-
-
-
- ) -} diff --git a/client/src/components/LogbookDashboard.tsx b/client/src/components/LogbookDashboard.tsx index db14f92..ae1daa5 100644 --- a/client/src/components/LogbookDashboard.tsx +++ b/client/src/components/LogbookDashboard.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react' +import React, { useState, useEffect, useRef } from 'react' import { useTranslation } from 'react-i18next' import { useLiveQuery } from 'dexie-react-hooks' import { db } from '../services/db.js' @@ -8,10 +8,9 @@ import BetaBadge from './BetaBadge.tsx' import { PlausibleEvents, trackPlausibleEvent } from '../services/analytics.js' import { logoutUser } from '../services/auth.js' import { useDialog } from './ModalDialog.tsx' -import { BookOpen, Plus, Trash2, Edit2, LogOut, Languages, RefreshCw, Ship, User, Wifi, WifiOff } from 'lucide-react' +import { BookOpen, Plus, Trash2, LogOut, Languages, RefreshCw, Ship, User, Wifi, WifiOff } from 'lucide-react' import DisclaimerHeaderButton from './DisclaimerHeaderButton.tsx' import FeedbackHeaderButton from './FeedbackHeaderButton.tsx' -import EditLogbookModal from './EditLogbookModal.tsx' interface LogbookDashboardProps { onSelectLogbook: (id: string, title: string) => void @@ -24,7 +23,9 @@ export default function LogbookDashboard({ onSelectLogbook, onLogout, onOpenProf const { showConfirm } = useDialog() const [logbooks, setLogbooks] = useState([]) const [newTitle, setNewTitle] = useState('') - const [editingLogbook, setEditingLogbook] = useState(null) + const [editingLogbookId, setEditingLogbookId] = useState(null) + const [editingTitleDraft, setEditingTitleDraft] = useState('') + const titleInputRef = useRef(null) const [loading, setLoading] = useState(false) const [refreshing, setRefreshing] = useState(false) const [error, setError] = useState(null) @@ -101,25 +102,44 @@ export default function LogbookDashboard({ onSelectLogbook, onLogout, onOpenProf } } - const handleEdit = (lb: DecryptedLogbook, e: React.MouseEvent) => { - e.stopPropagation() // Prevent selecting the logbook when clicking edit - setEditingLogbook(lb) + useEffect(() => { + if (editingLogbookId) { + titleInputRef.current?.focus() + titleInputRef.current?.select() + } + }, [editingLogbookId]) + + const startTitleEdit = (lb: DecryptedLogbook, e: React.MouseEvent) => { + e.stopPropagation() + setEditingLogbookId(lb.id) + setEditingTitleDraft(lb.title) } - const handleSaveTitle = async (newTitle: string) => { - if (!editingLogbook) return + const cancelTitleEdit = () => { + setEditingLogbookId(null) + setEditingTitleDraft('') + } + + const commitTitleEdit = async (id: string) => { + if (editingLogbookId !== id) return + + const lb = logbooks.find((item) => item.id === id) + const trimmedTitle = editingTitleDraft.trim() + cancelTitleEdit() + + if (!lb || !trimmedTitle || trimmedTitle === lb.title.trim()) return setLoading(true) setError(null) try { - await updateLogbookTitle(editingLogbook.id, newTitle) + await updateLogbookTitle(id, trimmedTitle) setLogbooks((prev) => - prev.map((lb) => (lb.id === editingLogbook.id ? { ...lb, title: newTitle, updatedAt: new Date().toISOString() } : lb)) + prev.map((item) => + item.id === id ? { ...item, title: trimmedTitle, updatedAt: new Date().toISOString() } : item + ) ) - setEditingLogbook(null) } catch (err: any) { setError(err.message || 'Failed to update logbook title') - throw err } finally { setLoading(false) } @@ -138,7 +158,10 @@ export default function LogbookDashboard({ onSelectLogbook, onLogout, onOpenProf const ownedLogbooks = logbooks.filter((lb) => !lb.isShared) const sharedLogbooks = logbooks.filter((lb) => lb.isShared) - const renderLogbookCard = (lb: DecryptedLogbook) => ( + const renderLogbookCard = (lb: DecryptedLogbook) => { + const isEditingTitle = editingLogbookId === lb.id + + return (
-

{lb.title}

+ {isEditingTitle ? ( + setEditingTitleDraft(e.target.value)} + onClick={(e) => e.stopPropagation()} + onKeyDown={(e) => { + if (e.key === 'Enter') { + e.preventDefault() + void commitTitleEdit(lb.id) + } else if (e.key === 'Escape') { + e.preventDefault() + cancelTitleEdit() + } + }} + onBlur={() => void commitTitleEdit(lb.id)} + disabled={loading} + aria-label={t('dashboard.edit_title')} + /> + ) : ( +

startTitleEdit(lb, e)} + title={lb.isShared ? undefined : t('dashboard.edit_title')} + > + {lb.title} +

+ )}
@@ -170,15 +222,6 @@ export default function LogbookDashboard({ onSelectLogbook, onLogout, onOpenProf
- - - ) + ) + } const renderLogbookSection = ( title: string, @@ -326,14 +370,6 @@ export default function LogbookDashboard({ onSelectLogbook, onLogout, onOpenProf )} - - {/* Edit Logbook Title Modal */} - setEditingLogbook(null)} - currentTitle={editingLogbook?.title || ''} - onSave={handleSaveTitle} - /> ) }