Navigate between similar targets within the detail drawer

Clicking a nearby target opens it inside the same drawer: the drawer
content switches to the new target (fetched via useTarget) and all
tabs/hooks update automatically. A '← [name]' back button appears in
the tab bar to return to the previous target. Navigation is stackable
(can chain through multiple nearby targets). Tab and filter selection
reset on each navigation step.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-17 14:13:01 +02:00
parent 53662ac36f
commit 7801c4a491
@@ -1,6 +1,6 @@
import { useState } from 'react';
import type { Target, Workflow } from '../../api/types';
import { useTargetCurve, useTargetFilters, useTargetSimilar, useTargetVisibility, useTargetWorkflow, useTargetYearly } from '../../hooks/useTargets';
import { useTarget, useTargetCurve, useTargetFilters, useTargetSimilar, useTargetVisibility, useTargetWorkflow, useTargetYearly } from '../../hooks/useTargets';
import { useTargetLog } from '../../hooks/useLog';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { api } from '../../api';
@@ -231,12 +231,35 @@ function ImagingCalculator({ target, filterId }: { target: Target; filterId: str
);
}
export default function DetailDrawer({ target }: Props) {
export default function DetailDrawer({ target: propTarget }: Props) {
const [tab, setTab] = useState(0);
const [selectedFilter, setSelectedFilter] = useState('sv220');
const [notes, setNotes] = useState<string | null>(null);
const [navStack, setNavStack] = useState<string[]>([]); // stack of navigated target IDs
const qc = useQueryClient();
const navId = navStack.length > 0 ? navStack[navStack.length - 1] : '';
const { data: navTarget } = useTarget(navId);
// Use navigated target when available, otherwise fall back to the prop target
const target = (navId && navTarget) ? navTarget : propTarget;
const navigateTo = (id: string) => {
setNavStack(prev => [...prev, id]);
setTab(0);
setSelectedFilter('sv220');
setNotes(null);
};
const navigateBack = () => {
setNavStack(prev => prev.slice(0, -1));
setTab(0);
setSelectedFilter('sv220');
setNotes(null);
};
// Name of the target we'd go back to
const backName = navStack.length === 1
? (propTarget.common_name ?? propTarget.name)
: navStack.length > 1 ? navStack[navStack.length - 2] : null;
const { data: tonight } = useTonight();
const { data: visData } = useTargetVisibility(target.id);
const { data: curveData } = useTargetCurve(target.id);
@@ -265,8 +288,24 @@ export default function DetailDrawer({ target }: Props) {
return (
<div style={{ background: 'var(--bg-panel)', border: '1px solid var(--border-hi)', borderRadius: 4, marginTop: 2 }}>
{/* Tabs */}
<div style={{ display: 'flex', borderBottom: '1px solid var(--border)' }}>
{/* Tabs + optional back button */}
<div style={{ display: 'flex', borderBottom: '1px solid var(--border)', alignItems: 'center' }}>
{navStack.length > 0 && (
<button
onClick={navigateBack}
style={{
padding: '8px 12px',
fontFamily: 'var(--font-mono)', fontSize: 11,
color: 'var(--text-lo)', background: 'none',
borderRight: '1px solid var(--border)',
borderBottom: '2px solid transparent',
cursor: 'pointer', whiteSpace: 'nowrap',
flexShrink: 0,
}}
>
{backName}
</button>
)}
{TABS.map((t, i) => (
<button
key={t}
@@ -455,7 +494,18 @@ export default function DetailDrawer({ target }: Props) {
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
{similarData!.similar.slice(0, 3).map(s => (
<div key={s.id} style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<button
key={s.id}
onClick={() => navigateTo(s.id)}
style={{
display: 'flex', alignItems: 'center', gap: 8,
background: 'none', border: 'none', cursor: 'pointer',
padding: '3px 6px', borderRadius: 3, textAlign: 'left',
transition: 'background 0.1s',
}}
onMouseEnter={e => (e.currentTarget.style.background = 'var(--bg-hover)')}
onMouseLeave={e => (e.currentTarget.style.background = 'none')}
>
{s.messier_num != null && (
<span style={{ fontFamily: 'var(--font-mono)', fontSize: 10, color: 'var(--amber)', fontWeight: 700, width: 28 }}>
M{s.messier_num}
@@ -476,7 +526,8 @@ export default function DetailDrawer({ target }: Props) {
{new Date(s.transit_utc).toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit', timeZone: 'Europe/Paris' })}
</span>
)}
</div>
<span style={{ fontFamily: 'var(--font-mono)', fontSize: 10, color: 'var(--text-lo)' }}></span>
</button>
))}
</div>
</div>