fix(tours): clear the last lint, knip and wiring findings of the follow-up

The unused day pills go, a client-only type stops being exported, the place
form context moves out of the page hook so its view model stays the first
object it returns, the place form test helpers split the stub component
from the plain setup, and four router files are formatted.
This commit is contained in:
Maurice
2026-10-20 22:13:25 +02:00
parent 786df612fd
commit b71e32cf12
12 changed files with 52 additions and 96 deletions
+1 -3
View File
@@ -4,7 +4,5 @@ import apiClient from './client';
/** The piste router: a ski tour's legs planned on the piste data the server keeps, held to the contract. */
export const skiRouteApi = {
plan: (tripId: number | string, request: SkiRouteInput, signal?: AbortSignal): Promise<SkiRouteResponse> =>
apiClient
.post(`/trips/${tripId}/ski/route`, request, { signal })
.then((r) => skiRouteResponseSchema.parse(r.data)),
apiClient.post(`/trips/${tripId}/ski/route`, request, { signal }).then((r) => skiRouteResponseSchema.parse(r.data)),
};
@@ -11,7 +11,7 @@ import { usePermissionsStore } from '../../store/permissionsStore';
import { usePluginStore } from '../../store/pluginStore';
import { resetAllStores, seedStore } from '../../../tests/helpers/store';
import { buildUser, buildTrip, buildPlace, buildCategory, buildAssignment } from '../../../tests/helpers/factories';
import { placeFormProps, seedPlaceFormStores } from '../../../tests/helpers/placeForm';
import { placeFormProps, seedPlaceFormStores } from '../../../tests/helpers/placeFormSetup';
import PlaceFormModal from './PlaceFormModal';
// CustomTimePicker as a plain text input, see TimePickerStub.
@@ -2,7 +2,7 @@
import type { TourListItem } from '@trek/shared';
import { buildAssignment, buildPlace } from '../../../tests/helpers/factories';
import { placeFormProps, seedPlaceFormStores } from '../../../tests/helpers/placeForm';
import { placeFormProps, seedPlaceFormStores } from '../../../tests/helpers/placeFormSetup';
import { render, screen } from '../../../tests/helpers/render';
import { resetAllStores } from '../../../tests/helpers/store';
import PlaceFormModal from './PlaceFormModal';
@@ -2,8 +2,8 @@ import type { SkiColourScheme, SkiFailCode, SkiFailHint, SkiRouteLeg, SkiRoutePo
import { skiRouteRepo, type SkiLegAnswer, type SkiPlanAnswer } from '../../../repo/skiRouteRepo';
import type { SkiChoice } from '../ski/skiChoice';
import { densifyLine, lineLengthM } from './tourFixedLegs';
import { isTourRoute } from './tourRouteOutcome';
import { freeLegBetween } from './tourRouteActions';
import { isTourRoute } from './tourRouteOutcome';
import { drawnTour, routeWalkingTour, type TourRoutePart, type TourRouteResult } from './tourRouting';
/**
@@ -6,11 +6,11 @@ import {
SKI_ROUTE_PRIORITIES,
skiOptionsReadBy,
skiUsesLifts,
type SkiRouteOptions as Options,
type SkiDifficulty,
type SkiDiscipline,
type SkiLiftClass,
type SkiPace,
type SkiRouteOptions as Options,
type SkiRouteOptionsInput,
type SkiRoutePriority,
} from '@trek/shared';
-42
View File
@@ -140,48 +140,6 @@ export function TourActivityBadge({ tour }: { tour: Pick<TourListItem, 'activity
)
}
/**
* The days a tour is on as small pills, "Day 2" each, the date and title in the
* tooltip. A tour on many days names the first ones and folds the rest into "+n",
* whose tooltip lists them. The pills come without a box of their own, so they
* flow on in the row of pills they are put into.
*/
export function TourDayPills({
tourDays,
shown: limit = SHOWN_TOUR_DAYS,
}: {
tourDays: readonly TourDay[]
/** How many days to name before folding the rest; a detail with room names them all. */
shown?: number
}) {
const { t, locale } = useTranslation()
if (tourDays.length === 0) return null
const fullLabel = ({ day, index }: TourDay) => dayMenuLabel(day, index, t, locale)
const shown = tourDays.length > limit ? tourDays.slice(0, limit - 1) : tourDays
const folded = tourDays.slice(shown.length)
const foldedLabel = folded.map(fullLabel).join(', ')
return (
<>
{shown.map((tourDay) => (
<Tooltip key={tourDay.day.id} label={fullLabel(tourDay)}>
<span data-testid="tour-day" aria-label={fullLabel(tourDay)} className="inline-flex">
<SoftPill icon={<CalendarDays size={10} strokeWidth={2.2} className="flex-none text-content-faint" />}>
{t('dayplan.dayN', { n: tourDay.index + 1 })}</SoftPill>
</span>
</Tooltip>
))}
{folded.length > 0 && (
<Tooltip label={foldedLabel}>
<span data-testid="tour-day" aria-label={foldedLabel} className="inline-flex">
<SoftPill className="tabular-nums">+{folded.length}
</SoftPill>
</span>
</Tooltip>
)}
</>
)
}
/**
* The days a tour is on, as the detail shows them: one box per day in the look
* of the measures below, so the days and the numbers read as one block. The
@@ -0,0 +1,18 @@
import type { TourListItem } from '@trek/shared';
import type { Assignment } from '../../types';
/**
* The place form's day context: while a place is edited, the whole day plan to check its times
* against and, TIME-02, the tour that place is, whose end the time fields work out from its start.
*/
export function placeFormContext(
editingPlace: { id: number } | null,
assignments: Record<string, Assignment[]>,
tours: readonly TourListItem[]
): { dayAssignments: Assignment[]; tour: TourListItem | null } {
if (!editingPlace) return { dayAssignments: [], tour: null };
return {
dayAssignments: Object.values(assignments).flat(),
tour: tours.find((item) => item.place_id === editingPlace.id) ?? null,
};
}
@@ -1,6 +1,4 @@
import { useCallback, useEffect, useMemo, useRef } from 'react'
import type { TourListItem } from '@trek/shared'
import type { Assignment } from '../../types'
import { usePoiExplore, type Bbox } from '../../components/Map/usePoiExplore'
import { placeActions } from '../../components/Planner/usePlaceActions'
import { tourRouteStyle } from '../../components/Tours/planner/tourRouteStyle'
@@ -17,22 +15,7 @@ import { usePlannerFocusPoints } from './usePlannerFocusPoints'
import { useTripPlanner } from './useTripPlanner'
import { useTourWorkspace } from './useTourWorkspace'
import { mapFocusKeyOf, offeredWaysOf, paddleLineOf } from './tourMapFocus'
/**
* The place form's day context: while a place is edited, the whole day plan to check its times
* against and, TIME-02, the tour that place is, whose end the time fields work out from its start.
*/
function placeFormContext(
editingPlace: { id: number } | null,
assignments: Record<string, Assignment[]>,
tours: readonly TourListItem[],
): { dayAssignments: Assignment[]; tour: TourListItem | null } {
if (!editingPlace) return { dayAssignments: [], tour: null }
return {
dayAssignments: Object.values(assignments).flat(),
tour: tours.find(item => item.place_id === editingPlace.id) ?? null,
}
}
import { placeFormContext } from './placeFormContext'
export function useTripPlannerPage() {
const planner = useTripPlanner()
+1 -1
View File
@@ -3,8 +3,8 @@ import {
type SkiColourScheme,
type SkiDiscipline,
type SkiRouteLeg,
type SkiRoutePoint,
type SkiRouteOptionsInput,
type SkiRoutePoint,
type SkiRouteResponse,
type SkiTechnique,
} from '@trek/shared';
-27
View File
@@ -1,10 +1,3 @@
import { vi } from 'vitest';
import { useAuthStore } from '../../src/store/authStore';
import { useTripStore } from '../../src/store/tripStore';
import { buildTrip, buildUser } from './factories';
import { seedStore } from './store';
/**
* Stands in for CustomTimePicker in the place form's tests: a plain text input
* instead of the portal-heavy picker. The aria props land on the input, as the
@@ -34,23 +27,3 @@ export function TimePickerStub({
/>
);
}
/** The props an open place form gets when a test sets none: a new place in trip 1. */
export const placeFormProps = {
isOpen: true,
onClose: vi.fn(),
onSave: vi.fn(),
place: null,
prefillCoords: null,
tripId: 1,
categories: [],
onCategoryCreated: vi.fn(),
assignmentId: null,
dayAssignments: [],
};
/** A signed-in user without a maps key, in trip 1: what the form reads before it renders. */
export function seedPlaceFormStores() {
seedStore(useAuthStore, { user: buildUser(), isAuthenticated: true, hasMapsKey: false });
seedStore(useTripStore, { trip: buildTrip({ id: 1 }) });
}
+26
View File
@@ -0,0 +1,26 @@
import { vi } from 'vitest';
import { useAuthStore } from '../../src/store/authStore';
import { useTripStore } from '../../src/store/tripStore';
import { buildTrip, buildUser } from './factories';
import { seedStore } from './store';
/** The props an open place form gets when a test sets none: a new place in trip 1. */
export const placeFormProps = {
isOpen: true,
onClose: vi.fn(),
onSave: vi.fn(),
place: null,
prefillCoords: null,
tripId: 1,
categories: [],
onCategoryCreated: vi.fn(),
assignmentId: null,
dayAssignments: [],
};
/** A signed-in user without a maps key, in trip 1: what the form reads before it renders. */
export function seedPlaceFormStores() {
seedStore(useAuthStore, { user: buildUser(), isAuthenticated: true, hasMapsKey: false });
seedStore(useTripStore, { trip: buildTrip({ id: 1 }) });
}
+1 -1
View File
@@ -750,7 +750,7 @@ const trekWayTagsSchema = z.object({
tags: osmTagsSchema.default({}),
});
/** One way of the ways layer, as `way(id:...);out tags;` answers it: only the keys the layer keeps. */
export type TrekWayTags = z.infer<typeof trekWayTagsSchema>;
type TrekWayTags = z.infer<typeof trekWayTagsSchema>;
const pointSchema = z.object({ lat: z.number().min(-90).max(90), lon: z.number().min(-180).max(180) });
const memberSchema = z.object({ type: z.enum(['node', 'way', 'relation']), ref: osmIdSchema, role: z.string() });