// Lista performerów + tap → PerformerScenes. // Domyślny order: scene_count desc (najpierw popularni); search po name_normalized. import { useNavigation } from '@react-navigation/native'; import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { useQuery } from '@tanstack/react-query'; import React, { useState } from 'react'; import { ActivityIndicator, FlatList, Pressable, StyleSheet, Text, TextInput, View, } from 'react-native'; import { useClient } from '../ClientContext'; import type { RootStackParamList } from '../navigation'; import { theme } from '../theme'; import type { PerformerCount } from '../types'; type Order = 'scene_count' | 'name'; export function PerformersScreen() { const client = useClient(); const navigation = useNavigation>(); const [q, setQ] = useState(''); const [debouncedQ, setDebouncedQ] = useState(''); const [order, setOrder] = useState('scene_count'); const [searchFocused, setSearchFocused] = useState(false); React.useEffect(() => { const t = setTimeout(() => setDebouncedQ(q), 350); return () => clearTimeout(t); }, [q]); const { data, isLoading, error, refetch, isRefetching } = useQuery({ queryKey: ['performers', debouncedQ, order], queryFn: () => client.listPerformers({ q: debouncedQ || undefined, order, per_page: 200 }), }); const total = data?.items.length ?? 0; return ( Performers {total} tap a row → all their scenes setSearchFocused(true)} onBlur={() => setSearchFocused(false)} placeholder="search performer…" placeholderTextColor={theme.mutedDim} autoCapitalize="none" /> setOrder('scene_count')} label="Top" /> setOrder('name')} label="A–Z" /> {isLoading && } {error instanceof Error && {error.message}} p.id} renderItem={({ item }) => ( navigation.navigate('PerformerScenes', { id: item.id, name: item.canonical_name, }) } /> )} refreshing={isRefetching} onRefresh={refetch} ListEmptyComponent={ !isLoading ? no performers : null } contentContainerStyle={{ paddingBottom: 24 }} /> ); } function SegButton({ active, onPress, label, }: { active: boolean; onPress: () => void; label: string; }) { return ( {label} ); } function initials(name: string): string { const parts = name.trim().split(/\s+/); if (parts.length === 0) return '?'; const first = parts[0][0] ?? ''; const last = parts.length > 1 ? parts[parts.length - 1][0] ?? '' : ''; return (first + last).toUpperCase() || '?'; } function PerformerRow({ performer, onPress, }: { performer: PerformerCount; onPress: () => void; }) { return ( [styles.row, pressed && styles.rowPressed]} onPress={onPress} > {initials(performer.canonical_name)} {performer.canonical_name} {performer.scene_count} {performer.scene_count === 1 ? 'scene' : 'scenes'} {performer.gender ? ( {performer.gender} ) : null} ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: theme.bg, paddingHorizontal: 16, paddingTop: 12 }, headerRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingBottom: 4, }, headerLabel: { color: theme.muted, fontSize: 12, textTransform: 'uppercase', letterSpacing: 1.2, fontWeight: '700', }, headerCount: { color: theme.fg, fontSize: 22, fontWeight: '800' }, hint: { color: theme.mutedDim, fontSize: 11, marginBottom: 12 }, toolbar: { flexDirection: 'row', gap: 12, marginBottom: 10 }, search: { flex: 1, backgroundColor: theme.card, borderColor: theme.border, borderWidth: 1.5, borderRadius: 12, color: theme.fg, padding: 12, fontSize: 16, }, searchFocused: { borderColor: theme.borderFocus }, segment: { flexDirection: 'row', backgroundColor: theme.bgElevated, borderColor: theme.border, borderWidth: 1, borderRadius: 12, padding: 4, marginBottom: 14, alignSelf: 'flex-start', }, segButton: { paddingHorizontal: 14, paddingVertical: 6, borderRadius: 8, }, segButtonActive: { backgroundColor: theme.accent, shadowColor: theme.accent, shadowOffset: { width: 0, height: 0 }, shadowOpacity: 0.4, shadowRadius: 6, elevation: 2, }, segButtonText: { color: theme.muted, fontWeight: '700', fontSize: 13, }, segButtonTextActive: { color: theme.fg }, row: { flexDirection: 'row', alignItems: 'center', gap: 12, backgroundColor: theme.card, borderColor: theme.border, borderWidth: 1, borderRadius: 14, paddingHorizontal: 14, paddingVertical: 12, marginBottom: 10, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.2, shadowRadius: 4, elevation: 3, }, rowPressed: { backgroundColor: theme.bgElevated, borderColor: theme.borderFocus }, avatar: { width: 40, height: 40, borderRadius: 20, backgroundColor: theme.bgElevated, borderColor: theme.border, borderWidth: 1, alignItems: 'center', justifyContent: 'center', }, avatarText: { color: theme.accentGlow, fontWeight: '800', fontSize: 13, letterSpacing: 0.5, }, rowContent: { flex: 1 }, rowTitle: { color: theme.fg, fontWeight: '700', fontSize: 16, marginBottom: 4 }, rowMetaRow: { flexDirection: 'row', alignItems: 'center', gap: 8 }, rowCount: { color: theme.muted, fontSize: 13 }, genderPill: { backgroundColor: `${theme.accentSecondary}1F`, borderColor: `${theme.accentSecondary}55`, borderWidth: 1, borderRadius: 8, paddingHorizontal: 8, paddingVertical: 2, }, genderPillText: { color: theme.accentSecondary, fontSize: 11, fontWeight: '700', textTransform: 'uppercase', letterSpacing: 0.6, }, chevron: { color: theme.mutedDim, fontSize: 22, fontWeight: '300', }, emptyText: { color: theme.muted, textAlign: 'center', marginTop: 48, fontSize: 16 }, error: { color: theme.bad, padding: 16 }, });