import { useQueries, useQuery } from '@tanstack/react-query'; import { Link } from 'react-router-dom'; import { api } from '@/api/client'; import type { LocaleStats } from '@/api/types'; import { useAuth } from '@/auth/AuthProvider'; import { ProgressBar } from '@/components/Status'; /** * Point d'entrée : les projets, et où en est chaque langue. * * Ce n'est délibérément PAS un tableau de bord. Un tableau de bord générique * affiche des chiffres que personne n'a demandés ; cet écran répond à la seule * question qu'on se pose en arrivant — « où reste-t-il du travail, et pour quelle * langue ? » — puis s'efface. */ export function ProjectsPage() { const { user, logout } = useAuth(); const projects = useQuery({ queryKey: ['projects'], queryFn: api.projects }); const stats = useQueries({ queries: (projects.data ?? []).map((project) => ({ queryKey: ['stats', project.uuid], queryFn: () => api.stats(project.uuid), })), }); return (

Projets

{user?.name} {user?.isExternal && ( prestataire externe )}

{/* Absente pour tous les autres, pas grisée : un menu plein d'options inaccessibles apprend à l'utilisateur que l'interface ne le concerne pas. */} {user?.isSuperAdmin && ( Administration )}
{projects.isLoading &&

Chargement…

} {projects.data?.length === 0 && (

Aucun projet ne vous est accessible.

L'accès à un projet s'obtient auprès de son administrateur.

)}
{projects.data?.map((project, index) => { const projectStats = stats[index]?.data; const targets = (projectStats?.locales ?? []).filter((l) => !l.isSource); return (

{project.name}

{project.description !== null && (

{project.description}

)}

{project.platforms.map((platform) => ( {platform.slug} ))}

source : {project.sourceLocale.code}
{/* Les langues de l'utilisateur d'abord. Présenter les sept langues d'un projet à une traductrice habilitée sur deux, c'est lui faire trier son propre travail. */} {(() => { const mine = targets.filter((l) => l.canWrite); const others = targets.filter((l) => !l.canWrite); const split = mine.length > 0 && others.length > 0; return ( <> {split && (

Vos langues

)}
{(split ? mine : targets).map((locale) => ( ))}
{split && ( <>

Autres langues du projet · consultation

{others.map((locale) => ( ))}
)} ); })()} {targets.length === 0 && stats[index]?.isSuccess && (

Aucune langue cible activée sur ce projet.

)}
); })}
); } /** * Une langue, son avancement, et le raccourci vers le travail restant. * * Le lien mène directement à la grille filtrée sur ce qui reste à faire. C'est * la différence entre « voici l'état des lieux » et « voici votre travail » : * un clic de moins, mais surtout aucune décision à prendre en arrivant. */ function LocaleRow({ locale, projectUuid }: { locale: LocaleStats; projectUuid: string }) { const actionable = locale.untranslated + locale.draft + locale.needsReview; const readOnly = !locale.canWrite; return ( 0 ? '&status=untranslated' : ''}`} className="group block rounded-md px-2 py-1.5 -mx-2 transition-colors hover:bg-ink-50" >
{locale.nativeName} {locale.direction === 'rtl' && ( RTL )} {locale.completion}%
{readOnly ? ( consultation ) : actionable > 0 ? ( {actionable} à traiter ) : ( Rien à faire )} {/* Les traductions obsolètes sont sorties du lot : c'est le seul chiffre qui signale un texte potentiellement faux en production. */} {locale.needsReview > 0 && ( {locale.needsReview} obsolète{locale.needsReview > 1 ? 's' : ''} )}
); }