mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 05:26:47 +00:00
feat(settings): add always-show-booking-routes account setting
New map_always_show_routes account setting, defaulting to off, i18n'd across all 22 supported locales. Lives in Display > Travel & map, directly under Booking route labels — its closest sibling — using that section's immediate-save On/Off pattern rather than a separate toggle+Save flow, since it's a booking-display preference, not a map render-config option. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// FE-COMP-DISPLAY-001 to FE-COMP-DISPLAY-027
|
||||
import { render, screen } from '../../../tests/helpers/render';
|
||||
import { render, screen, within } from '../../../tests/helpers/render';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { http, HttpResponse } from 'msw';
|
||||
import { server } from '../../../tests/helpers/msw/server';
|
||||
@@ -127,11 +127,38 @@ describe('DisplaySettingsTab', () => {
|
||||
it('FE-COMP-DISPLAY-025: blur booking codes On button is active when blur_booking_codes is true', () => {
|
||||
seedStore(useSettingsStore, { settings: buildSettings({ blur_booking_codes: true }) });
|
||||
render(<DisplaySettingsTab />);
|
||||
const onButtons = screen.getAllByText(/^On$/i);
|
||||
const blurOnBtn = onButtons[1].closest('button')!;
|
||||
const block = screen.getByText(/blur booking codes/i).closest('div')!;
|
||||
const blurOnBtn = within(block).getByText(/^On$/i).closest('button')!;
|
||||
expect(blurOnBtn.style.border).toContain('var(--text-primary)');
|
||||
});
|
||||
|
||||
it('FE-COMP-DISPLAY-030: shows Always show booking routes next to Booking route labels', () => {
|
||||
render(<DisplaySettingsTab />);
|
||||
const bookingLabels = screen.getByText(/booking route labels/i);
|
||||
const alwaysShow = screen.getByText(/always show booking routes/i);
|
||||
expect(alwaysShow).toBeInTheDocument();
|
||||
// Adjacent siblings within the Travel & Map section: alwaysShow's block
|
||||
// immediately follows bookingLabels' block.
|
||||
expect(bookingLabels.closest('div')!.nextElementSibling).toBe(alwaysShow.closest('div'));
|
||||
});
|
||||
|
||||
it('FE-COMP-DISPLAY-031: always-show-routes Off button is active by default (unset)', () => {
|
||||
render(<DisplaySettingsTab />);
|
||||
const block = screen.getByText(/always show booking routes/i).closest('div')!;
|
||||
const offBtn = within(block).getByText(/^Off$/i).closest('button')!;
|
||||
expect(offBtn.style.border).toContain('var(--text-primary)');
|
||||
});
|
||||
|
||||
it('FE-COMP-DISPLAY-032: clicking On for always-show-routes calls updateSetting with map_always_show_routes true', async () => {
|
||||
const user = userEvent.setup();
|
||||
const updateSetting = vi.fn().mockResolvedValue(undefined);
|
||||
seedStore(useSettingsStore, { settings: buildSettings(), updateSetting });
|
||||
render(<DisplaySettingsTab />);
|
||||
const block = screen.getByText(/always show booking routes/i).closest('div')!;
|
||||
await user.click(within(block).getByText(/^On$/i));
|
||||
expect(updateSetting).toHaveBeenCalledWith('map_always_show_routes', true);
|
||||
});
|
||||
|
||||
it('FE-COMP-DISPLAY-026: updateSetting failure shows toast error', async () => {
|
||||
const user = userEvent.setup();
|
||||
const updateSetting = vi.fn().mockRejectedValue(new Error('Server error'));
|
||||
|
||||
@@ -265,6 +265,37 @@ export default function DisplaySettingsTab(): React.ReactElement {
|
||||
<p className="text-xs mt-1 text-content-faint">{t('settings.bookingLabelsHint')}</p>
|
||||
</div>
|
||||
|
||||
{/* Always show booking routes */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2 text-content-secondary">{t('settings.alwaysShowRoutes')}</label>
|
||||
<div className="flex gap-3">
|
||||
{[
|
||||
{ value: true, label: t('settings.on') || 'On' },
|
||||
{ value: false, label: t('settings.off') || 'Off' },
|
||||
].map(opt => (
|
||||
<button
|
||||
key={String(opt.value)}
|
||||
onClick={async () => {
|
||||
try { await updateSetting('map_always_show_routes', opt.value) }
|
||||
catch (e: unknown) { toast.error(e instanceof Error ? e.message : t('common.error')) }
|
||||
}}
|
||||
style={{
|
||||
display: 'flex', alignItems: 'center', gap: 8,
|
||||
padding: '10px 20px', borderRadius: 10, cursor: 'pointer',
|
||||
fontFamily: 'inherit', fontSize: 'calc(14px * var(--fs-scale-body, 1))', fontWeight: 500,
|
||||
border: (settings.map_always_show_routes === true) === opt.value ? '2px solid var(--text-primary)' : '2px solid var(--border-primary)',
|
||||
background: (settings.map_always_show_routes === true) === opt.value ? 'var(--bg-hover)' : 'var(--bg-card)',
|
||||
color: 'var(--text-primary)',
|
||||
transition: 'all 0.15s',
|
||||
}}
|
||||
>
|
||||
{opt.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<p className="text-xs mt-1 text-content-faint">{t('settings.alwaysShowRoutesHint')}</p>
|
||||
</div>
|
||||
|
||||
{/* Explore places on the map (POI category pill) */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2 text-content-secondary">{t('settings.mapPoiPill')}</label>
|
||||
|
||||
@@ -117,6 +117,7 @@ export interface Settings {
|
||||
blur_booking_codes?: boolean
|
||||
map_booking_labels?: boolean
|
||||
map_poi_pill_enabled?: boolean
|
||||
map_always_show_routes?: boolean
|
||||
optimize_from_accommodation?: boolean
|
||||
map_provider?: 'leaflet' | 'mapbox-gl' | 'maplibre-gl'
|
||||
mapbox_access_token?: string
|
||||
|
||||
@@ -477,6 +477,8 @@ const settings: TranslationStrings = {
|
||||
'settings.pluginActivity.columns.resource': 'المورد',
|
||||
'settings.pluginActivity.columns.when': 'الوقت',
|
||||
'settings.pluginActivity.columns.status': 'النتيجة',
|
||||
'settings.alwaysShowRoutes': 'إظهار مسارات الحجوزات دائمًا',
|
||||
'settings.alwaysShowRoutesHint': 'يعرض تلقائيًا مسار كل رحلة طيران وقطار وحجز آخر على الخريطة، دون الحاجة إلى تفعيله لكل عنصر على حدة.',
|
||||
};
|
||||
|
||||
export default settings;
|
||||
|
||||
@@ -493,6 +493,8 @@ const settings: TranslationStrings = {
|
||||
'settings.pluginActivity.columns.resource': 'Recurso',
|
||||
'settings.pluginActivity.columns.when': 'Quando',
|
||||
'settings.pluginActivity.columns.status': 'Resultado',
|
||||
'settings.alwaysShowRoutes': 'Sempre mostrar rotas de reserva',
|
||||
'settings.alwaysShowRoutesHint': 'Mostra automaticamente no mapa a rota de cada voo, trem e outra reserva, sem precisar ativar item por item.',
|
||||
};
|
||||
|
||||
export default settings;
|
||||
|
||||
@@ -485,6 +485,8 @@ const settings: TranslationStrings = {
|
||||
'settings.pluginActivity.columns.resource': 'Zdroj',
|
||||
'settings.pluginActivity.columns.when': 'Kdy',
|
||||
'settings.pluginActivity.columns.status': 'Výsledek',
|
||||
'settings.alwaysShowRoutes': 'Vždy zobrazovat trasy rezervací',
|
||||
'settings.alwaysShowRoutesHint': 'Automaticky zobrazí na mapě trasu každého letu, vlaku a jiné rezervace, aniž byste ji museli zapínat jednotlivě.',
|
||||
};
|
||||
|
||||
export default settings;
|
||||
|
||||
@@ -496,6 +496,8 @@ const settings: TranslationStrings = {
|
||||
'settings.pluginActivity.columns.resource': 'Ressource',
|
||||
'settings.pluginActivity.columns.when': 'Zeitpunkt',
|
||||
'settings.pluginActivity.columns.status': 'Ergebnis',
|
||||
'settings.alwaysShowRoutes': 'Buchungsrouten immer anzeigen',
|
||||
'settings.alwaysShowRoutesHint': 'Zeigt die Route für jeden Flug, jede Zugfahrt und jede andere Buchung automatisch auf der Karte an – ohne sie einzeln aktivieren zu müssen.',
|
||||
};
|
||||
|
||||
export default settings;
|
||||
|
||||
@@ -483,6 +483,8 @@ const settings: TranslationStrings = {
|
||||
'settings.offline.banner.syncing': 'Syncing {count}…',
|
||||
'settings.offline.banner.failed': 'Failed to sync: {count}',
|
||||
'settings.offline.banner.conflicts': 'Conflicts: {count}',
|
||||
'settings.alwaysShowRoutes': 'Always show booking routes',
|
||||
'settings.alwaysShowRoutesHint': 'Automatically draw the route for every flight, train and other booking on the map — no need to switch it on per item.',
|
||||
};
|
||||
|
||||
export default settings;
|
||||
|
||||
@@ -494,6 +494,8 @@ const settings: TranslationStrings = {
|
||||
'settings.pluginActivity.columns.resource': 'Recurso',
|
||||
'settings.pluginActivity.columns.when': 'Cuándo',
|
||||
'settings.pluginActivity.columns.status': 'Resultado',
|
||||
'settings.alwaysShowRoutes': 'Mostrar siempre las rutas de reserva',
|
||||
'settings.alwaysShowRoutesHint': 'Muestra automáticamente en el mapa la ruta de cada vuelo, tren y otra reserva, sin necesidad de activarla una por una.',
|
||||
};
|
||||
|
||||
export default settings;
|
||||
|
||||
@@ -501,6 +501,8 @@ const settings: TranslationStrings = {
|
||||
'settings.pluginActivity.columns.resource': 'Ressource',
|
||||
'settings.pluginActivity.columns.when': 'Quand',
|
||||
'settings.pluginActivity.columns.status': 'Résultat',
|
||||
'settings.alwaysShowRoutes': 'Toujours afficher les itinéraires de réservation',
|
||||
'settings.alwaysShowRoutesHint': 'Affiche automatiquement sur la carte l\'itinéraire de chaque vol, trajet en train et autre réservation, sans avoir à l\'activer un par un.',
|
||||
};
|
||||
|
||||
export default settings;
|
||||
|
||||
@@ -504,6 +504,8 @@ const settings: TranslationStrings = {
|
||||
'settings.pluginActivity.columns.resource': 'Πόρος',
|
||||
'settings.pluginActivity.columns.when': 'Πότε',
|
||||
'settings.pluginActivity.columns.status': 'Αποτέλεσμα',
|
||||
'settings.alwaysShowRoutes': 'Πάντα εμφάνιση διαδρομών κράτησης',
|
||||
'settings.alwaysShowRoutesHint': 'Εμφανίζει αυτόματα στον χάρτη τη διαδρομή κάθε πτήσης, τρένου και άλλης κράτησης, χωρίς να χρειάζεται να την ενεργοποιείτε μία προς μία.',
|
||||
};
|
||||
|
||||
export default settings;
|
||||
|
||||
@@ -494,6 +494,8 @@ const settings: TranslationStrings = {
|
||||
'settings.pluginActivity.columns.resource': 'Erőforrás',
|
||||
'settings.pluginActivity.columns.when': 'Mikor',
|
||||
'settings.pluginActivity.columns.status': 'Eredmény',
|
||||
'settings.alwaysShowRoutes': 'Mindig jelenjenek meg a foglalási útvonalak',
|
||||
'settings.alwaysShowRoutesHint': 'Automatikusan megjeleníti minden repülőjárat, vonat és egyéb foglalás útvonalát a térképen, nincs szükség egyenkénti bekapcsolásra.',
|
||||
};
|
||||
|
||||
export default settings;
|
||||
|
||||
@@ -491,6 +491,8 @@ const settings: TranslationStrings = {
|
||||
'settings.pluginActivity.columns.resource': 'Sumber daya',
|
||||
'settings.pluginActivity.columns.when': 'Waktu',
|
||||
'settings.pluginActivity.columns.status': 'Hasil',
|
||||
'settings.alwaysShowRoutes': 'Selalu tampilkan rute pemesanan',
|
||||
'settings.alwaysShowRoutesHint': 'Menampilkan rute setiap penerbangan, kereta, dan pemesanan lainnya di peta secara otomatis, tanpa perlu mengaktifkannya satu per satu.',
|
||||
};
|
||||
|
||||
export default settings;
|
||||
|
||||
@@ -493,6 +493,8 @@ const settings: TranslationStrings = {
|
||||
'settings.pluginActivity.columns.resource': 'Risorsa',
|
||||
'settings.pluginActivity.columns.when': 'Quando',
|
||||
'settings.pluginActivity.columns.status': 'Esito',
|
||||
'settings.alwaysShowRoutes': 'Mostra sempre i percorsi delle prenotazioni',
|
||||
'settings.alwaysShowRoutesHint': 'Mostra automaticamente sulla mappa il percorso di ogni volo, treno e altra prenotazione, senza doverlo attivare singolarmente.',
|
||||
};
|
||||
|
||||
export default settings;
|
||||
|
||||
@@ -465,6 +465,8 @@ const settings: TranslationStrings = {
|
||||
'settings.pluginActivity.columns.resource': 'リソース',
|
||||
'settings.pluginActivity.columns.when': '日時',
|
||||
'settings.pluginActivity.columns.status': '結果',
|
||||
'settings.alwaysShowRoutes': '予約ルートを常に表示',
|
||||
'settings.alwaysShowRoutesHint': 'フライトや電車などすべての予約のルートを、個別にオンにしなくても地図上に自動的に表示します。',
|
||||
};
|
||||
|
||||
export default settings;
|
||||
|
||||
@@ -481,6 +481,8 @@ const settings: TranslationStrings = {
|
||||
'settings.pluginActivity.columns.resource': '리소스',
|
||||
'settings.pluginActivity.columns.when': '시간',
|
||||
'settings.pluginActivity.columns.status': '결과',
|
||||
'settings.alwaysShowRoutes': '예약 경로 항상 표시',
|
||||
'settings.alwaysShowRoutesHint': '항공편, 기차 등 모든 예약의 경로를 개별적으로 켤 필요 없이 지도에 자동으로 표시합니다.',
|
||||
};
|
||||
|
||||
export default settings;
|
||||
|
||||
@@ -494,6 +494,8 @@ const settings: TranslationStrings = {
|
||||
'settings.pluginActivity.columns.resource': 'Bron',
|
||||
'settings.pluginActivity.columns.when': 'Wanneer',
|
||||
'settings.pluginActivity.columns.status': 'Resultaat',
|
||||
'settings.alwaysShowRoutes': 'Boekingsroutes altijd tonen',
|
||||
'settings.alwaysShowRoutesHint': 'Toont automatisch de route van elke vlucht, trein en andere boeking op de kaart, zonder dat u dit per boeking hoeft in te schakelen.',
|
||||
};
|
||||
|
||||
export default settings;
|
||||
|
||||
@@ -493,6 +493,8 @@ const settings: TranslationStrings = {
|
||||
'settings.pluginActivity.columns.resource': 'Zasób',
|
||||
'settings.pluginActivity.columns.when': 'Kiedy',
|
||||
'settings.pluginActivity.columns.status': 'Wynik',
|
||||
'settings.alwaysShowRoutes': 'Zawsze pokazuj trasy rezerwacji',
|
||||
'settings.alwaysShowRoutesHint': 'Automatycznie pokazuje na mapie trasę każdego lotu, pociągu i innej rezerwacji, bez konieczności włączania jej pojedynczo.',
|
||||
};
|
||||
|
||||
export default settings;
|
||||
|
||||
@@ -494,6 +494,8 @@ const settings: TranslationStrings = {
|
||||
'settings.pluginActivity.columns.resource': 'Ресурс',
|
||||
'settings.pluginActivity.columns.when': 'Когда',
|
||||
'settings.pluginActivity.columns.status': 'Результат',
|
||||
'settings.alwaysShowRoutes': 'Всегда показывать маршруты бронирований',
|
||||
'settings.alwaysShowRoutesHint': 'Автоматически показывает на карте маршрут для каждого рейса, поезда и другого бронирования — без необходимости включать это отдельно для каждого элемента.',
|
||||
};
|
||||
|
||||
export default settings;
|
||||
|
||||
@@ -490,6 +490,8 @@ const settings: TranslationStrings = {
|
||||
'settings.pluginActivity.columns.resource': 'Resurs',
|
||||
'settings.pluginActivity.columns.when': 'När',
|
||||
'settings.pluginActivity.columns.status': 'Resultat',
|
||||
'settings.alwaysShowRoutes': 'Visa alltid bokningsrutter',
|
||||
'settings.alwaysShowRoutesHint': 'Visar automatiskt rutten för varje flyg, tåg och annan bokning på kartan, utan att du behöver aktivera det för varje bokning för sig.',
|
||||
};
|
||||
|
||||
export default settings;
|
||||
|
||||
@@ -490,6 +490,8 @@ const settings: TranslationStrings = {
|
||||
'settings.pluginActivity.columns.resource': 'Kaynak',
|
||||
'settings.pluginActivity.columns.when': 'Ne zaman',
|
||||
'settings.pluginActivity.columns.status': 'Sonuç',
|
||||
'settings.alwaysShowRoutes': 'Rezervasyon rotalarını her zaman göster',
|
||||
'settings.alwaysShowRoutesHint': 'Haritada her uçuş, tren ve diğer rezervasyonun rotasını, tek tek açmaya gerek kalmadan otomatik olarak gösterir.',
|
||||
};
|
||||
|
||||
export default settings;
|
||||
|
||||
@@ -493,6 +493,8 @@ const settings: TranslationStrings = {
|
||||
'settings.pluginActivity.columns.resource': 'Ресурс',
|
||||
'settings.pluginActivity.columns.when': 'Коли',
|
||||
'settings.pluginActivity.columns.status': 'Результат',
|
||||
'settings.alwaysShowRoutes': 'Завжди показувати маршрути бронювань',
|
||||
'settings.alwaysShowRoutesHint': 'Автоматично показує на карті маршрут для кожного рейсу, поїзда та іншого бронювання — без потреби вмикати це окремо для кожного елемента.',
|
||||
};
|
||||
|
||||
export default settings;
|
||||
|
||||
@@ -495,6 +495,8 @@ const settings: TranslationStrings = {
|
||||
'settings.pluginActivity.columns.resource': 'Tài nguyên',
|
||||
'settings.pluginActivity.columns.when': 'Thời điểm',
|
||||
'settings.pluginActivity.columns.status': 'Kết quả',
|
||||
'settings.alwaysShowRoutes': 'Luôn hiển thị tuyến đường đặt chỗ',
|
||||
'settings.alwaysShowRoutesHint': 'Tự động hiển thị trên bản đồ tuyến đường của mỗi chuyến bay, tàu hỏa và đặt chỗ khác, không cần bật riêng từng mục.',
|
||||
};
|
||||
|
||||
export default settings;
|
||||
|
||||
@@ -464,6 +464,8 @@ const settings: TranslationStrings = {
|
||||
'settings.pluginActivity.columns.resource': '資源',
|
||||
'settings.pluginActivity.columns.when': '時間',
|
||||
'settings.pluginActivity.columns.status': '結果',
|
||||
'settings.alwaysShowRoutes': '一律顯示訂票路線',
|
||||
'settings.alwaysShowRoutesHint': '自動在地圖上顯示每個航班、火車及其他預訂的路線,不需要逐一手動開啟。',
|
||||
};
|
||||
|
||||
export default settings;
|
||||
|
||||
@@ -460,6 +460,8 @@ const settings: TranslationStrings = {
|
||||
'settings.pluginActivity.columns.resource': '资源',
|
||||
'settings.pluginActivity.columns.when': '时间',
|
||||
'settings.pluginActivity.columns.status': '结果',
|
||||
'settings.alwaysShowRoutes': '始终显示预订路线',
|
||||
'settings.alwaysShowRoutesHint': '自动在地图上显示每个航班、火车和其他预订的路线,无需逐个手动开启。',
|
||||
};
|
||||
|
||||
export default settings;
|
||||
|
||||
Reference in New Issue
Block a user