diff --git a/client/src/components/Collections/CollectionFilterBar.tsx b/client/src/components/Collections/CollectionFilterBar.tsx index 01ca777ff..809cf1fa9 100644 --- a/client/src/components/Collections/CollectionFilterBar.tsx +++ b/client/src/components/Collections/CollectionFilterBar.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useRef, useState } from 'react' -import { ChevronDown, Check, Layers, Tag } from 'lucide-react' +import { ChevronDown, Check, Layers, Tag, CheckSquare } from 'lucide-react' import type { StatusFilter } from '../../store/collectionStore' import type { TranslationFn } from '../../types' import { getCategoryIcon } from '../shared/categoryIcons' @@ -69,6 +69,9 @@ interface CollectionFilterBarProps { categoryOptions: CategoryOption[] onStatusFilter: (f: StatusFilter) => void onCategoryFilter: (f: number | 'all') => void + showSelect: boolean + selectMode: boolean + onToggleSelect: () => void t: TranslationFn } @@ -78,7 +81,8 @@ interface CollectionFilterBarProps { * Custom compact dropdowns so they barely take any space. */ export default function CollectionFilterBar({ - statusFilter, counts, categoryFilter, categoryOptions, onStatusFilter, onCategoryFilter, t, + statusFilter, counts, categoryFilter, categoryOptions, onStatusFilter, onCategoryFilter, + showSelect, selectMode, onToggleSelect, t, }: CollectionFilterBarProps): React.ReactElement { const statusOpts: Opt[] = [ { key: 'all', label: t('common.all'), count: counts.all }, @@ -103,6 +107,11 @@ export default function CollectionFilterBar({ {categoryOptions.length > 0 && ( onCategoryFilter(k as number | 'all')} lead={} /> )} + {showSelect && ( + + )} ) } diff --git a/client/src/components/Collections/MoveToListModal.tsx b/client/src/components/Collections/MoveToListModal.tsx new file mode 100644 index 000000000..b6683ad1e --- /dev/null +++ b/client/src/components/Collections/MoveToListModal.tsx @@ -0,0 +1,90 @@ +import React, { useMemo, useState } from 'react' +import { Search, ArrowRight, Copy, Loader2, Bookmark } from 'lucide-react' +import Modal from '../shared/Modal' +import type { Collection } from '@trek/shared' +import type { TranslationFn } from '../../types' + +interface MoveToListModalProps { + mode: 'move' | 'copy' + /** Candidate target lists (owned, excluding the current one). */ + lists: Collection[] + /** Number of selected places. */ + count: number + onPick: (targetId: number) => Promise | void + onClose: () => void + t: TranslationFn +} + +/** + * Target-list picker for moving or duplicating the selected places into another + * of the user's lists. `mode` only changes the wording + the trailing icon; the + * action itself is the parent's onPick. + */ +export default function MoveToListModal({ mode, lists, count, onPick, onClose, t }: MoveToListModalProps): React.ReactElement { + const [search, setSearch] = useState('') + const [busy, setBusy] = useState(null) + + const filtered = useMemo(() => { + const q = search.trim().toLowerCase() + return q ? lists.filter(l => l.name.toLowerCase().includes(q)) : lists + }, [lists, search]) + + const pick = async (id: number) => { + if (busy != null) return + setBusy(id) + try { await onPick(id) } finally { setBusy(null) } + } + + const Trailing = mode === 'move' ? ArrowRight : Copy + + return ( + +
+ {lists.length > 3 && ( +
+ + setSearch(e.target.value)} + placeholder={t('collections.copyToTripSearch')} + className="w-full pl-8 pr-3 py-2 rounded-lg border border-edge bg-surface-input text-content text-[13px] outline-none focus:border-accent" + /> +
+ )} + {filtered.length === 0 ? ( +

{t('collections.noOtherLists')}

+ ) : ( +
+ {filtered.map(list => { + const isBusy = busy === list.id + return ( + + ) + })} +
+ )} +
+
+ ) +} diff --git a/client/src/pages/CollectionsPage.tsx b/client/src/pages/CollectionsPage.tsx index 1edc05a11..6319d829d 100644 --- a/client/src/pages/CollectionsPage.tsx +++ b/client/src/pages/CollectionsPage.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { List as ListIcon, Map as MapIcon, Search, Bookmark, CheckSquare, X, Trash2, Copy, Plus } from 'lucide-react' +import { List as ListIcon, Map as MapIcon, Search, Bookmark, CheckCheck, X, Trash2, Copy, CopyPlus, FolderInput, Plus } from 'lucide-react' import Navbar from '../components/Layout/Navbar' import Modal from '../components/shared/Modal' import ListsRail from '../components/Collections/ListsRail' @@ -9,6 +9,7 @@ import CollectionList from '../components/Collections/CollectionList' import CollectionFilterBar from '../components/Collections/CollectionFilterBar' import CollectionMapPanel from '../components/Collections/CollectionMapPanel' import CopyToTripModal from '../components/Collections/CopyToTripModal' +import MoveToListModal from '../components/Collections/MoveToListModal' import ShareCollectionModal from '../components/Collections/ShareCollectionModal' import AddPlaceToCollectionModal from '../components/Collections/AddPlaceToCollectionModal' import CollectionPlaceDetail from '../components/Collections/CollectionPlaceDetail' @@ -105,6 +106,9 @@ export default function CollectionsPage(): React.ReactElement { categoryOptions={c.categoryOptions} onStatusFilter={c.setStatusFilter} onCategoryFilter={c.setCategoryFilter} + showSelect={showSelect} + selectMode={c.selectMode} + onToggleSelect={() => c.setSelectMode(!c.selectMode)} t={t} /> ) : null @@ -204,17 +208,6 @@ export default function CollectionsPage(): React.ReactElement { )} - {showSelect && ( - - )} {canAddPlace && ( {t('collections.selectedCount', { count: c.selectedIds.length })}
- - + +