SPB Git

spb/forma-ka Public

Python 65.1% TypeScript 17.9% CSS 16.4% HTML 0.5%
2.7 KB · 76 lines tsx
Raw Blame History
1// -----------------------------------------------------------------------------2// Forma-Ka — Agrégateur de formations (province de Québec)3// Auteur : Simon-Pierre Boucher — contact@spboucher.ai4// pages/Sources.tsx : registre des établissements de formation agrégés5// -----------------------------------------------------------------------------6import { useEffect, useState } from "react";7import { Source, fetchSources } from "../api";89export default function SourcesPage() {10  const [sources, setSources] = useState<Source[] | null>(null);11  const [error, setError] = useState<string | null>(null);1213  useEffect(() => {14    fetchSources()15      .then((r) => setSources(r.sources))16      .catch((e) => setError(String(e)));17  }, []);1819  return (20    <div className="container sources">21      <span className="kicker">Registre — établissements de formation</span>22      <h1>Sources agrégées</h1>23      <p className="sub">24        Universités, cégeps, firmes de formation et organisateurs d'événements du25        Québec recensés par Forma-Ka. Chaque source « active » est synchronisée26        périodiquement par un connecteur dédié; les autres sont en attente de27        connecteur.28      </p>2930      {error && <div className="notice">⚠️ {error}</div>}31      {!sources && !error && <div className="notice">Chargement…</div>}3233      {sources && (34        <div className="src-wrap">35        <table className="src-table">36          <thead>37            <tr>38              <th>Établissement</th>39              <th>Type d'offre</th>40              <th>Statut</th>41              <th style={{ textAlign: "right" }}>Formations actives</th>42              <th>Dernière synchro</th>43            </tr>44          </thead>45          <tbody>46            {sources.map((s) => (47              <tr key={s.id}>48                <td>49                  <a href={s.url} target="_blank" rel="noopener noreferrer">{s.name}</a>50                </td>51                <td style={{ color: "var(--ink-2)", maxWidth: 380 }}>{s.type_offre}</td>52                <td>53                  {s.connector ? (54                    <span className="pill ok">connecté</span>55                  ) : (56                    <span className="pill todo">{s.status}</span>57                  )}58                </td>59                <td style={{ textAlign: "right" }}>60                  <span className="count-pill">{s.active_formations || "—"}</span>61                </td>62                <td style={{ color: "var(--ink-3)" }}>63                  {s.last_sync64                    ? new Date(s.last_sync * 1000).toLocaleString("fr-CA")65                    : "—"}66                </td>67              </tr>68            ))}69          </tbody>70        </table>71        </div>72      )}73    </div>74  );75}76