const { useState, useEffect, useCallback, useMemo, useRef } = React; /* --------------------------------------------------- Filet anti-crash ---- */ // Un bug dans un composant n'écroule plus toute l'appli : on affiche un message // récupérable au lieu d'un écran blanc. (Doit être une classe : les hooks ne // peuvent pas intercepter les erreurs de rendu.) class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { err: null }; } static getDerivedStateFromError(err) { return { err }; } componentDidCatch(err, info) { console.error("[RMA] crash intercepté :", err, info); } componentDidUpdate(prev) { if (prev.resetKey !== this.props.resetKey && this.state.err) this.setState({ err: null }); } render() { if (!this.state.err) return this.props.children; const reset = () => this.setState({ err: null }); if (this.props.fallback) return this.props.fallback(this.state.err, reset); return (
L'application a rencontré un problème mais tes données sont intactes (rien n'est perdu).
{String(this.state.err && (this.state.err.message || this.state.err))}
| {d.reference} | {d.client_name} | {d.product} | {fmtMoney(d.amount_claimed, d.currency)} | {d.next_action_type ? (() => { const ad = daysUntil(d.next_action_date); const m = actionMeta(d.next_action_type); return {m.icon} {m.label} · {ad < 0 ? `+${-ad} j` : ad === 0 ? "auj." : "J-" + ad} ; })() : —} | {fmtDate(d.due_date)}{isOverdue(d) && " ⚠"} | {ago(d.updated_at)} |
| Site | Client | Produit | Réf. cmd | N° série | Montant | Statut | Échéance | |
|---|---|---|---|---|---|---|---|---|
| setCell(i, "client_name", e.target.value)} /> | setCell(i, "product", e.target.value)} /> | setCell(i, "order_ref", e.target.value)} /> | setCell(i, "serial_number", e.target.value)} /> | setCell(i, "amount_claimed", e.target.value)} /> | setCell(i, "due_date", e.target.value)} /> |
Colonnes attendues (séparateur ; ou ,) :
site;client_name;product;order_ref;serial_number;dispute_reason;carrier;amount_claimed;currency;status;due_date
Dernier scan : {new Date(st.last_scan).toLocaleString("fr-FR")} — {st.last_scanned} analysé(s), {st.last_matched} rattaché(s).
} {scanResult && (Une seule fois, ~10 minutes : crée tes identifiants Google (gratuit) puis dépose le fichier ici.
{window.location.origin}/api/gmail/oauth2callbackCredentials en place. Clique pour connecter une boîte supplémentaire (même procédure Google à chaque fois).
Communique ce code au titulaire. Il ne sera plus jamais affiché — note-le maintenant.
{newCode.code}
| Identifiant | Nom | Rôle | Statut | Dernier accès | |
|---|---|---|---|---|---|
| {u.login_id}{u.id === currentUser.id && (toi)} | {u.full_name || "—"} | {u.is_admin ? Admin : Agent} | {u.active ? Actif : Désactivé} | {u.last_login ? : jamais connecté} | {" "} {" "} {u.id !== currentUser.id && }{" "} {u.id !== currentUser.id && } |
| Date | IP | Machine | Opérateur | Localisation |
|---|---|---|---|---|
| {fmtDateTime(e.created_at)} | {e.ip || "—"} | {devIcon(e.device)} {e.os} · {e.browser} | {e.isp || "—"} | {e.location || "—"} |
Ce panneau a rencontré une erreur — le reste de l'appli fonctionne toujours.
{String(err && (err.message || err))}