import { useMemo } from 'react';
import { ApiError } from '@/api';
import { useAssignments, usePositions, useShifts, useVolunteer } from '@/api/hooks';
import { useAuth } from '@/auth/auth-context';
import { ErrorNote, Loading } from '@/components/state';
import { DesignNote, PhoneFrame } from '@/pages/mobile/PhoneFrame';
import { hourRangeLabel, hoursBetween, longDayLabel, naiveMinutes } from '@/lib/time';
import { t } from '@/i18n';
export function MobileVolunteerPage() {
const { account } = useAuth();
const volunteerId = account?.volunteer_id ?? null;
const meVolunteer = useVolunteer(volunteerId);
const assignments = useAssignments(volunteerId ? { volunteerId } : undefined, {
enabled: Boolean(volunteerId),
});
const shifts = useShifts();
const positions = usePositions();
const shiftsById = useMemo(
() => new Map((shifts.data ?? []).map((s) => [s.id, s])),
[shifts.data],
);
const positionsById = useMemo(
() => new Map((positions.data ?? []).map((p) => [p.id, p])),
[positions.data],
);
if (!volunteerId) {
return (
{t('mobileVol.noAccount')}
);
}
if (assignments.isLoading) return ;
if (assignments.error) return ;
const firstName = meVolunteer.data?.first_name ?? account?.email.split('@')[0] ?? '';
// shifts / positions peuvent être refusés (403) à un bénévole côté back-end réel :
// on affiche alors les créneaux sans détail plutôt que de tomber en erreur.
const detailsUnavailable =
(shifts.error instanceof ApiError && shifts.error.status === 403) ||
(positions.error instanceof ApiError && positions.error.status === 403);
const slots = (assignments.data ?? [])
.map((a) => ({ assignment: a, shift: shiftsById.get(a.shift_id) ?? null }))
.sort((a, b) => {
const av = a.shift ? naiveMinutes(a.shift.starts_at) : 0;
const bv = b.shift ? naiveMinutes(b.shift.starts_at) : 0;
return av - bv;
});
const totalHours =
Math.round(
slots.reduce(
(sum, s) => sum + (s.shift ? hoursBetween(s.shift.starts_at, s.shift.ends_at) : 0),
0,
) * 10,
) / 10;
return (
{t('mobileVol.kicker')}
{t('mobileVol.greeting', { name: firstName.toUpperCase() })}
{t('mobileVol.slots', { count: slots.length })}
{!detailsUnavailable ? (
{t('mobileVol.hours', { count: totalHours })}
) : null}
{detailsUnavailable ? (
{t('mobileVol.detailsUnavailable')}
) : null}
{slots.map(({ assignment, shift }) => {
const position = shift ? positionsById.get(shift.position_id) : undefined;
return (
{shift ? (
{longDayLabel(shift.starts_at)}
{hourRangeLabel(shift.starts_at, shift.ends_at)}
) : null}
{position ? (
{position.name.toUpperCase()}
) : null}
{position?.location ? (
{t('mobileVol.location', { location: position.location })}
) : null}
{t('mobileVol.slotAssigned')}
);
})}
{slots.length === 0 ? (
{t('mobileVol.noSlots')}
) : null}
{t('mobileVol.designNote')}
);
}