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 (
{/* 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.
{/* 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 && (
);
}
/**
* 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"
>
{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' : ''}
)}