import { useState } from "react"; import { useTranslation } from "react-i18next"; import { BookmarkPlus, GripVertical, Play, Star, Trash2 } from "lucide-react"; import { closestCenter, DndContext, PointerSensor, useSensor, useSensors, type DragEndEvent, } from "@dnd-kit/core"; import { arrayMove, SortableContext, useSortable, verticalListSortingStrategy, } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; import type { SavedView } from "../lib/api"; import Modal from "./Modal"; import { useSavedViews, asFilters } from "./SavedViewsProvider"; import { useActiveFilters } from "../lib/useActiveFilters"; // Master–detail manager for saved ("personal") views: the left column lists the views (drag to // reorder) plus a "save the current filters" input; the right column shows the selected view's name, // what filters it captures, its default flag, apply, and delete. Opened from the filter panel's title // or the saved-views popover; all mutations flow through SavedViewsProvider. function ViewRow({ view, selected, active, onSelect, }: { view: SavedView; selected: boolean; active: boolean; onSelect: () => void; }) { const { t } = useTranslation(); const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: view.id, }); return (
{view.is_default && } {active && }
); } export default function PersonalViewsModal() { const { t } = useTranslation(); const { views, activeViewId, apply, create, rename, toggleDefault, remove, reorder, closeModal } = useSavedViews(); const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 4 } })); const [selectedId, setSelectedId] = useState(activeViewId ?? views[0]?.id ?? null); const selected = views.find((v) => v.id === selectedId) ?? null; const [draftName, setDraftName] = useState(""); // The selected view's captured filters, as the same human labels the toolbar chip row shows. const captured = useActiveFilters( selected ? asFilters(selected.filters) : ({ tags: [], channelIds: [] } as never), () => {}, ); async function onDragEnd(e: DragEndEvent) { const { active, over } = e; if (!over || active.id === over.id) return; const ids = views.map((v) => v.id); const next = arrayMove(ids, ids.indexOf(active.id as number), ids.indexOf(over.id as number)); await reorder(next); } async function onCreate() { const name = draftName.trim(); if (!name) return; if (await create(name)) setDraftName(""); } return (
{/* Left: the list + create */}
{views.length === 0 && (

{t("views.empty")}

)} v.id)} strategy={verticalListSortingStrategy} > {views.map((v) => ( setSelectedId(v.id)} /> ))}
setDraftName(e.target.value)} onKeyDown={(e) => e.key === "Enter" && onCreate()} placeholder={t("views.namePlaceholder")} className="min-w-0 flex-1 rounded-lg border border-border bg-surface px-2 py-1.5 text-sm focus:border-accent focus:outline-hidden focus:ring-2 focus:ring-accent/30" />
{/* Right: the selected view's detail */} {selected ? (
{ const next = e.target.value.trim(); if (next && next !== selected.name) void rename(selected.id, next); }} className="rounded-lg border border-border bg-surface px-3 py-2 text-sm font-medium focus:border-accent focus:outline-hidden focus:ring-2 focus:ring-accent/30" />
{t("views.capturedFilters")}
{captured.length === 0 ? (

{t("views.capturedNone")}

) : (
{captured.map((f) => ( {f.label} ))}
)}
) : (
{t("views.selectPrompt")}
)}
); }