Files
moon/components/LunarCalendar.tsx
Puechberty Arthur 49fd31f4db first commit
2026-03-30 23:07:36 +02:00

181 lines
6.9 KiB
TypeScript

'use client';
import { useState, useMemo } from 'react';
import { useLocale } from './LocaleProvider';
import { ts, getMonths } from '@/lib/i18n';
import { getMoonEventsForYear, type MoonEvent } from '@/lib/lunar';
const PHASE_EMOJIS: Record<string, string> = {
new_moon: '🌑',
first_quarter: '🌓',
full_moon: '🌕',
last_quarter: '🌗',
};
export default function LunarCalendar() {
const { locale } = useLocale();
const currentYear = new Date().getFullYear();
const [year, setYear] = useState(currentYear);
const [viewMode, setViewMode] = useState<'year' | 'month'>('year');
const [selectedMonth, setSelectedMonth] = useState(new Date().getMonth());
const events = useMemo(() => getMoonEventsForYear(year), [year]);
const months = getMonths(locale);
const formatTime = (date: Date) => {
try {
return date.toLocaleTimeString(locale, { hour: '2-digit', minute: '2-digit', timeZoneName: 'short' });
} catch {
return date.toLocaleTimeString('en', { hour: '2-digit', minute: '2-digit' });
}
};
const formatDay = (date: Date) => {
try {
return date.toLocaleDateString(locale, { weekday: 'short', day: 'numeric' });
} catch {
return date.toLocaleDateString('en', { weekday: 'short', day: 'numeric' });
}
};
const getMonthEvents = (monthIndex: number): MoonEvent[] => {
return events.filter(e => e.date.getMonth() === monthIndex);
};
const phaseKey = (phase: string) => {
return phase as 'new_moon' | 'first_quarter' | 'full_moon' | 'last_quarter';
};
return (
<section id="calendar" aria-label="Lunar Calendar 2026" className="section-container">
<div className="text-center mb-10">
<h2 className="section-title">{ts('calendar_title', locale)}</h2>
<p className="section-subtitle mx-auto">{ts('calendar_subtitle', locale)}</p>
</div>
{/* Controls */}
<div className="flex flex-wrap items-center justify-center gap-4 mb-8">
<div className="flex items-center gap-2">
<button
onClick={() => setYear(y => y - 1)}
className="px-3 py-2 rounded-lg bg-white/5 border border-white/10 hover:border-indigo-400/50 transition-all"
>
</button>
<span className="text-xl font-bold px-4 min-w-20 text-center">{year}</span>
<button
onClick={() => setYear(y => y + 1)}
className="px-3 py-2 rounded-lg bg-white/5 border border-white/10 hover:border-indigo-400/50 transition-all"
>
</button>
</div>
<div className="flex rounded-full bg-white/5 border border-white/10 p-1">
<button
onClick={() => setViewMode('year')}
className={`px-4 py-1.5 rounded-full text-sm transition-all ${
viewMode === 'year' ? 'bg-indigo-500/30 text-indigo-200' : 'text-white/50 hover:text-white/70'
}`}
>
📅 {locale === 'fr' ? 'Année' : 'Year'}
</button>
<button
onClick={() => setViewMode('month')}
className={`px-4 py-1.5 rounded-full text-sm transition-all ${
viewMode === 'month' ? 'bg-indigo-500/30 text-indigo-200' : 'text-white/50 hover:text-white/70'
}`}
>
📆 {locale === 'fr' ? 'Mois' : 'Month'}
</button>
</div>
</div>
{/* Month selector (when in month mode) */}
{viewMode === 'month' && (
<div className="flex flex-wrap justify-center gap-2 mb-8">
{months.map((m, i) => (
<button
key={i}
onClick={() => setSelectedMonth(i)}
className={`px-3 py-1.5 rounded-full text-sm transition-all ${
selectedMonth === i ? 'bg-indigo-500/30 text-indigo-200 border border-indigo-400/50' : 'bg-white/5 text-white/50 hover:text-white/70 border border-transparent'
}`}
>
{m}
</button>
))}
</div>
)}
{/* Calendar grid */}
{viewMode === 'year' ? (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{months.map((month, i) => {
const monthEvents = getMonthEvents(i);
return (
<div key={i} className="glass-card p-4">
<h3 className="text-lg font-semibold mb-3 text-indigo-200">{month}</h3>
<div className="space-y-2">
{monthEvents.length === 0 ? (
<p className="text-white/30 text-sm italic"></p>
) : (
monthEvents.map((event, j) => (
<div key={j} className="flex items-center justify-between gap-2">
<div className={`phase-badge ${event.phase}`}>
<span>{PHASE_EMOJIS[event.phase]}</span>
<span>{ts(phaseKey(event.phase), locale)}</span>
</div>
<div className="text-right text-sm">
<div className="text-white/70">{formatDay(event.date)}</div>
<div className="text-white/40 text-xs">{formatTime(event.date)}</div>
</div>
</div>
))
)}
</div>
</div>
);
})}
</div>
) : (
<div className="glass-card max-w-2xl mx-auto p-6">
<h3 className="text-2xl font-semibold mb-6 text-center text-indigo-200">
{months[selectedMonth]} {year}
</h3>
<div className="space-y-4">
{getMonthEvents(selectedMonth).length === 0 ? (
<p className="text-center text-white/40"></p>
) : (
getMonthEvents(selectedMonth).map((event, j) => (
<div key={j} className="flex items-center justify-between p-4 rounded-xl bg-white/5 border border-white/5">
<div className="flex items-center gap-4">
<span className="text-3xl">{PHASE_EMOJIS[event.phase]}</span>
<div>
<p className="font-semibold">{ts(phaseKey(event.phase), locale)}</p>
<p className="text-white/50 text-sm">{formatDay(event.date)}</p>
</div>
</div>
<div className="text-right">
<p className="text-indigo-300 font-mono">{formatTime(event.date)}</p>
</div>
</div>
))
)}
</div>
</div>
)}
{/* Legend */}
<div className="flex flex-wrap justify-center gap-4 mt-8">
{(['new_moon', 'first_quarter', 'full_moon', 'last_quarter'] as const).map((phase) => (
<div key={phase} className={`phase-badge ${phase}`}>
<span>{PHASE_EMOJIS[phase]}</span>
<span>{ts(phase, locale)}</span>
</div>
))}
</div>
</section>
);
}