K oder R an den Kettenpfeilen anzeigen

This commit is contained in:
2026-09-22 10:15:18 +02:00
parent 823439f28d
commit 9b024d343d
4 changed files with 100 additions and 8 deletions
+24 -1
View File
@@ -633,7 +633,7 @@ export async function revertCharacter(
* überhaupt zu sehen ist. * überhaupt zu sehen ist.
*/ */
export async function listCharacters(playerId: number) { export async function listCharacters(playerId: number) {
return prisma.character.findMany({ const ccs = await prisma.character.findMany({
where: { members: { some: { playerId } } }, where: { members: { some: { playerId } } },
orderBy: [{ lineageId: 'asc' }, { chainPosition: 'asc' }, { createdAt: 'asc' }], orderBy: [{ lineageId: 'asc' }, { chainPosition: 'asc' }, { createdAt: 'asc' }],
include: { include: {
@@ -657,6 +657,29 @@ export async function listCharacters(playerId: number) {
}, },
}, },
}); });
// Womit dieser Spieler den jeweiligen Eintrag bezahlt hat. In der Kette
// steht das am Übergang: der Pfeil zum nächsten Glied trägt die Art, mit
// der dieses Glied bezahlt wurde.
const buchungen = await prisma.creditLedger.findMany({
where: {
characterId: { in: ccs.map((c) => c.id) },
playerId,
source: CreditSource.CONSUME,
reversedBy: null,
},
select: { characterId: true, kind: true },
orderBy: { id: 'desc' },
});
const artNach = new Map<number, CreditKind>();
for (const buchung of buchungen) {
if (buchung.characterId !== null && !artNach.has(buchung.characterId)) {
artNach.set(buchung.characterId, buchung.kind);
}
}
return ccs.map((cc) => ({ ...cc, fundedWith: artNach.get(cc.id) ?? null }));
} }
+34 -7
View File
@@ -312,9 +312,9 @@ export default function ChainView({
{i < vonObenNachUnten.length - 1 && ( {i < vonObenNachUnten.length - 1 && (
<> <>
<div className="chain-arrow" aria-hidden="true"> {/* Der Pfeil führt zu diesem Eintrag - also trägt er,
⬆ womit dieser Schritt bezahlt wurde. */}
</div> <Uebergang character={character} />
{zone( {zone(
kette.lineageId, kette.lineageId,
vonObenNachUnten[i + 1]?.id ?? null, vonObenNachUnten[i + 1]?.id ?? null,
@@ -327,11 +327,9 @@ export default function ChainView({
{zone(kette.lineageId, null, `${kette.lineageId}-unten`)} {zone(kette.lineageId, null, `${kette.lineageId}-unten`)}
{kette.herkunft.map((frueher) => ( {kette.herkunft.map((frueher, i) => (
<div key={frueher.id}> <div key={frueher.id}>
<div className="chain-arrow" aria-hidden="true"> <Uebergang character={i === 0 ? kette.glieder[0] : kette.herkunft[i - 1]} />
⬆
</div>
<ChainEntry <ChainEntry
character={frueher} character={frueher}
playerId={playerId} playerId={playerId}
@@ -393,6 +391,35 @@ export default function ChainView({
); );
} }
/// Kurzzeichen für die Bezahlart eines Übergangs.
const UEBERGANG: Record<string, { zeichen: string; titel: string; klasse: string }> = {
REDESIGN_KULANZ: { zeichen: 'K', titel: 'Kulanz-Redesign', klasse: 'is-kulanz' },
REDESIGN_PAID: { zeichen: 'R', titel: 'Bezahltes Redesign', klasse: 'is-paid' },
CC: { zeichen: 'CC', titel: 'Aus dem CC-Topf bezahlt', klasse: 'is-cc' },
};
/**
* Der Pfeil zum nächsten Glied, mit dem Kurzzeichen der Bezahlart.
*
* Womit ein Schritt bezahlt wurde, gehört an den Schritt - nicht in die
* Karte darüber. So liest sich die Kette in einem Zug: Eintrag, womit,
* Eintrag.
*/
function Uebergang({ character }: { character: Character | undefined }) {
const art = character?.fundedWith ? UEBERGANG[character.fundedWith] : undefined;
return (
<div className="chain-arrow">
<span aria-hidden="true">⬆</span>
{art && (
<span className={`chain-step ${art.klasse}`} title={art.titel}>
{art.zeichen}
</span>
)}
</div>
);
}
function ChainEntry({ function ChainEntry({
character, character,
playerId, playerId,
+2
View File
@@ -149,6 +149,8 @@ export interface Character {
successors: CharacterRef[]; successors: CharacterRef[];
forumThreads?: ThreadRef[]; forumThreads?: ThreadRef[];
links?: CharacterLink[]; links?: CharacterLink[];
/// Womit dieser Eintrag bezahlt wurde, aus Sicht des betrachteten Spielers.
fundedWith?: CreditKind | null;
members?: CharacterMember[]; members?: CharacterMember[];
createdAt: string; createdAt: string;
} }
+40
View File
@@ -1171,6 +1171,7 @@ textarea {
/* Der Pfeil zeigt zum jüngeren Eintrag, also nach oben. Er ist bewusst /* Der Pfeil zeigt zum jüngeren Eintrag, also nach oben. Er ist bewusst
deutlich: er ist das Einzige, was die Leserichtung der Kette verrät. */ deutlich: er ist das Einzige, was die Leserichtung der Kette verrät. */
.chain-arrow { .chain-arrow {
position: relative;
text-align: center; text-align: center;
color: var(--accent); color: var(--accent);
font-size: 1.4rem; font-size: 1.4rem;
@@ -1287,3 +1288,42 @@ textarea {
padding-left: 0.6rem; padding-left: 0.6rem;
padding-right: 0.6rem; padding-right: 0.6rem;
} }
/* Das Kurzzeichen sitzt am Pfeil, nicht in der Karte: womit ein Schritt
bezahlt wurde, gehört an den Schritt. Absolut gesetzt, damit die Pfeile
aller Ketten in einer Flucht bleiben. */
.chain-step {
position: absolute;
top: 50%;
left: 50%;
transform: translate(0.75rem, -50%);
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 1.35rem;
height: 1.35rem;
padding: 0 0.3rem;
border: 1px solid var(--border-strong);
border-radius: 999px;
background: var(--bg);
font-size: 0.72rem;
font-weight: 600;
line-height: 1;
letter-spacing: 0;
}
.chain-step.is-kulanz {
border-color: var(--positive);
color: var(--positive);
}
.chain-step.is-paid {
border-color: var(--accent);
color: var(--accent);
}
/* Aus dem CC-Topf bezahlt - die Ausnahme, also auffälliger. */
.chain-step.is-cc {
border-color: var(--warn);
color: var(--warn);
}