feat(admin): Tierliste zeigt alle erreichten Raenge

found.md: Admin -> Tiere zeigte nur den hoechsten Rang (Cache referralRankId).
Die Liste laedt jetzt alle ReferralRankAchievements des Owners (nach Schwelle)
und zeigt sie als Symbole, unabhaengig vom Vorteil NAME_BADGE; entzogene
Raenge ausgegraut, Tooltip mit 'manuell vergeben' / 'entzogen'.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-10-04 15:01:51 +02:00
1 parent 2bd4b2d973
commit dbc416c43c
3 files changed
+46 -2

No files matched your search

+2 -2
View File
@@ -9,7 +9,7 @@ import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { PetAvatar } from "@/components/ui/pet-avatar";
import { ReferralRankBadge } from "@/components/referrals/ReferralRankBadge";
import { AchievedRankBadges } from "@/components/admin/pets/AchievedRankBadges";
import { PetDetailSheet } from "@/components/admin/pets/PetDetailSheet";
type Filter = "all" | "verified" | "founding" | "ranked" | "bannedOwner";
@@ -135,7 +135,7 @@ export default function AdminPetsPage() {
</span>
{pet.isVerified && <BadgeCheck className="h-3.5 w-3.5 fill-blue-500 text-background" aria-label="Verifiziert" />}
{pet.isFoundingMember && <Sprout className="h-3.5 w-3.5 text-emerald-600" aria-label="Gründungsmitglied" />}
<ReferralRankBadge owner={pet.owner} />
<AchievedRankBadges achievements={pet.owner.rankAchievements} />
</button>
</td>
<td className="px-3 py-2 text-muted-foreground">{pet.species.name}</td>
@@ -0,0 +1,39 @@
"use client";
import { useReferralRanks } from "@/hooks/useReferralRanks";
import { ReferralIcon } from "@/components/referrals/ReferralRankBadge";
type Achievement = { rankId: string; revokedAt: Date | string | null; grantedManually: boolean };
/**
* Admin → Tiere: alle Ränge, die der Owner je erreicht hat (found.md — vorher
* nur der höchste). Unabhängig vom Vorteil NAME_BADGE, weil das hier eine
* Übersicht fürs Team ist. Entzogene Ränge bleiben ausgegraut sichtbar.
*/
export function AchievedRankBadges({ achievements }: { achievements: Achievement[] }) {
const { byId } = useReferralRanks();
const items = achievements.flatMap((achievement) => {
const rank = byId.get(achievement.rankId);
return rank ? [{ achievement, rank }] : [];
});
if (items.length === 0) return null;
return (
<span className="inline-flex shrink-0 items-center gap-0.5">
{items.map(({ achievement, rank }) => {
const isRevoked = !!achievement.revokedAt;
const status = isRevoked ? " · entzogen" : achievement.grantedManually ? " · manuell vergeben" : "";
return (
<span key={rank.id} title={`${rank.name}${status}`} className={isRevoked ? "opacity-30" : undefined}>
<ReferralIcon
icon={rank.icon}
className="h-3.5 w-3.5"
style={{ color: rank.color, fill: isRevoked ? "none" : rank.color }}
aria-label={`${rank.name}${status}`}
/>
</span>
);
})}
</span>
);
}
+5
View File
@@ -97,6 +97,11 @@ export const petAdminRouter = router({
sponsorSince: true,
sponsorPeriodEnd: true,
ban: { select: { expiresAt: true } },
// found.md: die Liste zeigt alle erreichten Ränge, nicht nur den höchsten (Cache referralRankId).
rankAchievements: {
select: { rankId: true, revokedAt: true, grantedManually: true },
orderBy: { rank: { threshold: "asc" } },
},
},
},
_count: { select: { posts: true, followers: true } },