Files
info-target-mobile/components/ui/CategorySelect.tsx
Samandar Turgunboyev 124798419b fitst commit
2026-01-28 18:26:50 +05:00

256 lines
6.3 KiB
TypeScript

import { useTheme } from '@/components/ThemeContext';
import { products_api } from '@/screens/home/lib/api';
import { useMutation, useQuery } from '@tanstack/react-query';
import { AxiosError } from 'axios';
import { ChevronLeft, ChevronRight } from 'lucide-react-native';
import React, { Dispatch, SetStateAction, useState } from 'react';
import {
ActivityIndicator,
FlatList,
StyleSheet,
Text,
TouchableOpacity,
View,
} from 'react-native';
interface Category {
id: number;
name: string;
code: string;
external_id: string | null;
level: number;
is_leaf: boolean;
icon_name: string | null;
}
interface CategoryResponse {
data: {
data: Category[];
};
}
interface Props {
selectedCategories: Category | null;
setSelectedCategories: Dispatch<SetStateAction<Category | null>>;
}
interface HistoryItem {
parentId: number | null;
categories: Category[];
}
export default function CategorySelect({ selectedCategories, setSelectedCategories }: Props) {
const { isDark } = useTheme();
const [currentCategories, setCurrentCategories] = useState<Category[]>([]);
const [currentParentId, setCurrentParentId] = useState<number | null>(null);
const [history, setHistory] = useState<HistoryItem[]>([]);
// Root categories
const { isLoading: rootLoading, error: rootError } = useQuery<CategoryResponse>({
queryKey: ['categories'],
queryFn: async () => products_api.getCategorys(),
select(data) {
setCurrentCategories(data.data.data);
setCurrentParentId(null);
setHistory([]);
return data;
},
});
// Child categories
const { mutate, isPending: mutatePending } = useMutation({
mutationFn: (id: number) => products_api.getCategorys({ parent: id }),
onSuccess: (response: CategoryResponse, id) => {
const childCategories = response.data.data;
setHistory((prev) => [...prev, { parentId: currentParentId, categories: currentCategories }]);
setCurrentCategories(childCategories);
setCurrentParentId(id);
},
onError: (err: AxiosError) => {
console.error('Child category loading error:', err);
},
});
const toggleCategory = (category: Category) => {
if (category.is_leaf) {
setSelectedCategories(category);
} else {
mutate(category.id);
}
};
const goBack = () => {
if (history.length > 0) {
const previous = history[history.length - 1];
setCurrentCategories(previous.categories);
setCurrentParentId(previous.parentId);
setHistory((prev) => prev.slice(0, -1));
}
};
const isLoading = rootLoading || mutatePending;
const error = rootError;
const renderCategory = ({ item: category }: { item: Category }) => {
const isSelected = selectedCategories?.id === category.id;
return (
<TouchableOpacity
style={[
styles.chip,
isDark ? styles.darkChip : styles.lightChip,
isSelected && styles.chipSelected,
]}
onPress={() => toggleCategory(category)}
>
<Text
style={[
styles.chipText,
isDark ? styles.darkChipText : styles.lightChipText,
isSelected && styles.chipTextSelected,
]}
>
{category.name}
</Text>
{!category.is_leaf && (
<ChevronRight size={20} color={isSelected ? '#ffffff' : isDark ? '#64748b' : '#94a3b8'} />
)}
</TouchableOpacity>
);
};
if (isLoading && currentCategories.length === 0) {
return (
<View style={styles.centerContainer}>
<ActivityIndicator size="small" color="#3b82f6" />
</View>
);
}
if (error && currentCategories.length === 0) {
return (
<View style={styles.centerContainer}>
<Text style={{ color: '#f87171' }}>Ma'lumot yuklashda xatolik yuz berdi</Text>
</View>
);
}
if (currentCategories.length === 0) {
return (
<View style={styles.centerContainer}>
<Text style={isDark ? styles.darkText : styles.lightText}>Kategoriyalar topilmadi</Text>
</View>
);
}
return (
<View style={[styles.container, isDark ? styles.darkBg : styles.lightBg]}>
<View style={styles.header}>
{history.length > 0 && (
<TouchableOpacity
onPress={goBack}
style={[styles.backButton, isDark ? styles.darkBackButton : styles.lightBackButton]}
>
<ChevronLeft color={'#3b82f6'} size={20} />
</TouchableOpacity>
)}
</View>
<FlatList
data={currentCategories}
renderItem={renderCategory}
keyExtractor={(item) => item.id.toString()}
scrollEnabled={false}
showsVerticalScrollIndicator={true}
ItemSeparatorComponent={() => <View style={{ height: 10 }} />}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
marginBottom: 20,
},
darkBg: {
backgroundColor: '#0f172a',
},
lightBg: {
backgroundColor: '#f8fafc',
},
centerContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
header: {
flexDirection: 'row',
alignItems: 'center',
gap: 12,
marginBottom: 12,
},
backButton: {
paddingHorizontal: 12,
paddingVertical: 6,
borderRadius: 8,
},
darkBackButton: {
backgroundColor: '#1e293b',
},
lightBackButton: {
backgroundColor: '#ffffff',
},
chip: {
paddingHorizontal: 16,
paddingVertical: 12,
borderRadius: 20,
borderWidth: 1,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.05,
shadowRadius: 2,
elevation: 1,
},
darkChip: {
backgroundColor: '#1e293b',
borderColor: '#334155',
},
lightChip: {
backgroundColor: '#ffffff',
borderColor: '#e2e8f0',
},
chipSelected: {
backgroundColor: '#3b82f6',
borderColor: '#3b82f6',
shadowColor: '#3b82f6',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.3,
shadowRadius: 4,
elevation: 3,
},
chipText: {
fontSize: 14,
fontWeight: '500',
flex: 1,
},
darkChipText: {
color: '#cbd5e1',
},
lightChipText: {
color: '#64748b',
},
chipTextSelected: {
color: '#ffffff',
fontWeight: '600',
},
darkText: {
color: '#f1f5f9',
},
lightText: {
color: '#0f172a',
},
});