feat: show date badge on day selectors + i18n transport modal titles

Day selectors in the Transport, Reservation and Hotel-Day-Range
modals only showed the renamed day title once a day had a custom
name — hiding the actual date. Added an optional badge prop to
CustomSelect, rendered as a pill next to the label, and wired the
date badge onto all affected dropdowns. FileManager day section
headers got the same pill for consistency.

Also translated transport.addTransport and transport.modalTitle.*
in all 13 non-English language files; the keys existed but still
carried the English source string.
This commit is contained in:
Maurice
2026-04-21 12:28:43 +02:00
parent 457d436cf6
commit 3450bd59f8
19 changed files with 81 additions and 49 deletions
+8 -2
View File
@@ -649,8 +649,14 @@ export default function FileManager({ files = [], onUpload, onDelete, onUpdate,
</div>
{dayGroups.map(({ day, dayPlaces }) => (
<div key={day.id}>
<div style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-muted)', padding: '8px 10px 2px' }}>
{day.title || `${t('dayplan.dayN', { n: day.day_number })}${day.date ? ` · ${day.date}` : ''}`}
<div style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 11, fontWeight: 600, color: 'var(--text-muted)', padding: '8px 10px 2px' }}>
<span>{day.title || t('dayplan.dayN', { n: day.day_number })}</span>
{day.date && (
<span style={{
fontSize: 10, fontWeight: 600, color: 'var(--text-faint)',
background: 'var(--bg-tertiary)', padding: '1px 6px', borderRadius: 999,
}}>{day.date}</span>
)}
</div>
{dayPlaces.map(placeBtn)}
</div>
@@ -462,7 +462,8 @@ export default function DayDetailPanel({ day, days, places, categories = [], tri
onChange={v => setHotelDayRange(prev => ({ start: v, end: Math.max(v, prev.end) }))}
options={days.map((d, i) => ({
value: d.id,
label: `${d.title || t('planner.dayN', { n: i + 1 })}${d.date ? `${new Date(d.date + 'T00:00:00Z').toLocaleDateString(locale, { day: 'numeric', month: 'short', timeZone: 'UTC' })}` : ''}`,
label: d.title || t('planner.dayN', { n: i + 1 }),
badge: d.date ? new Date(d.date + 'T00:00:00Z').toLocaleDateString(locale, { day: 'numeric', month: 'short', timeZone: 'UTC' }) : undefined,
}))}
size="sm"
/>
@@ -474,7 +475,8 @@ export default function DayDetailPanel({ day, days, places, categories = [], tri
onChange={v => setHotelDayRange(prev => ({ start: Math.min(prev.start, v), end: v }))}
options={days.map((d, i) => ({
value: d.id,
label: `${d.title || t('planner.dayN', { n: i + 1 })}${d.date ? `${new Date(d.date + 'T00:00:00Z').toLocaleDateString(locale, { day: 'numeric', month: 'short', timeZone: 'UTC' })}` : ''}`,
label: d.title || t('planner.dayN', { n: i + 1 }),
badge: d.date ? new Date(d.date + 'T00:00:00Z').toLocaleDateString(locale, { day: 'numeric', month: 'short', timeZone: 'UTC' }) : undefined,
}))}
size="sm"
/>
@@ -439,7 +439,11 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p
value={form.hotel_start_day}
onChange={value => set('hotel_start_day', value)}
placeholder={t('reservations.meta.selectDay')}
options={days.map(d => ({ value: d.id, label: d.title || `${t('dayplan.dayN', { n: d.day_number })}${d.date ? ` · ${formatDate(d.date, locale)}` : ''}` }))}
options={days.map(d => ({
value: d.id,
label: d.title || t('dayplan.dayN', { n: d.day_number }),
badge: d.date ? (formatDate(d.date, locale) ?? undefined) : undefined,
}))}
size="sm"
/>
</div>
@@ -449,7 +453,11 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p
value={form.hotel_end_day}
onChange={value => set('hotel_end_day', value)}
placeholder={t('reservations.meta.selectDay')}
options={days.map(d => ({ value: d.id, label: d.title || `${t('dayplan.dayN', { n: d.day_number })}${d.date ? ` · ${formatDate(d.date, locale)}` : ''}` }))}
options={days.map(d => ({
value: d.id,
label: d.title || t('dayplan.dayN', { n: d.day_number }),
badge: d.date ? (formatDate(d.date, locale) ?? undefined) : undefined,
}))}
size="sm"
/>
</div>
@@ -222,7 +222,8 @@ export function TransportModal({ isOpen, onClose, onSave, reservation, days, sel
{ value: '', label: '—' },
...days.map(d => ({
value: d.id,
label: d.title || `${t('dayplan.dayN', { n: d.day_number })}${d.date ? ` · ${formatDate(d.date, locale) ?? ''}` : ''}`,
label: d.title || t('dayplan.dayN', { n: d.day_number }),
badge: d.date ? (formatDate(d.date, locale) ?? undefined) : undefined,
})),
]
@@ -9,6 +9,7 @@ interface SelectOption {
isHeader?: boolean
searchLabel?: string
groupLabel?: string
badge?: string
}
interface CustomSelectProps {
@@ -104,6 +105,13 @@ export default function CustomSelect({
<span style={{ flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', color: selected ? 'var(--text-primary)' : 'var(--text-faint)' }}>
{selected ? selected.label : placeholder}
</span>
{selected?.badge && (
<span style={{
flexShrink: 0, fontSize: 10, fontWeight: 600, color: 'var(--text-muted)',
background: 'var(--bg-tertiary)', padding: '2px 7px', borderRadius: 999,
letterSpacing: '0.01em',
}}>{selected.badge}</span>
)}
<ChevronDown size={sm ? 12 : 14} style={{ flexShrink: 0, color: 'var(--text-faint)', transition: 'transform 200ms cubic-bezier(0.23,1,0.32,1)', transform: open ? 'rotate(180deg)' : 'none' }} />
</button>
@@ -186,6 +194,13 @@ export default function CustomSelect({
>
{option.icon && <span style={{ display: 'flex', flexShrink: 0 }}>{option.icon}</span>}
<span style={{ flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{option.label}</span>
{option.badge && (
<span style={{
flexShrink: 0, fontSize: 10, fontWeight: 600, color: 'var(--text-muted)',
background: 'var(--bg-tertiary)', padding: '2px 7px', borderRadius: 999,
letterSpacing: '0.01em',
}}>{option.badge}</span>
)}
{isSelected && <Check size={13} style={{ flexShrink: 0, color: 'var(--text-muted)' }} />}
</button>
)