From b81eae8eaf54843c063d44a79d03aebec586f78f Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 3 Jan 2026 04:58:00 +0000 Subject: [PATCH 1/2] fix: Improve queue drawer ordering and scroll to current climb - Reorder queue display to show current and upcoming items at the top - Move history items (completed climbs) below a "History" divider - Auto-scroll to the current climb when the drawer opens --- .../queue-control/queue-control-bar.tsx | 18 +++- .../components/queue-control/queue-list.tsx | 94 +++++++++++++++---- 2 files changed, 89 insertions(+), 23 deletions(-) diff --git a/packages/web/app/components/queue-control/queue-control-bar.tsx b/packages/web/app/components/queue-control/queue-control-bar.tsx index cc1d7be4..2e09b909 100644 --- a/packages/web/app/components/queue-control/queue-control-bar.tsx +++ b/packages/web/app/components/queue-control/queue-control-bar.tsx @@ -1,5 +1,5 @@ 'use client'; -import React, { useState, useCallback } from 'react'; +import React, { useState, useCallback, useRef } from 'react'; import { Button, Row, Col, Card, Drawer, Space, Popconfirm } from 'antd'; import { SyncOutlined, DeleteOutlined, ExpandOutlined, FastForwardOutlined, FastBackwardOutlined } from '@ant-design/icons'; import { track } from '@vercel/analytics'; @@ -12,7 +12,7 @@ import { constructPlayUrlWithSlugs, constructClimbViewUrlWithSlugs, parseBoardRo import { BoardRouteParameters, BoardRouteParametersWithUuid } from '@/app/lib/types'; import PreviousClimbButton from './previous-climb-button'; import { BoardName, BoardDetails, Angle } from '@/app/lib/types'; -import QueueList from './queue-list'; +import QueueList, { QueueListHandle } from './queue-list'; import { TickButton } from '../logbook/tick-button'; import ClimbThumbnail from '../climb-card/climb-thumbnail'; import ClimbTitle from '../climb-card/climb-title'; @@ -38,6 +38,17 @@ const QueueControlBar: React.FC = ({ boardDetails, angle }: Que const params = useParams(); const searchParams = useSearchParams(); const router = useRouter(); + const queueListRef = useRef(null); + + // Scroll to current climb when drawer finishes opening + const handleDrawerOpenChange = useCallback((open: boolean) => { + if (open) { + // Small delay to ensure the drawer content is rendered + setTimeout(() => { + queueListRef.current?.scrollToCurrentClimb(); + }, 100); + } + }, []); const isViewPage = pathname.includes('/view/'); const isListPage = pathname.includes('/list'); @@ -354,6 +365,7 @@ const QueueControlBar: React.FC = ({ boardDetails, angle }: Que height="70%" // Adjust as per design preference open={isQueueOpen} onClose={toggleQueueDrawer} + afterOpenChange={handleDrawerOpenChange} styles={{ body: { padding: 0 } }} extra={ queue.length > 0 && ( @@ -371,7 +383,7 @@ const QueueControlBar: React.FC = ({ boardDetails, angle }: Que ) } > - setIsQueueOpen(false)} /> + setIsQueueOpen(false)} /> ); diff --git a/packages/web/app/components/queue-control/queue-list.tsx b/packages/web/app/components/queue-control/queue-list.tsx index f355db4b..2ab0eac7 100644 --- a/packages/web/app/components/queue-control/queue-list.tsx +++ b/packages/web/app/components/queue-control/queue-list.tsx @@ -1,5 +1,5 @@ 'use client'; -import React, { useEffect, useState, useCallback, useRef } from 'react'; +import React, { useEffect, useState, useCallback, useRef, useImperativeHandle, forwardRef } from 'react'; import { Divider, Row, Col, Button, Flex, Drawer, Space, Typography, Skeleton } from 'antd'; import { PlusOutlined, LoginOutlined } from '@ant-design/icons'; import { useQueueContext } from '../graphql-queue'; @@ -18,12 +18,16 @@ import AuthModal from '../auth/auth-modal'; const { Text, Paragraph } = Typography; +export type QueueListHandle = { + scrollToCurrentClimb: () => void; +}; + type QueueListProps = { boardDetails: BoardDetails; onClimbNavigate?: () => void; }; -const QueueList: React.FC = ({ boardDetails, onClimbNavigate }) => { +const QueueList = forwardRef(({ boardDetails, onClimbNavigate }, ref) => { const { viewOnlyMode, currentClimbQueueItem, @@ -46,6 +50,18 @@ const QueueList: React.FC = ({ boardDetails, onClimbNavigate }) const [tickClimb, setTickClimb] = useState(null); const [showAuthModal, setShowAuthModal] = useState(false); + // Ref for scrolling to current climb + const currentClimbRef = useRef(null); + + // Expose scroll method to parent via ref + useImperativeHandle(ref, () => ({ + scrollToCurrentClimb: () => { + if (currentClimbRef.current) { + currentClimbRef.current.scrollIntoView({ behavior: 'smooth', block: 'start' }); + } + }, + })); + const handleTickClick = useCallback((climb: Climb) => { setTickClimb(climb); setTickDrawerVisible(true); @@ -129,31 +145,67 @@ const QueueList: React.FC = ({ boardDetails, onClimbNavigate }) }; }, [handleObserver, viewOnlyMode]); + // Find the index of the current climb in the queue + const currentIndex = queue.findIndex((item) => item.uuid === currentClimbQueueItem?.uuid); + + // Split queue into upcoming (current + future) and history (past) + // Upcoming items are displayed first, then history items below + const upcomingItems = currentIndex >= 0 ? queue.slice(currentIndex) : queue; + const historyItems = currentIndex > 0 ? queue.slice(0, currentIndex) : []; + return ( <> - {queue.map((climbQueueItem, index) => { + {/* Current and upcoming items (displayed first) */} + {upcomingItems.map((climbQueueItem) => { const isCurrent = currentClimbQueueItem?.uuid === climbQueueItem.uuid; - const isHistory = - queue.findIndex((item) => item.uuid === currentClimbQueueItem?.uuid) > - queue.findIndex((item) => item.uuid === climbQueueItem.uuid); + // Get the original index in the queue for drag-and-drop + const originalIndex = queue.findIndex((item) => item.uuid === climbQueueItem.uuid); return ( - +
+ +
); })} + + {/* History items (displayed below current/upcoming) */} + {historyItems.length > 0 && ( + <> + History + {historyItems.map((climbQueueItem) => { + // Get the original index in the queue for drag-and-drop + const originalIndex = queue.findIndex((item) => item.uuid === climbQueueItem.uuid); + + return ( + + ); + })} + + )}
{!viewOnlyMode && ( <> @@ -264,6 +316,8 @@ const QueueList: React.FC = ({ boardDetails, onClimbNavigate }) /> ); -}; +}); + +QueueList.displayName = 'QueueList'; export default QueueList; From 56fa96a3723662bc9a3167122d19a0d0fb258be5 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 3 Jan 2026 06:11:26 +0000 Subject: [PATCH 2/2] fix: Add timeout cleanup and use CSS module for divider styling - Add cleanup for scroll timeout on component unmount - Create queue-list.module.css for history divider styling - Replace inline style with CSS class per project guidelines --- .../components/queue-control/queue-control-bar.tsx | 14 ++++++++++++-- .../components/queue-control/queue-list.module.css | 3 +++ .../app/components/queue-control/queue-list.tsx | 3 ++- 3 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 packages/web/app/components/queue-control/queue-list.module.css diff --git a/packages/web/app/components/queue-control/queue-control-bar.tsx b/packages/web/app/components/queue-control/queue-control-bar.tsx index 2e09b909..43881d83 100644 --- a/packages/web/app/components/queue-control/queue-control-bar.tsx +++ b/packages/web/app/components/queue-control/queue-control-bar.tsx @@ -1,5 +1,5 @@ 'use client'; -import React, { useState, useCallback, useRef } from 'react'; +import React, { useState, useCallback, useRef, useEffect } from 'react'; import { Button, Row, Col, Card, Drawer, Space, Popconfirm } from 'antd'; import { SyncOutlined, DeleteOutlined, ExpandOutlined, FastForwardOutlined, FastBackwardOutlined } from '@ant-design/icons'; import { track } from '@vercel/analytics'; @@ -39,12 +39,22 @@ const QueueControlBar: React.FC = ({ boardDetails, angle }: Que const searchParams = useSearchParams(); const router = useRouter(); const queueListRef = useRef(null); + const scrollTimeoutRef = useRef(null); + + // Cleanup timeout on unmount + useEffect(() => { + return () => { + if (scrollTimeoutRef.current) { + clearTimeout(scrollTimeoutRef.current); + } + }; + }, []); // Scroll to current climb when drawer finishes opening const handleDrawerOpenChange = useCallback((open: boolean) => { if (open) { // Small delay to ensure the drawer content is rendered - setTimeout(() => { + scrollTimeoutRef.current = setTimeout(() => { queueListRef.current?.scrollToCurrentClimb(); }, 100); } diff --git a/packages/web/app/components/queue-control/queue-list.module.css b/packages/web/app/components/queue-control/queue-list.module.css new file mode 100644 index 00000000..17acc97f --- /dev/null +++ b/packages/web/app/components/queue-control/queue-list.module.css @@ -0,0 +1,3 @@ +.historyDivider { + margin: 8px 0; +} diff --git a/packages/web/app/components/queue-control/queue-list.tsx b/packages/web/app/components/queue-control/queue-list.tsx index 2ab0eac7..fcc5bc20 100644 --- a/packages/web/app/components/queue-control/queue-list.tsx +++ b/packages/web/app/components/queue-control/queue-list.tsx @@ -15,6 +15,7 @@ import { SUGGESTIONS_THRESHOLD } from '../board-page/constants'; import { useBoardProvider } from '../board-provider/board-provider-context'; import { LogAscentDrawer } from '../logbook/log-ascent-drawer'; import AuthModal from '../auth/auth-modal'; +import styles from './queue-list.module.css'; const { Text, Paragraph } = Typography; @@ -183,7 +184,7 @@ const QueueList = forwardRef(({ boardDetails, o {/* History items (displayed below current/upcoming) */} {historyItems.length > 0 && ( <> - History + History {historyItems.map((climbQueueItem) => { // Get the original index in the queue for drag-and-drop const originalIndex = queue.findIndex((item) => item.uuid === climbQueueItem.uuid);