Initial Commit

This commit is contained in:
2026-04-09 23:23:31 +02:00
commit 66b1c6777d
94 changed files with 15173 additions and 0 deletions
+203
View File
@@ -0,0 +1,203 @@
import { NavLink } from 'react-router-dom';
import { useTonight } from '../../hooks/useTonight';
import { useWeather, useForecast } from '../../hooks/useWeather';
import MoonPhaseIcon from '../sky/MoonPhaseIcon';
import GoNogo from '../weather/GoNogo';
const SEEING_LABELS: Record<number, string> = {
1: '0.5″', 2: '0.75″', 3: '1.0″', 4: '1.25″',
5: '1.5″', 6: '2.0″', 7: '2.5″', 8: '>3″',
};
const TRANSP_LABELS: Record<number, string> = {
1: 'Excellent', 2: 'Good', 3: 'Good', 4: 'Average',
5: 'Average', 6: 'Poor', 7: 'Poor', 8: 'Bad',
};
const navItems = [
{ path: '/dashboard', label: 'Dashboard', icon: '⬡' },
{ path: '/targets', label: 'Targets', icon: '✦' },
{ path: '/calendar', label: 'Calendar', icon: '◫' },
{ path: '/stats', label: 'Statistics', icon: '▤' },
{ path: '/gallery', label: 'Gallery', icon: '⬚' },
{ path: '/solar-system', label: 'Solar System', icon: '◉' },
{ path: '/settings', label: 'Settings', icon: '⚙' },
];
function fmtTime(utc?: string): string {
if (!utc) return '—';
return new Date(utc).toLocaleTimeString('fr-FR', {
hour: '2-digit',
minute: '2-digit',
timeZone: 'Europe/Paris',
});
}
export default function Sidebar() {
const { data: tonight } = useTonight();
const { data: weather } = useWeather();
const { data: forecast } = useForecast();
// First forecast slot = current/nearest 3-hour window
const slot = (forecast as { dataseries?: { seeing?: number; transparency?: number; cloudcover?: number }[] })?.dataseries?.[0];
const darkStart = tonight?.true_dark_start_utc;
const darkEnd = tonight?.true_dark_end_utc;
const darkStr = darkStart && darkEnd
? `${fmtTime(darkStart)}${fmtTime(darkEnd)}`
: '—';
const dewMargin = weather?.temp_c != null && weather?.dew_point_c != null
? (weather.temp_c - weather.dew_point_c).toFixed(1)
: null;
const seeingMap: Record<number, string> = {
1: '0.5″', 2: '0.75″', 3: '1.0″', 4: '1.25″',
5: '1.5″', 6: '2.0″', 7: '2.5″', 8: '>3″',
};
return (
<aside style={{
width: 220,
minWidth: 220,
background: 'var(--bg-deep)',
borderRight: '1px solid var(--border)',
display: 'flex',
flexDirection: 'column',
height: '100vh',
position: 'sticky',
top: 0,
overflow: 'hidden',
}}>
{/* Logo */}
<div style={{
padding: '20px 20px 16px',
borderBottom: '1px solid var(--border)',
}}>
<div style={{
fontFamily: 'var(--font-display)',
fontSize: 16,
fontWeight: 700,
color: 'var(--amber)',
letterSpacing: '0.06em',
textTransform: 'uppercase',
}}>
Astronome
</div>
</div>
{/* Navigation */}
<nav style={{ padding: '8px 0', flex: 1, overflow: 'auto' }}>
{navItems.map(item => (
<NavLink
key={item.path}
to={item.path}
style={({ isActive }) => ({
display: 'flex',
alignItems: 'center',
gap: 10,
padding: '9px 20px',
color: isActive ? 'var(--text-hi)' : 'var(--text-mid)',
background: isActive ? 'var(--bg-hover)' : 'transparent',
borderLeft: `2px solid ${isActive ? 'var(--amber)' : 'transparent'}`,
fontFamily: 'var(--font-display)',
fontSize: 13,
fontWeight: isActive ? 700 : 400,
letterSpacing: '0.05em',
textDecoration: 'none',
transition: 'color 0.1s',
})}
>
<span style={{ fontSize: 14, opacity: 0.7 }}>{item.icon}</span>
{item.label}
</NavLink>
))}
</nav>
{/* Tonight widget */}
<div style={{
borderTop: '1px solid var(--border)',
padding: '12px 16px',
}}>
<div style={{
fontSize: 10,
fontFamily: 'var(--font-mono)',
color: 'var(--text-lo)',
letterSpacing: '0.1em',
marginBottom: 8,
textTransform: 'uppercase',
}}>
Tonight
</div>
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<tbody>
{[
['Dusk', fmtTime(tonight?.astro_dusk_utc)],
['Dawn', fmtTime(tonight?.astro_dawn_utc)],
['Dark', darkStr],
].map(([label, value]) => (
<tr key={label}>
<td style={{ color: 'var(--text-lo)', fontSize: 11, fontFamily: 'var(--font-mono)', paddingBottom: 3 }}>{label}</td>
<td style={{ color: 'var(--text-mid)', fontSize: 11, fontFamily: 'var(--font-mono)', textAlign: 'right' }}>{value}</td>
</tr>
))}
<tr>
<td style={{ color: 'var(--text-lo)', fontSize: 11, fontFamily: 'var(--font-mono)' }}>Moon</td>
<td style={{ textAlign: 'right', display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: 4 }}>
<span style={{ color: 'var(--text-mid)', fontSize: 11, fontFamily: 'var(--font-mono)' }}>
{tonight?.moon_illumination != null
? `${Math.round(tonight.moon_illumination * 100)}%`
: '—'}
</span>
{tonight?.moon_illumination != null && (
<MoonPhaseIcon illumination={tonight.moon_illumination} size={14} />
)}
</td>
</tr>
</tbody>
</table>
</div>
{/* Conditions widget */}
<div style={{
borderTop: '1px solid var(--border)',
padding: '12px 16px',
}}>
<div style={{
fontSize: 10,
fontFamily: 'var(--font-mono)',
color: 'var(--text-lo)',
letterSpacing: '0.1em',
marginBottom: 8,
textTransform: 'uppercase',
}}>
Conditions
</div>
<div style={{ marginBottom: 6 }}>
<GoNogo status={weather?.go_nogo} compact />
</div>
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<tbody>
{[
['Temp', weather?.temp_c != null ? `${weather.temp_c.toFixed(1)}°C` : '—'],
['Dew Δ', dewMargin ? `${dewMargin}°C ${parseFloat(dewMargin) < 4 ? '⚠' : '✓'}` : '—'],
['Seeing', slot?.seeing ? SEEING_LABELS[slot.seeing] ?? '—' : '—'],
['Transp', slot?.transparency ? TRANSP_LABELS[slot.transparency] ?? '—' : '—'],
].map(([label, value]) => (
<tr key={label}>
<td style={{ color: 'var(--text-lo)', fontSize: 11, fontFamily: 'var(--font-mono)', paddingBottom: 3 }}>{label}</td>
<td style={{
color: label === 'Dew Δ' && dewMargin && parseFloat(dewMargin) < 4
? 'var(--danger)'
: 'var(--text-mid)',
fontSize: 11,
fontFamily: 'var(--font-mono)',
textAlign: 'right',
}}>{value as string}</td>
</tr>
))}
</tbody>
</table>
</div>
</aside>
);
}