256 lines
6.3 KiB
TypeScript
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',
|
|
},
|
|
});
|