mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-23 23:46:35 +00:00
19ae607f54
A third scope, home, next to trip and journey: threads that name neither a trip nor a journey (the CHECK now only forbids both), reached under /api/assistant by their owner only. A home turn reads the user's trips through list_trips into a short card (under way, next, later, behind, undated), offers every read (a trip's on the id the model names, checked by the tool with the user's rights) and only the writes that take no trip or journey (atlas, vacation planner, collections, tour library, settings), all held as change cards. open_trip_chat answers with a card that goes on in that trip's own chat with the question waiting in its composer. On the desktop the window opens on the home chat outside a trip; its head lists the trips to switch into and a switched trip chat has the way back. On the phone the home chat opens full screen from the dashboard head and the dock's More list, and the card opens the trip with its chat. Both run on useAssistantHome.
68 lines
3.1 KiB
TypeScript
68 lines
3.1 KiB
TypeScript
import { useCallback, useMemo } from 'react';
|
|
import { useLocation, useMatch, useNavigate } from 'react-router';
|
|
|
|
import { rememberListsSubTab } from '../../hooks/useListsSubTab';
|
|
import { useAssistantStore } from '../../store/assistantStore';
|
|
import { useTripStore } from '../../store/tripStore';
|
|
import AssistantWindow from './AssistantWindow';
|
|
import { plannerSearchOf } from './assistantShowOnMap';
|
|
import type { EntityTarget } from './cards/cardTypes';
|
|
|
|
/** The trip a route under /trips/:id shows, or null anywhere else. */
|
|
function useRouteTripId(): number | null {
|
|
const match = useMatch('/trips/:id/*');
|
|
const id = Number(match?.params.id);
|
|
return Number.isInteger(id) && id > 0 ? id : null;
|
|
}
|
|
|
|
/**
|
|
* The desktop assistant, mounted once above the routes so a conversation
|
|
* keeps going from page to page. Inside a trip it is about that trip, outside
|
|
* one it is the home chat about all of them (AssistantWindow); what a
|
|
* card or an applied change opens goes through the planner's links: "Show on
|
|
* map" opens the planner on the change (`?place=`, `?day=`), the same way a
|
|
* notification opens a place, a tab of the trip opens as `?tab=` (a list's
|
|
* own tab remembered first), and another page is simply navigated to. A day
|
|
* picked in the chat (its weather) selects that day the same way, and "On
|
|
* map" on a card brings the planner's map back first from a tab without one.
|
|
*/
|
|
export default function AssistantHost() {
|
|
const navigate = useNavigate();
|
|
const { pathname } = useLocation();
|
|
const tripId = useRouteTripId();
|
|
// The title is there once the planner has loaded the trip; the window waits for nothing else.
|
|
const title = useTripStore((s) => (s.trip && s.trip.id === tripId ? s.trip.title : ''));
|
|
const trip = useMemo(() => (tripId === null ? null : { id: tripId, title }), [tripId, title]);
|
|
|
|
const openPlanner = useCallback(
|
|
(search: string) => {
|
|
if (tripId === null) return;
|
|
const planner = `/trips/${tripId}`;
|
|
void navigate({ pathname: planner, search }, { replace: pathname === planner });
|
|
},
|
|
[navigate, pathname, tripId]
|
|
);
|
|
const open = useCallback(
|
|
(target: EntityTarget) => {
|
|
if (target.kind === 'page') {
|
|
void navigate(target.path);
|
|
return;
|
|
}
|
|
if (target.kind === 'tab' && target.tab === 'listen' && tripId !== null) {
|
|
if (target.sub === 'packing' || target.sub === 'todo') rememberListsSubTab(tripId, target.sub);
|
|
}
|
|
openPlanner(plannerSearchOf(target));
|
|
},
|
|
[navigate, openPlanner, tripId]
|
|
);
|
|
const focusDay = useCallback((dayId: number) => openPlanner(`?day=${dayId}`), [openPlanner]);
|
|
// "On map" from a tab without a map (Costs, Lists, ...) first brings the plan's map back; the Tours tab
|
|
// shows the route on its own map. A map on screen tells the assistant its view, which is how it is known.
|
|
const revealMap = useCallback(() => {
|
|
if (useAssistantStore.getState().mapView?.tripId === tripId) return;
|
|
openPlanner('?tab=plan');
|
|
}, [openPlanner, tripId]);
|
|
|
|
return <AssistantWindow trip={trip} onOpen={open} onFocusDay={focusDay} onRevealMap={revealMap} />;
|
|
}
|