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:
@@ -1,6 +1,6 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import type { Target, Workflow } from '../../api/types';
|
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 { useTargetLog } from '../../hooks/useLog';
|
||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { api } from '../../api';
|
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 [tab, setTab] = useState(0);
|
||||||
const [selectedFilter, setSelectedFilter] = useState('sv220');
|
const [selectedFilter, setSelectedFilter] = useState('sv220');
|
||||||
const [notes, setNotes] = useState<string | null>(null);
|
const [notes, setNotes] = useState<string | null>(null);
|
||||||
|
const [navStack, setNavStack] = useState<string[]>([]); // stack of navigated target IDs
|
||||||
const qc = useQueryClient();
|
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: tonight } = useTonight();
|
||||||
const { data: visData } = useTargetVisibility(target.id);
|
const { data: visData } = useTargetVisibility(target.id);
|
||||||
const { data: curveData } = useTargetCurve(target.id);
|
const { data: curveData } = useTargetCurve(target.id);
|
||||||
@@ -265,8 +288,24 @@ export default function DetailDrawer({ target }: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ background: 'var(--bg-panel)', border: '1px solid var(--border-hi)', borderRadius: 4, marginTop: 2 }}>
|
<div style={{ background: 'var(--bg-panel)', border: '1px solid var(--border-hi)', borderRadius: 4, marginTop: 2 }}>
|
||||||
{/* Tabs */}
|
{/* Tabs + optional back button */}
|
||||||
<div style={{ display: 'flex', borderBottom: '1px solid var(--border)' }}>
|
<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) => (
|
{TABS.map((t, i) => (
|
||||||
<button
|
<button
|
||||||
key={t}
|
key={t}
|
||||||
@@ -455,7 +494,18 @@ export default function DetailDrawer({ target }: Props) {
|
|||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
|
||||||
{similarData!.similar.slice(0, 3).map(s => (
|
{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 && (
|
{s.messier_num != null && (
|
||||||
<span style={{ fontFamily: 'var(--font-mono)', fontSize: 10, color: 'var(--amber)', fontWeight: 700, width: 28 }}>
|
<span style={{ fontFamily: 'var(--font-mono)', fontSize: 10, color: 'var(--amber)', fontWeight: 700, width: 28 }}>
|
||||||
M{s.messier_num}
|
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' })}
|
{new Date(s.transit_utc).toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit', timeZone: 'Europe/Paris' })}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
<span style={{ fontFamily: 'var(--font-mono)', fontSize: 10, color: 'var(--text-lo)' }}>→</span>
|
||||||
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user