feat: Phase 1 — pinnedFlowsStore, pagination hook, cached quota hook, sidebar refactor

- Add pin() to pinnedFlowsApi
- Create pinnedFlowsStore (Zustand) — single source of truth for pin state
- Add dashboardMyFlowsView preference to userPreferencesStore
- Create usePaginationParams hook (URL-synced)
- Create useCachedQuota hook (5-min TTL)
- Sidebar uses pinnedFlowsStore instead of local state

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Michael Chihlas
2026-02-20 21:24:34 -05:00
parent 045fdd6200
commit 67543dfb8f
6 changed files with 221 additions and 18 deletions

View File

@@ -2,32 +2,36 @@ import { useEffect, useState } from 'react'
import { LayoutGrid, Box, PenLine, Clock, FileText, Bookmark, BarChart3, Settings, PanelLeftClose, PanelLeftOpen, MessageSquareText } from 'lucide-react'
import { cn } from '@/lib/utils'
import { useUserPreferencesStore } from '@/store/userPreferencesStore'
import { usePinnedFlowsStore } from '@/store/pinnedFlowsStore'
import { PinnedFlowsSection } from '@/components/sidebar/PinnedFlowsSection'
import { NavItem } from './NavItem'
import { sessionsApi, treesApi } from '@/api'
import { pinnedFlowsApi } from '@/api/pinnedFlows'
import type { PinnedFlow } from '@/api/pinnedFlows'
import { toast } from '@/lib/toast'
export function Sidebar() {
const sidebarCollapsed = useUserPreferencesStore(s => s.sidebarCollapsed)
const toggleSidebar = useUserPreferencesStore(s => s.toggleSidebar)
const pinnedItems = usePinnedFlowsStore((s) => s.items)
const loadPinned = usePinnedFlowsStore((s) => s.load)
const unpinFlow = usePinnedFlowsStore((s) => s.unpin)
const [activeSessionCount, setActiveSessionCount] = useState(0)
const [pinnedFlows, setPinnedFlows] = useState<PinnedFlow[]>([])
const [treeCounts, setTreeCounts] = useState({ total: 0, troubleshooting: 0, procedural: 0, maintenance: 0 })
// Load pinned flows on mount
useEffect(() => {
loadPinned()
}, [loadPinned])
// Fetch sidebar data on mount
useEffect(() => {
const fetchData = async () => {
try {
const [activeSessions, allTrees, pinnedData] = await Promise.all([
const [activeSessions, allTrees] = await Promise.all([
sessionsApi.list({ completed: false, size: 50 }).catch(() => []),
treesApi.list({ sort_by: 'name' }).catch(() => []),
pinnedFlowsApi.list().catch(() => ({ items: [], count: 0 })),
])
setActiveSessionCount(activeSessions.length)
setPinnedFlows(pinnedData.items)
const total = allTrees.length
const troubleshooting = allTrees.filter(t => t.tree_type === 'troubleshooting').length
@@ -41,16 +45,6 @@ export function Sidebar() {
fetchData()
}, [])
const handleUnpin = async (treeId: string) => {
try {
await pinnedFlowsApi.unpin(treeId)
setPinnedFlows(prev => prev.filter(f => f.tree_id !== treeId))
toast.success('Unpinned from sidebar')
} catch {
toast.error('Failed to unpin flow')
}
}
const handleSidebarWheel = (e: React.WheelEvent<HTMLElement>) => {
const sidebar = e.currentTarget
const canSidebarScroll = sidebar.scrollHeight > sidebar.clientHeight
@@ -89,7 +83,7 @@ export function Sidebar() {
) : (
<>
{/* Pinned Flows */}
<PinnedFlowsSection flows={pinnedFlows} onUnpin={handleUnpin} />
<PinnedFlowsSection flows={pinnedItems} onUnpin={unpinFlow} />
<div className="border-b border-[hsl(var(--border-subtle))]" />