Merge pull request #716 from mauriceboe/dev

Dev
This commit is contained in:
Julien G.
2026-04-18 02:08:16 +02:00
committed by GitHub
10 changed files with 161 additions and 112 deletions
+6 -15
View File
@@ -416,15 +416,10 @@ describe('DashboardPage', () => {
expect(screen.getAllByText('Paris Adventure').length).toBeGreaterThan(0);
});
// Find settings button — it's the gear icon button without title or text
// Find settings button — the gear icon button (icon-only, no visible label)
const allBtns = screen.getAllByRole('button');
const settingsButton = allBtns.find(
btn => {
const title = btn.getAttribute('title');
const text = btn.textContent?.trim() || '';
// Settings gear: no title, no meaningful text, not the notification bell
return !title && !text && btn.querySelector('.lucide-settings');
}
const settingsButton = allBtns.find(btn =>
btn.querySelector('.lucide-settings') && !btn.textContent?.trim()
);
expect(settingsButton).toBeDefined();
@@ -646,14 +641,10 @@ describe('DashboardPage', () => {
expect(screen.getAllByText('Paris Adventure').length).toBeGreaterThan(0);
});
// Open widget settings
// Open widget settings — gear icon button (icon-only, no visible label)
const allBtns = screen.getAllByRole('button');
const settingsButton = allBtns.find(
btn => {
const title = btn.getAttribute('title');
const text = btn.textContent?.trim() || '';
return !title && !text && btn.querySelector('.lucide-settings');
}
const settingsButton = allBtns.find(btn =>
btn.querySelector('.lucide-settings') && !btn.textContent?.trim()
);
expect(settingsButton).toBeDefined();
+67 -52
View File
@@ -897,61 +897,76 @@ export default function DashboardPage(): React.ReactElement {
</button>
</div>
{/* Desktop header */}
<div className="hidden md:flex" style={{ alignItems: 'center', justifyContent: 'space-between', marginBottom: 28 }}>
<div>
<h1 style={{ margin: 0, fontSize: 24, fontWeight: 800, color: 'var(--text-primary)' }}>{t('dashboard.title')}</h1>
<p style={{ margin: '3px 0 0', fontSize: 13, color: '#9ca3af' }}>
{/* Desktop header — unified toolbar */}
<div className="hidden md:block" style={{ marginBottom: 20 }}>
<div style={{
background: 'var(--bg-tertiary)', borderRadius: 18,
border: '1px solid var(--border-primary)',
boxShadow: '0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04)',
padding: '14px 16px 14px 22px',
display: 'flex', alignItems: 'center', gap: 16, flexWrap: 'wrap',
}}>
<h2 style={{ margin: 0, fontSize: 18, fontWeight: 600, color: 'var(--text-primary)', letterSpacing: '-0.01em', flexShrink: 0 }}>
{t('dashboard.title')}
</h2>
<div style={{ width: 1, height: 22, background: 'var(--border-faint)', flexShrink: 0 }} />
<span style={{ fontSize: 13, color: 'var(--text-muted)' }}>
{isLoading ? t('common.loading')
: trips.length > 0 ? `${t(trips.length !== 1 ? 'dashboard.subtitle.activeMany' : 'dashboard.subtitle.activeOne', { count: trips.length })}${archivedTrips.length > 0 ? t('dashboard.subtitle.archivedSuffix', { count: archivedTrips.length }) : ''}`
: t('dashboard.subtitle.empty')}
</p>
</div>
<div style={{ display: 'flex', gap: 8, alignItems: 'stretch' }}>
{/* View mode toggle */}
<button
onClick={toggleViewMode}
title={viewMode === 'grid' ? t('dashboard.listView') : t('dashboard.gridView')}
style={{
display: 'flex', alignItems: 'center', justifyContent: 'center',
padding: '0 14px', height: 37,
background: 'var(--bg-card)', border: '1px solid var(--border-primary)', borderRadius: 12,
cursor: 'pointer', color: 'var(--text-faint)', fontFamily: 'inherit',
transition: 'background 0.15s, border-color 0.15s',
}}
onMouseEnter={e => { e.currentTarget.style.background = 'var(--bg-hover)'; e.currentTarget.style.borderColor = 'var(--text-faint)' }}
onMouseLeave={e => { e.currentTarget.style.background = 'var(--bg-card)'; e.currentTarget.style.borderColor = 'var(--border-primary)' }}
>
{viewMode === 'grid' ? <List size={15} /> : <LayoutGrid size={15} />}
</button>
{/* Widget settings */}
<button
onClick={() => setShowWidgetSettings(s => s ? false : true)}
style={{
display: 'flex', alignItems: 'center', justifyContent: 'center',
padding: '0 14px', height: 37,
background: 'var(--bg-card)', border: '1px solid var(--border-primary)', borderRadius: 12,
cursor: 'pointer', color: 'var(--text-faint)', fontFamily: 'inherit',
transition: 'background 0.15s, border-color 0.15s',
}}
onMouseEnter={e => { e.currentTarget.style.background = 'var(--bg-hover)'; e.currentTarget.style.borderColor = 'var(--text-faint)' }}
onMouseLeave={e => { e.currentTarget.style.background = 'var(--bg-card)'; e.currentTarget.style.borderColor = 'var(--border-primary)' }}
>
<Settings size={15} />
</button>
{can('trip_create') && <button
onClick={() => { setEditingTrip(null); setShowForm(true) }}
style={{
display: 'flex', alignItems: 'center', gap: 7, padding: '9px 18px',
background: 'var(--accent)', color: 'var(--accent-text)', border: 'none', borderRadius: 12,
fontSize: 13, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit',
boxShadow: '0 2px 8px rgba(0,0,0,0.15)',
}}
onMouseEnter={e => e.currentTarget.style.opacity = '0.85'}
onMouseLeave={e => e.currentTarget.style.opacity = '1'}
>
<Plus size={15} /> {t('dashboard.newTrip')}
</button>}
</span>
<div style={{ display: 'inline-flex', gap: 6, alignItems: 'center', marginLeft: 'auto', flexShrink: 0 }}>
<button
onClick={toggleViewMode}
title={viewMode === 'grid' ? t('dashboard.listView') : t('dashboard.gridView')}
style={{
appearance: 'none', border: 'none', cursor: 'pointer', fontFamily: 'inherit',
display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
padding: '7px 11px', borderRadius: 99,
background: 'transparent', color: 'var(--text-muted)',
transition: 'all 0.15s ease',
}}
onMouseEnter={e => { e.currentTarget.style.background = 'var(--bg-card)'; e.currentTarget.style.color = 'var(--text-primary)' }}
onMouseLeave={e => { e.currentTarget.style.background = 'transparent'; e.currentTarget.style.color = 'var(--text-muted)' }}
>
{viewMode === 'grid' ? <List size={15} /> : <LayoutGrid size={15} />}
</button>
<button
onClick={() => setShowWidgetSettings(s => s ? false : true)}
title={t('dashboard.widgets') || 'Widgets'}
style={{
appearance: 'none', border: 'none', cursor: 'pointer', fontFamily: 'inherit',
display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
padding: '7px 11px', borderRadius: 99,
background: showWidgetSettings ? 'var(--bg-card)' : 'transparent',
color: showWidgetSettings ? 'var(--text-primary)' : 'var(--text-muted)',
boxShadow: showWidgetSettings ? '0 1px 2px rgba(0,0,0,0.06)' : 'none',
transition: 'all 0.15s ease',
}}
onMouseEnter={e => { if (!showWidgetSettings) { e.currentTarget.style.background = 'var(--bg-card)'; e.currentTarget.style.color = 'var(--text-primary)' } }}
onMouseLeave={e => { if (!showWidgetSettings) { e.currentTarget.style.background = 'transparent'; e.currentTarget.style.color = 'var(--text-muted)' } }}
>
<Settings size={15} />
</button>
{can('trip_create') && (
<button
onClick={() => { setEditingTrip(null); setShowForm(true) }}
style={{
appearance: 'none', border: 'none', cursor: 'pointer', fontFamily: 'inherit',
display: 'inline-flex', alignItems: 'center', gap: 6,
padding: '9px 14px', borderRadius: 10, fontSize: 13, fontWeight: 500,
background: 'var(--accent)', color: 'var(--accent-text)', flexShrink: 0,
marginLeft: 2,
transition: 'opacity 0.15s ease',
}}
onMouseEnter={e => e.currentTarget.style.opacity = '0.88'}
onMouseLeave={e => e.currentTarget.style.opacity = '1'}
>
<Plus size={14} strokeWidth={2.5} /> {t('dashboard.newTrip')}
</button>
)}
</div>
</div>
</div>
+35 -33
View File
@@ -150,39 +150,41 @@ export default function JourneyPage() {
)}
</div>
{/* Header — desktop */}
<div className="hidden md:flex items-start justify-between px-8 pt-10 pb-7">
<div>
<h1 className="text-[32px] font-extrabold tracking-[-0.025em] text-zinc-900 dark:text-white leading-none">{t('journey.title')}</h1>
<p className="text-[13px] text-zinc-500 mt-1.5">{t("journey.frontpage.subtitle")}</p>
</div>
<div className="flex items-center gap-2">
{searchOpen && (
<input
value={searchQuery}
onChange={e => setSearchQuery(e.target.value)}
onKeyDown={e => { if (e.key === 'Escape') { setSearchQuery(''); setSearchOpen(false) } }}
placeholder={t('journey.search.placeholder')}
autoFocus
className="w-52 px-3 py-2 border border-zinc-200 dark:border-zinc-700 rounded-[10px] text-[13px] bg-white dark:bg-zinc-800 text-zinc-900 dark:text-white focus:border-zinc-400 focus:outline-none"
/>
)}
<button
onClick={() => {
setSearchOpen(s => !s)
if (searchOpen) setSearchQuery('')
}}
className={`w-9 h-9 rounded-[10px] border border-zinc-200 dark:border-zinc-700 flex items-center justify-center text-zinc-500 transition-colors ${searchOpen ? 'bg-zinc-100 dark:bg-zinc-700' : 'bg-white dark:bg-zinc-800 hover:bg-zinc-50 dark:hover:bg-zinc-700'}`}
>
{searchOpen ? <X size={15} /> : <Search size={15} />}
</button>
<button
onClick={() => openCreateModal()}
className="inline-flex items-center gap-1.5 px-3.5 py-2 rounded-[10px] bg-zinc-900 dark:bg-white text-white dark:text-zinc-900 text-[13px] font-medium hover:bg-zinc-800 dark:hover:bg-zinc-100 transition-all hover:-translate-y-px"
>
<Plus size={14} />
{t('journey.frontpage.createJourney')}
</button>
{/* Header — desktop (unified toolbar) */}
<div className="hidden md:block px-8 pt-10 pb-7">
<div style={{
background: 'var(--bg-tertiary)', borderRadius: 18,
border: '1px solid var(--border-primary)',
boxShadow: '0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04)',
padding: '14px 16px 14px 22px',
display: 'flex', alignItems: 'center', gap: 16, flexWrap: 'wrap',
}}>
<h2 style={{ margin: 0, fontSize: 18, fontWeight: 600, color: 'var(--text-primary)', letterSpacing: '-0.01em', flexShrink: 0 }}>
{t('journey.title')}
</h2>
<div style={{ width: 1, height: 22, background: 'var(--border-faint)', flexShrink: 0 }} />
<span style={{ fontSize: 13, color: 'var(--text-muted)' }}>
{t('journey.frontpage.subtitle')}
</span>
<div style={{ display: 'inline-flex', gap: 6, alignItems: 'center', marginLeft: 'auto', flexShrink: 0 }}>
<button
onClick={() => openCreateModal()}
style={{
appearance: 'none', border: 'none', cursor: 'pointer', fontFamily: 'inherit',
display: 'inline-flex', alignItems: 'center', gap: 6,
padding: '9px 14px', borderRadius: 10, fontSize: 13, fontWeight: 500,
background: 'var(--accent)', color: 'var(--accent-text)', flexShrink: 0,
marginLeft: 2,
transition: 'opacity 0.15s ease',
}}
onMouseEnter={e => e.currentTarget.style.opacity = '0.88'}
onMouseLeave={e => e.currentTarget.style.opacity = '1'}
>
<Plus size={14} strokeWidth={2.5} />
{t('journey.frontpage.createJourney')}
</button>
</div>
</div>
</div>
+10
View File
@@ -233,10 +233,20 @@ export default function TripPlannerPage(): React.ReactElement | null {
const [showReservationModal, setShowReservationModal] = useState<boolean>(false)
const [editingReservation, setEditingReservation] = useState<Reservation | null>(null)
const [fitKey, setFitKey] = useState<number>(0)
const initialFitTripId = useRef<number | null>(null)
const [mobileSidebarOpen, setMobileSidebarOpen] = useState<'left' | 'right' | null>(null)
const [deletePlaceId, setDeletePlaceId] = useState<number | null>(null)
const [deletePlaceIds, setDeletePlaceIds] = useState<number[] | null>(null)
useEffect(() => {
if (!trip) return
if (initialFitTripId.current === trip.id) return
const hasGeoPlaces = places.some(p => p.lat != null && p.lng != null)
if (!hasGeoPlaces) return
initialFitTripId.current = trip.id
setFitKey(k => k + 1)
}, [trip, places])
const connectionsStorageKey = tripId ? `trek:visible-connections:${tripId}` : null
const [visibleConnections, setVisibleConnections] = useState<number[]>(() => {
if (typeof window === 'undefined' || !connectionsStorageKey) return []
+39 -8
View File
@@ -138,19 +138,15 @@ export default function VacayPage(): React.ReactElement {
<div style={{ paddingTop: 'var(--nav-h)' }}>
<div className="max-w-[1800px] mx-auto px-3 sm:px-4 py-4 sm:py-6">
{/* Header */}
<div className="flex items-center justify-between mb-4 sm:mb-5">
{/* Mobile + tablet header (filter toggle lives here) */}
<div className="lg:hidden flex items-center justify-between mb-4">
<div className="flex items-center gap-3">
<div className="w-9 h-9 rounded-xl flex items-center justify-center" style={{ background: 'var(--bg-secondary)' }}>
<CalendarDays size={18} style={{ color: 'var(--text-primary)' }} />
</div>
<div>
<h1 className="text-lg sm:text-xl font-bold" style={{ color: 'var(--text-primary)' }}>{t('admin.addons.catalog.vacay.name')}</h1>
<p className="text-xs hidden sm:block" style={{ color: 'var(--text-muted)' }}>{t('vacay.subtitle')}</p>
</div>
<h1 className="text-lg font-bold" style={{ color: 'var(--text-primary)' }}>{t('admin.addons.catalog.vacay.name')}</h1>
</div>
<div className="flex items-center gap-2">
{/* Mobile sidebar toggle */}
<button
onClick={() => setShowMobileSidebar(true)}
className="lg:hidden flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm transition-colors"
@@ -164,11 +160,46 @@ export default function VacayPage(): React.ReactElement {
style={{ background: 'var(--bg-secondary)', color: 'var(--text-muted)' }}
>
<Settings size={14} />
<span className="hidden sm:inline">{t('vacay.settings')}</span>
</button>
</div>
</div>
{/* Desktop header — unified toolbar (sidebar is always visible at this width) */}
<div className="hidden lg:block" style={{ marginBottom: 20 }}>
<div style={{
background: 'var(--bg-tertiary)', borderRadius: 18,
border: '1px solid var(--border-primary)',
boxShadow: '0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04)',
padding: '14px 16px 14px 22px',
display: 'flex', alignItems: 'center', gap: 16, flexWrap: 'wrap',
}}>
<h2 style={{ margin: 0, fontSize: 18, fontWeight: 600, color: 'var(--text-primary)', letterSpacing: '-0.01em', flexShrink: 0 }}>
{t('admin.addons.catalog.vacay.name')}
</h2>
<div style={{ width: 1, height: 22, background: 'var(--border-faint)', flexShrink: 0 }} />
<span style={{ fontSize: 13, color: 'var(--text-muted)' }}>
{t('vacay.subtitle')}
</span>
<div style={{ display: 'inline-flex', gap: 6, alignItems: 'center', marginLeft: 'auto', flexShrink: 0 }}>
<button
onClick={() => setShowSettings(true)}
style={{
appearance: 'none', border: 'none', cursor: 'pointer', fontFamily: 'inherit',
display: 'inline-flex', alignItems: 'center', gap: 6,
padding: '9px 14px', borderRadius: 10, fontSize: 13, fontWeight: 500,
background: 'var(--accent)', color: 'var(--accent-text)', flexShrink: 0,
marginLeft: 2,
transition: 'opacity 0.15s ease',
}}
onMouseEnter={e => e.currentTarget.style.opacity = '0.88'}
onMouseLeave={e => e.currentTarget.style.opacity = '1'}
>
<Settings size={14} strokeWidth={2.5} /> {t('vacay.settings')}
</button>
</div>
</div>
</div>
{/* Main layout */}
<div className="flex gap-4 items-start">
{/* Desktop Sidebar */}
+1
View File
@@ -212,6 +212,7 @@ export interface Settings {
show_place_description: boolean
route_calculation?: boolean
blur_booking_codes?: boolean
map_booking_labels?: boolean
}
export interface AssignmentsMap {