Einzelne Gruppenmitglieder, Austritt per Redesign, Ladebalken beim Abgleich
Bei einem CC mit mehreren Nutzern gibt es zwei Ebenen, und bisher liess sich nur die obere bedienen. Jetzt beide: Ein Spieler kann seinen Anteil redesignen und steigt damit aus - er bekommt einen eigenen CC, die Gruppe laeuft unberuehrt weiter. Fuer ihn ist es ein Tausch wie jeder andere: der Gruppenplatz geht weg, der eigene CC kommt an seine Stelle, es kommt keiner hinzu. Wohin er gewechselt ist, bleibt an der Mitgliedschaft festgehalten und steht bei den frueheren Nutzern. Das Redesign der ganzen Gruppe, bei dem alle mitwandern, gibt es unveraendert daneben. Ebenso koennen einzelne Nutzer ausfallen, ohne die uebrigen zu beruehren: bei einem Trio kann einer im Rollenspiel sterben, waehrend die anderen weiterspielen, und ebenso koennen alle nacheinander wegfallen. Grund und Begruendung haengen jetzt auch an der Mitgliedschaft. Die Rueckkehr folgt denselben Regeln wie beim eigenen CC - geputzt gratis, entzogen gegen Guthaben, gestorben nur durch einen Administrator mit Begruendung. Wer ausgefallen ist, sieht seinen CC weiter auf der Spielerseite; sonst gaebe es nichts, worueber er zurueckkehren koennte. Ein freiwilliger Austritt bleibt davon getrennt: dort gibt es nichts zurueckzuholen, ein Wiedereintritt kostet neu. Der GExtension-Abgleich hat denselben Ladebalken wie der Forumslauf bekommen, fuer alle Spieler wie fuer einzelne, samt Protokoll. Beide teilen sich jetzt dieselbe Anzeige. Dabei aufgefallen: Zeitplan und Handklick konnten denselben Abgleich doppelt starten und haetten dieselben Profile zweimal abgerufen. Die Sperre sitzt jetzt im Lauf selbst statt im Starter, damit sie fuer beide Wege gilt.
This commit is contained in:
@@ -113,6 +113,12 @@ export interface CharacterMember {
|
||||
paidWith: CreditKind | null;
|
||||
/// Der eigene CC, den der Spieler für den Platz aufgegeben hat.
|
||||
predecessorId: number | null;
|
||||
/// Der CC, in den er von hier aus gewechselt ist.
|
||||
successorId: number | null;
|
||||
successor?: CharacterRef | null;
|
||||
/// Warum er den CC nicht mehr nutzt. Leer bei einem freiwilligen Austritt.
|
||||
inactiveReason: InactiveReason | null;
|
||||
inactiveNote: string | null;
|
||||
note: string | null;
|
||||
}
|
||||
|
||||
@@ -347,3 +353,15 @@ export interface PendingOverview {
|
||||
withoutPlayer: number;
|
||||
players: { playerId: number; displayName: string; count: number }[];
|
||||
}
|
||||
|
||||
export interface ScrapeState {
|
||||
running: boolean;
|
||||
startedAt: string | null;
|
||||
finishedAt: string | null;
|
||||
currentPlayer: string | null;
|
||||
playersRead: number;
|
||||
totalPlayers: number;
|
||||
log: string[];
|
||||
summary: ScrapeSummary | null;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { ApiError, api } from './api';
|
||||
import type { ScrapeState, ScrapeSummary } from './types';
|
||||
|
||||
/** Was am Ende eines Laufs in der Meldung steht. */
|
||||
export function scrapeBericht(result: ScrapeSummary): string {
|
||||
const parts = [`${result.scannedPlayers} Profile geprüft`];
|
||||
if (result.newRows > 0) parts.push(`${result.newRows} neue Pakete gefunden`);
|
||||
if (result.awaitingConfirmation > 0) parts.push(`${result.awaitingConfirmation} warten schon`);
|
||||
if (result.unclear > 0) parts.push(`${result.unclear} ungeklärt`);
|
||||
if (result.duplicates > 0) parts.push(`${result.duplicates} schon gebucht`);
|
||||
if (result.failedPlayers.length > 0) {
|
||||
parts.push(`${result.failedPlayers.length} nicht erreichbar`);
|
||||
}
|
||||
|
||||
const offen = result.newRows + result.awaitingConfirmation;
|
||||
return (
|
||||
parts.join(', ') +
|
||||
(offen > 0 ? '. Beim Spieler oder unter Import buchen, damit die Guthaben entstehen.' : '.')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stößt einen GExtension-Abgleich an und verfolgt ihn.
|
||||
*
|
||||
* Der Lauf geht über dutzende Profile, zwischen denen gewartet wird - er
|
||||
* läuft deshalb im Hintergrund weiter, auch wenn die Seite gewechselt wird.
|
||||
* Hier wird nur nachgefragt, wie weit er ist.
|
||||
*/
|
||||
export function useScrape(options: {
|
||||
onDone: (bericht: string) => void;
|
||||
onError: (message: string) => void;
|
||||
onFinished?: () => void | Promise<void>;
|
||||
}) {
|
||||
const { onDone, onError, onFinished } = options;
|
||||
const [state, setState] = useState<ScrapeState | null>(null);
|
||||
const [running, setRunning] = useState(false);
|
||||
|
||||
// Ein Lauf aus einer anderen Sitzung - oder vom Zeitplan - soll hier
|
||||
// ebenfalls sichtbar sein.
|
||||
useEffect(() => {
|
||||
void (async () => {
|
||||
try {
|
||||
const aktuell = await api<ScrapeState>('/api/scrape/status');
|
||||
setState(aktuell);
|
||||
if (aktuell.running) setRunning(true);
|
||||
} catch {
|
||||
// Ohne Stand bleibt der Knopf einfach bedienbar.
|
||||
}
|
||||
})();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!running) return;
|
||||
|
||||
const abfragen = async () => {
|
||||
try {
|
||||
const aktuell = await api<ScrapeState>('/api/scrape/status');
|
||||
setState(aktuell);
|
||||
if (aktuell.running) return;
|
||||
|
||||
setRunning(false);
|
||||
if (aktuell.error) onError(aktuell.error);
|
||||
else if (aktuell.summary) onDone(scrapeBericht(aktuell.summary));
|
||||
await onFinished?.();
|
||||
} catch (err) {
|
||||
setRunning(false);
|
||||
onError(err instanceof ApiError ? err.message : 'Abgleich fehlgeschlagen');
|
||||
}
|
||||
};
|
||||
|
||||
const timer = window.setInterval(() => void abfragen(), 1500);
|
||||
return () => window.clearInterval(timer);
|
||||
}, [running, onDone, onError, onFinished]);
|
||||
|
||||
const start = useCallback(
|
||||
async (pfad: string) => {
|
||||
try {
|
||||
setState(await api<ScrapeState>(pfad, { method: 'POST', body: {} }));
|
||||
setRunning(true);
|
||||
} catch (err) {
|
||||
onError(err instanceof ApiError ? err.message : 'Abgleich fehlgeschlagen');
|
||||
}
|
||||
},
|
||||
[onError],
|
||||
);
|
||||
|
||||
return { state, running, start };
|
||||
}
|
||||
Reference in New Issue
Block a user