Fix double delete confirm, inline place name editing, preserve assignments on trip extend

- Replace double browser confirm() with single custom ConfirmDialog for place deletion
- Add inline name editing via double-click in PlaceInspector
- Rewrite generateDays() to preserve existing days/assignments when extending trips
- Use UTC date math to avoid timezone-related day count errors
- Add missing collab.chat.emptyDesc translation (en/de)
This commit is contained in:
Maurice
2026-03-26 22:08:44 +01:00
parent feb2a8a5f2
commit 35275e209d
8 changed files with 226 additions and 19 deletions
@@ -674,7 +674,7 @@ export default function DayPlanSidebar({
place.website && { label: t('inspector.website'), icon: ExternalLink, onClick: () => window.open(place.website, '_blank') },
(place.lat && place.lng) && { label: 'Google Maps', icon: Navigation, onClick: () => window.open(`https://www.google.com/maps/search/?api=1&query=${place.lat},${place.lng}`, '_blank') },
{ divider: true },
onDeletePlace && { label: t('common.delete'), icon: Trash2, danger: true, onClick: () => { if (confirm(t('trip.confirm.deletePlace'))) onDeletePlace(place.id) } },
onDeletePlace && { label: t('common.delete'), icon: Trash2, danger: true, onClick: () => onDeletePlace(place.id) },
])}
onMouseEnter={() => setHoveredId(assignment.id)}
onMouseLeave={() => setHoveredId(null)}
@@ -100,16 +100,39 @@ function formatFileSize(bytes) {
export default function PlaceInspector({
place, categories, days, selectedDayId, selectedAssignmentId, assignments, reservations = [],
onClose, onEdit, onDelete, onAssignToDay, onRemoveAssignment,
files, onFileUpload, tripMembers = [], onSetParticipants,
files, onFileUpload, tripMembers = [], onSetParticipants, onUpdatePlace,
}) {
const { t, locale, language } = useTranslation()
const timeFormat = useSettingsStore(s => s.settings.time_format) || '24h'
const [hoursExpanded, setHoursExpanded] = useState(false)
const [filesExpanded, setFilesExpanded] = useState(false)
const [isUploading, setIsUploading] = useState(false)
const [editingName, setEditingName] = useState(false)
const [nameValue, setNameValue] = useState('')
const nameInputRef = useRef(null)
const fileInputRef = useRef(null)
const googleDetails = useGoogleDetails(place?.google_place_id, language)
const startNameEdit = () => {
if (!onUpdatePlace) return
setNameValue(place.name || '')
setEditingName(true)
setTimeout(() => nameInputRef.current?.focus(), 0)
}
const commitNameEdit = () => {
if (!editingName) return
const trimmed = nameValue.trim()
setEditingName(false)
if (!trimmed || trimmed === place.name) return
onUpdatePlace(place.id, { name: trimmed })
}
const handleNameKeyDown = (e) => {
if (e.key === 'Enter') { e.preventDefault(); commitNameEdit() }
if (e.key === 'Escape') setEditingName(false)
}
if (!place) return null
const category = categories?.find(c => c.id === place.category_id)
@@ -192,7 +215,21 @@ export default function PlaceInspector({
</div>
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 6, flexWrap: 'wrap' }}>
<span style={{ fontWeight: 600, fontSize: 15, color: 'var(--text-primary)', lineHeight: '1.3' }}>{place.name}</span>
{editingName ? (
<input
ref={nameInputRef}
value={nameValue}
onChange={e => setNameValue(e.target.value)}
onBlur={commitNameEdit}
onKeyDown={handleNameKeyDown}
style={{ fontWeight: 600, fontSize: 15, color: 'var(--text-primary)', lineHeight: '1.3', background: 'var(--bg-secondary)', border: '1px solid var(--border-primary)', borderRadius: 6, padding: '1px 6px', fontFamily: 'inherit', outline: 'none', width: '100%' }}
/>
) : (
<span
onDoubleClick={startNameEdit}
style={{ fontWeight: 600, fontSize: 15, color: 'var(--text-primary)', lineHeight: '1.3', cursor: onUpdatePlace ? 'text' : 'default' }}
>{place.name}</span>
)}
{category && (() => {
const CatIcon = getCategoryIcon(category.icon)
return (
@@ -146,7 +146,7 @@ export default function PlacesSidebar({
place.website && { label: t('inspector.website'), icon: ExternalLink, onClick: () => window.open(place.website, '_blank') },
(place.lat && place.lng) && { label: 'Google Maps', icon: Navigation, onClick: () => window.open(`https://www.google.com/maps/search/?api=1&query=${place.lat},${place.lng}`, '_blank') },
{ divider: true },
onDeletePlace && { label: t('common.delete'), icon: Trash2, danger: true, onClick: () => { if (confirm(t('trip.confirm.deletePlace'))) onDeletePlace(place.id) } },
onDeletePlace && { label: t('common.delete'), icon: Trash2, danger: true, onClick: () => onDeletePlace(place.id) },
])}
style={{
display: 'flex', alignItems: 'center', gap: 10,