Commit ebeabe51 authored by Kourser's avatar Kourser
Browse files

Console : dire quand une action est refusée, au lieu de ne rien faire

Douze actions appelaient l'API sans rien attraper : dupliquer, archiver,
supprimer un utilisateur, un personnage, un inject, une phase, une
fiche, installer le référentiel, confirmer un appel, rejouer ou annuler
un inject. Le serveur refusait — organisation suspendue, session en
observation, droits insuffisants — et l'écran restait muet : le bouton
cliquait, la ligne restait, rien n'expliquait pourquoi.

Chacune remonte désormais le motif du serveur, là où l'écran affiche
déjà ses erreurs. L'éditeur de kit a reçu un état distinct : son
« error » remplace l'écran entier et ne vaut que pour le chargement.

Co-Authored-By: default avatarClaude Opus 5 <noreply@anthropic.com>
parent 55bf7622
Loading
Loading
Loading
Loading
+17 −1
Changes for apps/web/src/Console.tsx: 17 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -474,8 +474,12 @@ export function Console({
                          <button
                            className="btn primary"
                            onClick={async () => {
                              try {
                                await api.confirmCall(i.id);
                                await reload();
                              } catch (e) {
                                setError(e instanceof ApiError ? e.message : t('Erreur'));
                              }
                            }}
                          >
                            ☎ {t('Appel passé')}
@@ -485,8 +489,12 @@ export function Console({
                          <button
                            className="btn ghost"
                            onClick={async () => {
                              try {
                                await api.replayInject(i.id);
                                await reload();
                              } catch (e) {
                                setError(e instanceof ApiError ? e.message : t('Erreur'));
                              }
                            }}
                          >
                            {t('Rejouer')}
@@ -501,8 +509,16 @@ export function Console({
                          <button
                            className="btn ghost"
                            onClick={async () => {
                              try {
                                await api.cancelInject(i.id);
                              setInjects((prev) => prev.map((x) => (x.id === i.id ? { ...x, status: 'CANCELLED' } : x)));
                                setInjects((prev) =>
                                  prev.map((x) => (x.id === i.id ? { ...x, status: 'CANCELLED' } : x)),
                                );
                              } catch (e) {
                                // Sans ce retour, l'inject restait affiche « en attente »
                                // sans que personne sache que l'annulation a echoue.
                                setError(e instanceof ApiError ? e.message : t('Erreur'));
                              }
                            }}
                          >
                            {t('Annuler')}
+4 −0
Changes for apps/web/src/Dashboard.tsx: 4 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -385,8 +385,12 @@ function UserRow({
              <button
                className="btn ghost danger"
                onClick={async () => {
                  try {
                    await api.deleteUser(user.id);
                    onChanged();
                  } catch (err) {
                    setError(err instanceof ApiError ? err.message : 'Suppression impossible');
                  }
                }}
              >
                {t('Supprimer')}
+12 −0
Changes for apps/web/src/Exercises.tsx: 12 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -135,8 +135,12 @@ function ExercisesList({
                <button
                  className="btn ghost"
                  onClick={async () => {
                    try {
                      await api.duplicateExercise(ex.id);
                      void reload();
                    } catch (err) {
                      setError(err instanceof ApiError ? err.message : 'Duplication impossible');
                    }
                  }}
                >
                  {t('Dupliquer')}
@@ -145,8 +149,12 @@ function ExercisesList({
                  <button
                    className="btn ghost"
                    onClick={async () => {
                      try {
                        await api.archiveExercise(ex.id);
                        void reload();
                      } catch (err) {
                        setError(err instanceof ApiError ? err.message : 'Archivage impossible');
                      }
                    }}
                  >
                    {t('Archiver')}
@@ -1075,8 +1083,12 @@ function CharacterRow({
        <button
          className="btn ghost danger"
          onClick={async () => {
            try {
              await api.deleteCharacter(c.id);
              onChanged();
            } catch (err) {
              setError(err instanceof ApiError ? err.message : 'Suppression impossible');
            }
          }}
        >
          {t('Supprimer')}
+38 −10
Changes for apps/web/src/KitEditor.tsx: 38 added lines, 10 removed lines.
Original line number Diff line number Diff line
@@ -35,6 +35,22 @@ export function KitEditor({ kitId, onBack }: { kitId: string; onBack: () => void
  const [phaseForm, setPhaseForm] = useState<ApiKitPhase | 'new' | null>(null);
  /** Section dont le panneau d'import CSV est ouvert. Une seule a la fois. */
  const [importOuvert, setImportOuvert] = useState<ImportKind | null>(null);
  /**
   * Echec d'une ACTION, distinct de `error` qui remplace l'ecran entier et ne
   * vaut que pour le chargement. Sans lui, un refus du serveur — organisation
   * suspendue, session en observation — ne laissait aucune trace : le bouton
   * cliquait, la ligne restait, et rien ne disait pourquoi.
   */
  const [actionError, setActionError] = useState<string | null>(null);

  async function agir(action: () => Promise<void>, defaut: string): Promise<void> {
    setActionError(null);
    try {
      await action();
    } catch (err) {
      setActionError(err instanceof ApiError ? err.message : defaut);
    }
  }

  const reload = useCallback(async () => {
    try {
@@ -94,6 +110,8 @@ export function KitEditor({ kitId, onBack }: { kitId: string; onBack: () => void
        </div>
      </div>

      {actionError && <p className="err">{t(actionError)}</p>}

      {/* Metadonnees */}
      <div className="ke-meta">
        <div className="row">
@@ -156,10 +174,12 @@ export function KitEditor({ kitId, onBack }: { kitId: string; onBack: () => void
              </button>
              <button
                className="btn ghost danger"
                onClick={async () => {
                onClick={() =>
                  void agir(async () => {
                    await api.deleteKitInject(kitId, i.id);
                    await reload();
                }}
                  }, 'Suppression impossible')
                }
              >
                {t('Supprimer')}
              </button>
@@ -216,10 +236,12 @@ export function KitEditor({ kitId, onBack }: { kitId: string; onBack: () => void
                </button>
                <button
                  className="btn ghost danger"
                  onClick={async () => {
                  onClick={() =>
                    void agir(async () => {
                      await api.deleteKitCharacter(kitId, c.id);
                      await reload();
                  }}
                    }, 'Suppression impossible')
                  }
                >
                  {t('Supprimer')}
                </button>
@@ -279,10 +301,12 @@ export function KitEditor({ kitId, onBack }: { kitId: string; onBack: () => void
                </button>
                <button
                  className="btn ghost danger"
                  onClick={async () => {
                  onClick={() =>
                    void agir(async () => {
                      await api.deleteKitPhase(kitId, ph.id);
                      await reload();
                  }}
                    }, 'Suppression impossible')
                  }
                >
                  {t('Supprimer')}
                </button>
@@ -315,10 +339,12 @@ export function KitEditor({ kitId, onBack }: { kitId: string; onBack: () => void
          </button>
          <button
            className="btn ghost"
            onClick={async () => {
            onClick={() =>
              void agir(async () => {
                await api.installKitReflexes(kitId);
                await reload();
            }}
              }, 'Installation impossible')
            }
          >
            {t('Installer le référentiel fourni')}
          </button>
@@ -359,10 +385,12 @@ export function KitEditor({ kitId, onBack }: { kitId: string; onBack: () => void
                  </button>
                  <button
                    className="btn ghost danger"
                    onClick={async () => {
                    onClick={() =>
                      void agir(async () => {
                        await api.deleteKitReflexSheet(kitId, r.id);
                        await reload();
                    }}
                      }, 'Suppression impossible')
                    }
                  >
                    {t('Supprimer')}
                  </button>