mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-24 07:53:09 +00:00
feat(planner): bring the plan tab and every trip dialog into the new look
The Plan tab was the last trip tab still in the old design. It now reads like Bookings, Costs, Lists, Files and Collab, without losing a function: - Day plan: each day is a card with a head band (day tile with weather, stay and rental pills, a "+" menu for place, stay, transport, transit and note), rows with "..." menus, white badges and a white route bar. - Places column: add, search and filters in three rows, a show dropdown, category and rating filters, and a selection bar at the foot. - Place inspector, day details and the stay editor on the shared dialog frame; the place form in the editor look with the name in the head band. - Clicking a booking or transport anywhere on the plan opens the booking detail popup first; Edit leads to the editor. The popup is one host shared with the Bookings and Transports tabs. - Files and Collab dialogs, notes, polls, links and chat moved to the same frame; shared DialogShell and dialogParts hold the building blocks. - The map hover card shows the place photo and follows the theme. Every new and reworded string is translated into all locales, and a few older English leftovers (help center, cost split, import hints) with them. Phone screens keep their own look.
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { isAmapUrl, isGoogleMapsUrl, isMapUrl } from './PlaceFormModal.helpers'
|
||||
import {
|
||||
DEFAULT_FORM, endsBeforeStart, findDuplicatePlace, isAmapUrl, isGoogleMapsUrl, isMapUrl, parseCoordinatePair,
|
||||
timeCollisions,
|
||||
} from './PlaceFormModal.helpers'
|
||||
|
||||
describe('isGoogleMapsUrl', () => {
|
||||
it('accepts the short share hosts', () => {
|
||||
@@ -61,3 +64,85 @@ describe('isMapUrl', () => {
|
||||
expect(isMapUrl('https://example.com/maps')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('findDuplicatePlace', () => {
|
||||
const form = (over: Partial<typeof DEFAULT_FORM>) => ({ ...DEFAULT_FORM, ...over })
|
||||
|
||||
it('matches a name case- and space-insensitively', () => {
|
||||
expect(findDuplicatePlace(form({ name: ' louvre ' }), [{ name: 'Louvre' }])).toEqual({ name: 'Louvre' })
|
||||
})
|
||||
|
||||
it('matches a shared Google place id whatever the name', () => {
|
||||
const hit = { name: 'Other', google_place_id: 'gp-1' }
|
||||
expect(findDuplicatePlace(form({ name: 'New', google_place_id: 'gp-1' }), [hit])).toBe(hit)
|
||||
})
|
||||
|
||||
it('matches coordinates within about eleven metres, and not beyond', () => {
|
||||
const near = { name: 'Tour Eiffel', lat: 48.8584, lng: 2.2945 }
|
||||
expect(findDuplicatePlace(form({ name: 'Iron Lady', lat: '48.85845', lng: '2.29455' }), [near])).toBe(near)
|
||||
expect(findDuplicatePlace(form({ name: 'Iron Lady', lat: '48.8594', lng: '2.2945' }), [near])).toBeNull()
|
||||
})
|
||||
|
||||
it('a stop on a drive ignores the name and compares the OSM object instead', () => {
|
||||
const aral = { name: 'Aral', osm_id: 'node:1' }
|
||||
const rules = { byName: false, byOsmId: true }
|
||||
expect(findDuplicatePlace(form({ name: 'Aral' }), [aral], rules)).toBeNull()
|
||||
expect(findDuplicatePlace(form({ name: 'Tankstelle', osm_id: 'node:1' }), [aral], rules)).toBe(aral)
|
||||
// The ordinary add place never looks at the OSM object.
|
||||
expect(findDuplicatePlace(form({ name: 'Tankstelle', osm_id: 'node:1' }), [aral])).toBeNull()
|
||||
})
|
||||
|
||||
it('an empty list or a missing one finds nothing', () => {
|
||||
expect(findDuplicatePlace(form({ name: 'Louvre' }), [])).toBeNull()
|
||||
expect(findDuplicatePlace(form({ name: 'Louvre' }), null as never)).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('parseCoordinatePair', () => {
|
||||
it('splits a pair on a comma, a semicolon or a space', () => {
|
||||
expect(parseCoordinatePair('48.8566, 2.3522')).toEqual(['48.8566', '2.3522'])
|
||||
expect(parseCoordinatePair(' -33.86;151.2 ')).toEqual(['-33.86', '151.2'])
|
||||
expect(parseCoordinatePair('35.68 139.77')).toEqual(['35.68', '139.77'])
|
||||
})
|
||||
|
||||
it('leaves anything else to the field', () => {
|
||||
expect(parseCoordinatePair('48.8566')).toBeNull()
|
||||
expect(parseCoordinatePair('not a coordinate')).toBeNull()
|
||||
expect(parseCoordinatePair('48.8, 2.3, 7')).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('endsBeforeStart', () => {
|
||||
it('only compares two complete clocks', () => {
|
||||
expect(endsBeforeStart('14:00', '13:00')).toBe(true)
|
||||
expect(endsBeforeStart('14:00', '14:00')).toBe(true)
|
||||
expect(endsBeforeStart('14:00', '15:00')).toBe(false)
|
||||
expect(endsBeforeStart('14:00', '1')).toBe(false)
|
||||
expect(endsBeforeStart('', '13:00')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('timeCollisions', () => {
|
||||
const visit = (id: number, day_id: number, place_time: string | null, end_time: string | null = null, name = `P${id}`) =>
|
||||
({ id, day_id, place: { name, place_time, end_time } }) as never
|
||||
|
||||
it('lists the overlapping visits of the same day only', () => {
|
||||
const day = [visit(1, 5, '12:30', '13:30'), visit(2, 5, '13:00', '14:00'), visit(3, 6, '13:00', '14:00'), visit(4, 5, null)]
|
||||
expect(timeCollisions(1, day, '12:30', '13:30').map((a: { id: number }) => a.id)).toEqual([2])
|
||||
})
|
||||
|
||||
it('an open end is a point in time, and touching ends do not overlap', () => {
|
||||
const day = [visit(1, 5, '11:30'), visit(2, 5, '11:00', '12:00'), visit(3, 5, '11:00'), visit(4, 5, '12:00', '13:00')]
|
||||
expect(timeCollisions(1, day, '11:30', '').map((a: { id: number }) => a.id)).toEqual([2])
|
||||
// 12:00 to 13:00 starts where this one ends, and the point at 11:00 is where it starts.
|
||||
expect(timeCollisions(1, day, '11:00', '12:00').map((a: { id: number }) => a.id)).toEqual([2])
|
||||
expect(timeCollisions(1, day, '11:00', '12:30').map((a: { id: number }) => a.id)).toEqual([2, 4])
|
||||
})
|
||||
|
||||
it('says nothing without an assignment, a start, or the assignment in the list', () => {
|
||||
const day = [visit(1, 5, '12:00', '13:00'), visit(2, 5, '12:30', '13:30')]
|
||||
expect(timeCollisions(null, day, '12:00', '13:00')).toEqual([])
|
||||
expect(timeCollisions(1, day, '', '13:00')).toEqual([])
|
||||
expect(timeCollisions(99, day, '12:00', '13:00')).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user