feat: implement jury member selection and sorting functionality, update related database migrations
This commit is contained in:
@@ -7,6 +7,7 @@ import { useIsMobile } from '@/spa/hooks/useIsMobile';
|
||||
import { useCmsCollection, useCmsRoute, type CmsRouteDoc } from '@/spa/cmsRoute';
|
||||
import { mediaAlt, mediaUrl } from '@/spa/cmsMediaField';
|
||||
import { homeContent } from '@/spa/homeContent';
|
||||
import { selectJuryDocs, sortJuryMembersQuoteFirst } from '@/spa/juryMembers';
|
||||
import { netzwerkContent } from '@/spa/netzwerkContent';
|
||||
import Image from '@/spa/components/ui/UnoptimizedImage'
|
||||
import { PartnerLogoMark, type PartnerLogoData } from '@/spa/components/ui/partner-logo';
|
||||
@@ -16,8 +17,9 @@ const FB = '"Inter", sans-serif';
|
||||
const NAVY = '#111D55';
|
||||
const GOLD = '#EFBF04';
|
||||
|
||||
type NetzwerkCms = Partial<typeof netzwerkContent> & {
|
||||
type NetzwerkCms = Omit<Partial<typeof netzwerkContent>, 'hero' | 'jury' | 'sponsoring'> & {
|
||||
hero?: Partial<typeof netzwerkContent.hero> & { image?: unknown }
|
||||
jury?: Partial<Omit<typeof netzwerkContent.jury, 'members'>> & { members?: unknown[] | null }
|
||||
sponsoring?: Partial<typeof netzwerkContent.sponsoring> & { image?: unknown }
|
||||
}
|
||||
|
||||
@@ -76,14 +78,15 @@ const normalizeJuryMember = (member: CmsRouteDoc): JuryMember => {
|
||||
const organization = fallbackText(member.organization, '')
|
||||
const rawRole = fallbackText(member.role, '')
|
||||
const role = rawRole && organization && rawRole !== organization ? `${rawRole} / ${organization}` : fallbackText(rawRole, organization)
|
||||
const image = member.image ?? member.imageUpload
|
||||
|
||||
return {
|
||||
name: fallbackText(member.name, ''),
|
||||
role,
|
||||
bio: fallbackText(member.bio, ''),
|
||||
image: mediaUrl(member.image, juryImageFallback),
|
||||
image: mediaUrl(image, juryImageFallback),
|
||||
imageAlt: fallbackText(member.imageAlt, fallbackText(member.name, 'Jury-Mitglied')),
|
||||
imageUpload: member.image,
|
||||
imageUpload: image,
|
||||
organization,
|
||||
quote: fallbackText(member.quote, ''),
|
||||
sortOrder: typeof member.sortOrder === 'number' ? member.sortOrder : undefined,
|
||||
@@ -91,18 +94,6 @@ const normalizeJuryMember = (member: CmsRouteDoc): JuryMember => {
|
||||
}
|
||||
}
|
||||
const hasJuryText = (value: unknown) => typeof value === 'string' && value.trim().length > 0
|
||||
const hasCompleteJuryProfile = (member: JuryMember) => hasJuryText(member.bio) && hasJuryText(member.quote)
|
||||
const sortedJuryMembers = (members: JuryMember[]) =>
|
||||
members
|
||||
.map((member, index) => ({ member, index }))
|
||||
.sort((a, b) => {
|
||||
const completenessOrder = Number(hasCompleteJuryProfile(b.member)) - Number(hasCompleteJuryProfile(a.member))
|
||||
if (completenessOrder !== 0) return completenessOrder
|
||||
|
||||
const sortOrder = Number(a.member.sortOrder ?? a.index) - Number(b.member.sortOrder ?? b.index)
|
||||
return sortOrder !== 0 ? sortOrder : a.index - b.index
|
||||
})
|
||||
.map(({ member }) => member)
|
||||
function Lines({ text, highlight }: { text: string; highlight?: string }) {
|
||||
return (
|
||||
<>
|
||||
@@ -494,10 +485,16 @@ const Netzwerk: React.FC = () => {
|
||||
const partnerModal = { ...netzwerkContent.partners.modal, ...((cms.partners as typeof netzwerkContent.partners | undefined)?.modal || {}) };
|
||||
const sponsoring = { ...netzwerkContent.sponsoring, ...(cms.sponsoring || {}) };
|
||||
const patronPeople = fallbackArray<PatronPerson>(patronage.people, netzwerkContent.patronage.people);
|
||||
const juryCollectionMembers = cmsJuryMembers.map(normalizeJuryMember).filter((member) => member.active !== false && member.name);
|
||||
const juryMembers = sortedJuryMembers(juryCollectionMembers.length
|
||||
? juryCollectionMembers
|
||||
: fallbackArray<JuryMember>(jury.members, netzwerkContent.jury.members));
|
||||
const cmsJurySelection = cms.jury?.members;
|
||||
const hasCmsJurySelection = cmsJurySelection !== undefined;
|
||||
const sourcedJuryDocs = selectJuryDocs(cmsJurySelection, cmsJuryMembers);
|
||||
const normalizedJuryMembers = sourcedJuryDocs
|
||||
.map(normalizeJuryMember)
|
||||
.filter((member) => member.active !== false && member.name);
|
||||
const jurySourceMembers: JuryMember[] = normalizedJuryMembers.length || hasCmsJurySelection
|
||||
? normalizedJuryMembers
|
||||
: netzwerkContent.jury.members;
|
||||
const juryMembers = sortJuryMembersQuoteFirst(jurySourceMembers);
|
||||
const expertiseRows = fallbackArray<ExpertiseRow>(expertise.rows, netzwerkContent.expertise.rows);
|
||||
const partners = sortedPartners(cmsPartners)
|
||||
.filter((partner) => partner.active !== false)
|
||||
@@ -710,9 +707,11 @@ const Netzwerk: React.FC = () => {
|
||||
</div>
|
||||
{/* ── Prominenter Vorsitz-Bereich ─────────────────────────────────── */}
|
||||
{/* TODO: echten Jury-Vorsitzenden bestätigen (Name & Foto sind Platzhalter) */}
|
||||
<div style={{ padding: isMobile ? '28px 24px 8px' : '64px 80px 16px' }}>
|
||||
<JuryChairCard member={juryMembers[0]} badge={fallbackText(jury.chairBadge, netzwerkContent.jury.chairBadge)} />
|
||||
</div>
|
||||
{juryMembers.length ? (
|
||||
<div style={{ padding: isMobile ? '28px 24px 8px' : '64px 80px 16px' }}>
|
||||
<JuryChairCard member={juryMembers[0]} badge={fallbackText(jury.chairBadge, netzwerkContent.jury.chairBadge)} />
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{/* ── Übrige Jury-Mitglieder ──────────────────────────────────────── */}
|
||||
<div style={{ display: isMobile ? 'flex' : 'grid', gridTemplateColumns: isMobile ? undefined : 'repeat(auto-fill, minmax(260px, 1fr))', overflowX: isMobile ? 'auto' : undefined, scrollSnapType: isMobile ? 'x mandatory' : undefined, gap: isMobile ? 12 : 0, padding: isMobile ? '24px 24px 36px' : '0 0 56px', WebkitOverflowScrolling: 'touch', scrollbarWidth: 'none', borderTop: isMobile ? 'none' : '1px solid rgba(255,255,255,0.07)' }}>
|
||||
|
||||
Reference in New Issue
Block a user