import { useEffect, useRef, useState } from 'react'; import { View, Text, Pressable, Switch, LayoutAnimation, Platform, UIManager, } from 'react-native'; import { Ionicons } from '@expo/vector-icons'; import { MenuView } from '@react-native-menu/menu'; import { getCitiesForBundesland } from '../../lib/germanCities'; import { WheelPickerModal } from '../WheelPickerModal'; import { colors } from '../../lib/theme'; import type { Plan } from '../../hooks/useUserPlan'; // Geburtsjahr-Optionen: 2010 (oldest 13y) → 1920, descending (neueste oben) const BIRTH_YEAR_OPTIONS = Array.from({ length: 91 }, (_, i) => 2010 - i).map((y) => ({ value: y, label: String(y), })); if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) { UIManager.setLayoutAnimationEnabledExperimental(true); } export type Demographics = { birthYear: number | null; gender: string | null; maritalStatus: string | null; employmentStatus: string | null; shiftWork: boolean | null; industry: string | null; jobTenure: string | null; bundesland: string | null; city: string | null; }; type Props = { demographics: Demographics; plan: Plan; expanded?: boolean; defaultExpanded?: boolean; onChange?: (next: Demographics) => void; onRevokeConsent?: () => void; }; const GENDER_OPTIONS: Array<{ label: string; value: string }> = [ { label: 'männlich', value: 'male' }, { label: 'weiblich', value: 'female' }, { label: 'divers', value: 'diverse' }, ]; const MARITAL_OPTIONS: Array<{ label: string; value: string }> = [ { label: 'ledig', value: 'single' }, { label: 'Partnerschaft', value: 'partnership' }, { label: 'verheiratet', value: 'married' }, { label: 'geschieden', value: 'divorced' }, { label: 'verwitwet', value: 'widowed' }, { label: 'keine Angabe', value: 'none' }, ]; const EMPLOYMENT_STATUS_OPTIONS: Array<{ label: string; value: string }> = [ { label: 'angestellt', value: 'employed' }, { label: 'selbständig', value: 'self_employed' }, { label: 'in Ausbildung / Studium', value: 'in_training' }, { label: 'arbeitslos / arbeitssuchend', value: 'unemployed' }, { label: 'pensioniert / im Ruhestand', value: 'retired' }, { label: 'Hausarbeit / Care-Arbeit', value: 'homemaking' }, { label: 'andere', value: 'other' }, ]; const INDUSTRY_OPTIONS: Array<{ label: string; value: string }> = [ { label: 'IT / Software', value: 'it_software' }, { label: 'Pflege / Medizin', value: 'healthcare' }, { label: 'Bildung / Lehre', value: 'education' }, { label: 'Gastronomie / Hotellerie', value: 'hospitality' }, { label: 'Bau / Handwerk', value: 'construction' }, { label: 'Banking / Finance', value: 'banking_finance' }, { label: 'Verkauf / Marketing', value: 'sales_marketing' }, { label: 'Verwaltung / Behörde', value: 'public_admin' }, { label: 'Logistik / Transport', value: 'logistics' }, { label: 'Kreativ / Medien', value: 'creative_media' }, { label: 'andere', value: 'other' }, ]; const JOB_TENURE_OPTIONS: Array<{ label: string; value: string }> = [ { label: 'weniger als 1 Jahr', value: 'less_1y' }, { label: '1-3 Jahre', value: '1_3y' }, { label: '3-5 Jahre', value: '3_5y' }, { label: '5-10 Jahre', value: '5_10y' }, { label: 'mehr als 10 Jahre', value: 'more_10y' }, ]; const BUNDESLAND_OPTIONS: Array<{ label: string; value: string }> = [ { label: 'Baden-Württemberg', value: 'BW' }, { label: 'Bayern', value: 'BY' }, { label: 'Berlin', value: 'BE' }, { label: 'Brandenburg', value: 'BB' }, { label: 'Bremen', value: 'HB' }, { label: 'Hamburg', value: 'HH' }, { label: 'Hessen', value: 'HE' }, { label: 'Mecklenburg-Vorpommern', value: 'MV' }, { label: 'Niedersachsen', value: 'NI' }, { label: 'Nordrhein-Westfalen', value: 'NW' }, { label: 'Rheinland-Pfalz', value: 'RP' }, { label: 'Saarland', value: 'SL' }, { label: 'Sachsen', value: 'SN' }, { label: 'Sachsen-Anhalt', value: 'ST' }, { label: 'Schleswig-Holstein', value: 'SH' }, { label: 'Thüringen', value: 'TH' }, ]; const STATUS_WITH_SHIFT: Array = ['employed', 'self_employed']; const STATUS_WITH_INDUSTRY: Array = ['employed', 'self_employed', 'in_training']; const STATUS_WITH_TENURE: Array = ['employed', 'self_employed']; function relevantFieldCount(d: Demographics): { filled: number; total: number } { const base = [d.birthYear !== null, !!d.gender, !!d.maritalStatus, !!d.employmentStatus, !!d.bundesland, !!d.city]; let filled = base.filter(Boolean).length; let total = base.length; const status = d.employmentStatus; if (status && STATUS_WITH_SHIFT.includes(status)) { total += 1; if (d.shiftWork !== null) filled += 1; } if (status && STATUS_WITH_INDUSTRY.includes(status)) { total += 1; if (!!d.industry) filled += 1; } if (status && STATUS_WITH_TENURE.includes(status)) { total += 1; if (!!d.jobTenure) filled += 1; } return { filled, total }; } function lookupLabel(options: Array<{ label: string; value: string }>, v: string | null) { if (!v) return null; return options.find((o) => o.value === v)?.label ?? v; } function mockPersist(_next: Demographics) {} export function DemographicsAccordion({ demographics, plan, expanded: expandedProp, defaultExpanded = false, onChange, onRevokeConsent, }: Props) { const [expandedLocal, setExpandedLocal] = useState(defaultExpanded); useEffect(() => { if (expandedProp) { LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); setExpandedLocal(true); } }, [expandedProp]); const expanded = expandedLocal; const [local, setLocal] = useState(demographics); // Generic wheel-picker state — alle Demographics-Auswahlfelder rendern via Wheel // (iOS 26 hat ActionSheetIOS rendering geändert → wheel ist konsistenter UX-Pattern) type WheelConfig = { title: string; options: Array<{ value: string | number; label: string }>; value: string | number | null; onSelect: (v: any) => void; }; const [wheelConfig, setWheelConfig] = useState(null); const saveTimer = useRef | null>(null); useEffect(() => { setLocal(demographics); }, [demographics]); function toggle() { LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); setExpandedLocal((v) => !v); } function persist(next: Demographics) { setLocal(next); if (saveTimer.current) clearTimeout(saveTimer.current); saveTimer.current = setTimeout(() => { mockPersist(next); onChange?.(next); }, 500); } function flushSave(next: Demographics) { if (saveTimer.current) clearTimeout(saveTimer.current); mockPersist(next); onChange?.(next); setLocal(next); } const { filled, total } = relevantFieldCount(local); const completed = filled === total; const showProTrialBanner = plan === 'free'; const progressRatio = total > 0 ? filled / total : 0; const showShiftWork = !!local.employmentStatus && STATUS_WITH_SHIFT.includes(local.employmentStatus); const showIndustry = !!local.employmentStatus && STATUS_WITH_INDUSTRY.includes(local.employmentStatus); const showJobTenure = !!local.employmentStatus && STATUS_WITH_TENURE.includes(local.employmentStatus); return ( ({ opacity: pressed ? 0.7 : 1 })} > ANONYMER BEITRAG ZUR FORSCHUNG Optional. Niemals mit Name oder Email verknüpft. Jederzeit löschbar. {completed ? ( Danke dass du ReBreak vertraust ) : ( {filled}/{total} )} {expanded ? ( {showProTrialBanner ? ( {completed ? 'Du bekommst 1 Woche Pro geschenkt' : 'Vervollständige dein Profil — 1 Woche Pro geschenkt'} Mit deinen anonymen Daten machen wir rebreak zur ersten DiGA-zertifizierten Spielsucht-App. Als Dankeschön: 1 Woche Pro. ) : null} setWheelConfig({ title: 'Geburtsjahr', options: BIRTH_YEAR_OPTIONS, value: local.birthYear, onSelect: (v) => flushSave({ ...local, birthYear: v as number }), }) } /> {/* ≤3 Optionen → UIMenu (anchored Pull-Down). Apple HIG-konform. */} ({ id: opt.value, title: opt.label, state: opt.value === local.gender ? 'on' : 'off', }))} onPressAction={({ nativeEvent: { event } }) => flushSave({ ...local, gender: event }) } shouldOpenOnLongPress={false} > ({ opacity: pressed ? 0.6 : 1 })} > {lookupLabel(GENDER_OPTIONS, local.gender) ?? 'auswählen'} setWheelConfig({ title: 'Familienstand', options: MARITAL_OPTIONS, value: local.maritalStatus, onSelect: (v) => flushSave({ ...local, maritalStatus: v as string }), }) } /> {/* Beruf-Section */} BERUF setWheelConfig({ title: 'Berufs-Status', options: EMPLOYMENT_STATUS_OPTIONS, value: local.employmentStatus, onSelect: (raw) => { const v = raw as string; const next = { ...local, employmentStatus: v, shiftWork: STATUS_WITH_SHIFT.includes(v) ? local.shiftWork : null, industry: STATUS_WITH_INDUSTRY.includes(v) ? local.industry : null, jobTenure: STATUS_WITH_TENURE.includes(v) ? local.jobTenure : null, }; flushSave(next); }, }) } /> {showShiftWork ? ( {local.shiftWork === null ? 'k.A.' : local.shiftWork ? 'Ja' : 'Nein'} flushSave({ ...local, shiftWork: v })} trackColor={{ false: '#e5e5e5', true: colors.brandOrange }} thumbColor="#ffffff" /> ) : null} {showIndustry ? ( setWheelConfig({ title: 'Branche', options: INDUSTRY_OPTIONS, value: local.industry, onSelect: (v) => flushSave({ ...local, industry: v as string }), }) } /> ) : null} {showJobTenure ? ( setWheelConfig({ title: 'Im aktuellen Job seit', options: JOB_TENURE_OPTIONS, value: local.jobTenure, onSelect: (v) => flushSave({ ...local, jobTenure: v as string }), }) } /> ) : null} {/* Wohnort-Section */} WOHNORT setWheelConfig({ title: 'Bundesland', options: BUNDESLAND_OPTIONS, value: local.bundesland, onSelect: (raw) => { const v = raw as string; flushSave({ ...local, bundesland: v, city: local.bundesland !== v ? null : local.city }); }, }) } /> {/* Stadt nur sichtbar wenn Bundesland gewählt */} {local.bundesland ? ( setWheelConfig({ title: 'Stadt', options: getCitiesForBundesland(local.bundesland).map((c) => ({ value: c, label: c })), value: local.city, onSelect: (v) => flushSave({ ...local, city: v as string }), }) } /> ) : null} ({ opacity: pressed ? 0.7 : 1 })} > Einwilligung widerrufen ) : null} } value={wheelConfig?.value ?? null} onSelect={(v) => wheelConfig?.onSelect(v)} onClose={() => setWheelConfig(null)} /> ); } const inputStyle = { fontSize: 14, color: colors.text, fontFamily: 'Nunito_600SemiBold', paddingVertical: 8, paddingHorizontal: 10, backgroundColor: '#fafafa', borderRadius: 8, borderWidth: 1, borderColor: '#ececec', minWidth: 140, textAlign: 'right' as const, }; function FieldRow({ label, why, isLast, indent, hideWhy, filled, children, }: { label: string; why: string; isLast?: boolean; indent?: boolean; hideWhy?: boolean; filled: boolean; children: React.ReactNode; }) { return ( {label} {children} {!hideWhy && why ? ( {why} ) : null} ); } function SelectButton({ value, onPress }: { value: string | null; onPress: () => void }) { return ( ({ opacity: pressed ? 0.6 : 1 })} > {/* Value als Chip */} {value ?? 'auswählen'} {/* Chevron-right am Ende, separat vom Chip */} ); }