Files
TREK/client/src/components/Planner/PlacesSidebarList.tsx
T
Maurice 0dd07befa7 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.
2026-09-27 21:00:19 +02:00

86 lines
3.7 KiB
TypeScript

import { Fragment, type ReactNode } from 'react'
import EmptyState from '../shared/EmptyState'
import { MemoPlaceRow } from './PlacesSidebarRow'
import type { SidebarState } from './usePlacesSidebar'
import { usePluginViewContributions, PluginCardFooter } from '../Plugins/PluginContributions'
export function PlacesList({ header, ...S }: SidebarState & {
/**
* A block that sits above the places and scrolls WITH them.
*
* The Dawarich panel lives here rather than in a band of its own above the list. As its
* own band it could not grow: the list is the flex child that scrolls, so a panel with
* ten stays in it squeezed the list to nothing and took the rail's scrolling with it,
* and capping the panel left half its stays below a fold with no way to reach them.
* Inside the scroller it simply opens to its full height and the rail scrolls past it.
*/
header?: ReactNode
}) {
const {
filtered, scrollContainerRef, onScrollTopChange, filter, t, canEditPlaces, onAddPlace,
categories, selectedPlaceId, plannedIds, inDaySet, selectedIds, selectMode, selectedDayId,
isMobile, onPlaceClick, openContextMenu, placeMenuItems, onAssignToDay, toggleSelected, setDayPickerPlace, registerPlaceRow, tripId,
} = S
// Plugin-contributed columns/actions for the places view, keyed by place id (#plugins).
const contribFor = usePluginViewContributions('places', tripId)
return (
<div className="trek-stagger min-h-0 flex-1 overflow-y-auto px-2 pb-2" ref={scrollContainerRef} onScroll={(e) => onScrollTopChange?.((e.currentTarget as HTMLElement).scrollTop)}>
{header}
{filtered.length === 0 ? (
/* The mascot and one line, the shape every other empty state in TREK has.
The add link stays as the state's action: an empty list of places is
one of the few that has an obvious next step. */
<EmptyState
scene="search"
mood="sad"
size={92}
fill
surface="var(--bg-secondary)"
title={filter === 'unplanned' ? t('places.allPlanned') : t('places.noneFound')}
action={canEditPlaces ? (
<button
type="button"
onClick={onAddPlace}
className="text-caption text-content underline underline-offset-2 hover:text-accent"
>
{t('places.addPlace')}
</button>
) : undefined}
/>
) : (
filtered.map(place => {
const cat = categories.find(c => c.id === place.category_id)
const contributions = contribFor(place.id)
return (
<Fragment key={place.id}>
<MemoPlaceRow
place={place}
category={cat}
isSelected={place.id === selectedPlaceId}
isPlanned={plannedIds.has(place.id)}
inDay={inDaySet.has(place.id)}
isChecked={selectedIds.has(place.id)}
selectMode={selectMode}
selectedDayId={selectedDayId}
canEditPlaces={canEditPlaces}
isMobile={isMobile}
t={t}
onPlaceClick={onPlaceClick}
onContextMenu={openContextMenu}
menuItems={placeMenuItems}
onAssignToDay={onAssignToDay}
toggleSelected={toggleSelected}
setDayPickerPlace={setDayPickerPlace}
registerPlaceRow={registerPlaceRow}
/>
{contributions.length > 0 && (
<div className="px-2.5 pb-2"><PluginCardFooter items={contributions} tripId={tripId} /></div>
)}
</Fragment>
)
})
)}
</div>
)
}