mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-20 14:06:27 +00:00
fcabb4c645
The MapLibre layer was already data-driven, so it only needed the feature property filled. Leaflet needed more work: the GPX polyline passed its style as bare color/weight props, and react-leaflet only calls setStyle when the pathOptions reference changes — a recoloured track would have kept its mount-time colour until the next reload. Every other vector layer in here already uses pathOptions. A track with a colour gets a white casing so it survives satellite imagery and dark basemaps. The casings live in their own pane below the lines: Leaflet stacks paths in insertion order, so drawn inline they end up over the line of a track that was added earlier. They are always mounted and hidden by opacity, otherwise giving a track a colour remounts the path into the wrong position. Tracks are clickable now — 3.5px is not a target, so the hit area is a transparent 14px line on top. bubblingMouseEvents is off on it: paths bubble to the map by default (markers do not), and the map's own click handler would clear the selection the track just made. The GL side needs the mirror image of that, a guard so a click on a cluster bubble over a track still belongs to the cluster.
303 lines
6.5 KiB
TypeScript
303 lines
6.5 KiB
TypeScript
/**
|
|
* Pure data builder functions for frontend tests.
|
|
* These return typed objects matching interfaces in src/types.ts.
|
|
* They do NOT touch a database.
|
|
*/
|
|
|
|
import type {
|
|
User,
|
|
Trip,
|
|
Day,
|
|
Place,
|
|
Assignment,
|
|
DayNote,
|
|
PackingItem,
|
|
TodoItem,
|
|
BudgetItem,
|
|
Reservation,
|
|
TripFile,
|
|
Tag,
|
|
Category,
|
|
Settings,
|
|
AppConfig,
|
|
} from '../../src/types';
|
|
|
|
// ── Counters ──────────────────────────────────────────────────────────────────
|
|
|
|
let _seq = 0;
|
|
function next(): number {
|
|
return ++_seq;
|
|
}
|
|
|
|
// ── InAppNotification (local interface, not in types.ts) ──────────────────────
|
|
|
|
export interface InAppNotification {
|
|
id: number;
|
|
type: string;
|
|
message: string;
|
|
read: boolean;
|
|
created_at: string;
|
|
trip_id?: number | null;
|
|
}
|
|
|
|
// ── Builders ──────────────────────────────────────────────────────────────────
|
|
|
|
export function buildUser(overrides: Partial<User> = {}): User {
|
|
const id = next();
|
|
return {
|
|
id,
|
|
username: `user${id}`,
|
|
email: `user${id}@example.com`,
|
|
role: 'user',
|
|
avatar_url: null,
|
|
maps_api_key: null,
|
|
created_at: '2025-01-01T00:00:00.000Z',
|
|
mfa_enabled: false,
|
|
must_change_password: false,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
export function buildAdmin(overrides: Partial<User> = {}): User {
|
|
return buildUser({ role: 'admin', ...overrides });
|
|
}
|
|
|
|
export function buildTrip(overrides: Partial<Trip> = {}): Trip {
|
|
const id = next();
|
|
return {
|
|
id,
|
|
user_id: 1,
|
|
title: `Trip ${id}`,
|
|
description: null,
|
|
start_date: '2025-06-01',
|
|
end_date: '2025-06-05',
|
|
currency: 'EUR',
|
|
cover_image: null,
|
|
is_archived: 0,
|
|
reminder_days: 7,
|
|
created_at: '2025-01-01T00:00:00.000Z',
|
|
updated_at: '2025-01-01T00:00:00.000Z',
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
export function buildDay(overrides: Partial<Day> = {}): Day {
|
|
const id = next();
|
|
return {
|
|
id,
|
|
trip_id: 1,
|
|
date: '2025-06-01',
|
|
title: null,
|
|
notes: null,
|
|
assignments: [],
|
|
notes_items: [],
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
export function buildPlace(overrides: Partial<Place> = {}): Place {
|
|
const id = next();
|
|
return {
|
|
id,
|
|
trip_id: 1,
|
|
name: `Place ${id}`,
|
|
description: null,
|
|
lat: 48.8566,
|
|
lng: 2.3522,
|
|
address: null,
|
|
category_id: null,
|
|
price: null,
|
|
currency: null,
|
|
image_url: null,
|
|
google_place_id: null,
|
|
osm_id: null,
|
|
route_geometry: null,
|
|
route_color: null,
|
|
place_time: null,
|
|
end_time: null,
|
|
duration_minutes: 60,
|
|
notes: null,
|
|
transport_mode: 'walking',
|
|
website: null,
|
|
phone: null,
|
|
created_at: '2025-01-01T00:00:00.000Z',
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
export function buildAssignment(overrides: Partial<Assignment> = {}): Assignment {
|
|
const id = next();
|
|
const place = overrides.place ?? buildPlace();
|
|
return {
|
|
id,
|
|
day_id: 1,
|
|
place_id: place.id,
|
|
order_index: 0,
|
|
notes: null,
|
|
place,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
export function buildDayNote(overrides: Partial<DayNote> = {}): DayNote {
|
|
const id = next();
|
|
return {
|
|
id,
|
|
day_id: 1,
|
|
text: 'Test note',
|
|
time: null,
|
|
icon: null,
|
|
sort_order: 0,
|
|
created_at: '2025-01-01T00:00:00.000Z',
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
export function buildPackingItem(overrides: Partial<PackingItem> = {}): PackingItem {
|
|
const id = next();
|
|
return {
|
|
id,
|
|
trip_id: 1,
|
|
name: `Packing item ${id}`,
|
|
category: null,
|
|
checked: 0,
|
|
sort_order: 0,
|
|
quantity: 1,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
export function buildTodoItem(overrides: Partial<TodoItem> = {}): TodoItem {
|
|
const id = next();
|
|
return {
|
|
id,
|
|
trip_id: 1,
|
|
name: `Todo ${id}`,
|
|
category: null,
|
|
checked: 0,
|
|
sort_order: 0,
|
|
due_date: null,
|
|
description: null,
|
|
assigned_user_id: null,
|
|
priority: 0,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
export function buildBudgetItem(overrides: Partial<BudgetItem> = {}): BudgetItem {
|
|
const id = next();
|
|
return {
|
|
id,
|
|
trip_id: 1,
|
|
category: 'Other',
|
|
name: `Budget item ${id}`,
|
|
total_price: 100,
|
|
persons: 1,
|
|
days: null,
|
|
note: null,
|
|
sort_order: 0,
|
|
members: [],
|
|
expense_date: null,
|
|
created_at: '2025-01-01T00:00:00.000Z',
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
export function buildReservation(overrides: Partial<Reservation> = {}): Reservation {
|
|
const id = next();
|
|
return {
|
|
id,
|
|
trip_id: 1,
|
|
title: `Reservation ${id}`,
|
|
type: 'restaurant',
|
|
status: 'confirmed',
|
|
reservation_time: null,
|
|
reservation_end_time: null,
|
|
location: null,
|
|
confirmation_number: null,
|
|
notes: null,
|
|
created_at: '2025-01-01T00:00:00.000Z',
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
export function buildTripFile(overrides: Partial<TripFile> = {}): TripFile {
|
|
const id = next();
|
|
return {
|
|
id,
|
|
trip_id: 1,
|
|
filename: 'test.pdf',
|
|
original_name: 'test.pdf',
|
|
mime_type: 'application/pdf',
|
|
url: `/api/trips/1/files/${id}/download`,
|
|
created_at: '2025-01-01T00:00:00.000Z',
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
export function buildTag(overrides: Partial<Tag> = {}): Tag {
|
|
const id = next();
|
|
return {
|
|
id,
|
|
name: `Tag ${id}`,
|
|
color: '#ff0000',
|
|
user_id: 1,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
export function buildCategory(overrides: Partial<Category> = {}): Category {
|
|
const id = next();
|
|
return {
|
|
id,
|
|
name: `Category ${id}`,
|
|
color: '#6366f1',
|
|
icon: 'restaurant',
|
|
user_id: 1,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
export function buildSettings(overrides: Partial<Settings> = {}): Settings {
|
|
return {
|
|
map_tile_url: '',
|
|
dark_mode: false,
|
|
default_currency: 'USD',
|
|
language: 'en',
|
|
temperature_unit: 'fahrenheit',
|
|
time_format: '12h',
|
|
show_place_description: false,
|
|
blur_booking_codes: false,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
export function buildInAppNotification(overrides: Partial<InAppNotification> = {}): InAppNotification {
|
|
const id = next();
|
|
return {
|
|
id,
|
|
type: 'trip_invite',
|
|
message: `Notification ${id}`,
|
|
read: false,
|
|
created_at: '2025-01-01T00:00:00.000Z',
|
|
trip_id: null,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
export function buildAppConfig(overrides: Partial<AppConfig> = {}): AppConfig {
|
|
return {
|
|
has_users: true,
|
|
allow_registration: true,
|
|
demo_mode: false,
|
|
oidc_configured: false,
|
|
oidc_only_mode: false,
|
|
password_login: true,
|
|
password_registration: true,
|
|
oidc_login: true,
|
|
oidc_registration: true,
|
|
env_override_oidc_only: false,
|
|
...overrides,
|
|
};
|
|
}
|