237 lines
6.3 KiB
TypeScript
237 lines
6.3 KiB
TypeScript
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';
|
|
import { useTheme } from '../ThemeContext';
|
|
|
|
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[];
|
|
setSelectedCategories: Dispatch<SetStateAction<Category[]>>;
|
|
}
|
|
|
|
export default function CategorySelection({ selectedCategories, setSelectedCategories }: Props) {
|
|
const [currentCategories, setCurrentCategories] = useState<Category[]>([]);
|
|
const [history, setHistory] = useState<{ parentId: number | null; categories: Category[] }[]>([]);
|
|
const [currentParentId, setCurrentParentId] = useState<number | null>(null);
|
|
const { isDark } = useTheme();
|
|
|
|
const theme = {
|
|
cardBg: isDark ? '#1e293b' : '#f8fafc',
|
|
cardBorder: isDark ? '#334155' : '#e2e8f0',
|
|
text: isDark ? '#cbd5e1' : '#334155',
|
|
textSelected: '#ffffff',
|
|
primary: '#2563eb',
|
|
primaryBg: '#3b82f6',
|
|
error: '#ef4444',
|
|
backButtonBg: isDark ? '#1e293b' : '#e2e8f0',
|
|
chevronColor: isDark ? '#94a3b8' : '#64748b',
|
|
shadow: isDark ? '#000' : '#94a3b8',
|
|
};
|
|
|
|
const {
|
|
data: rootData,
|
|
isLoading: rootLoading,
|
|
error: rootError,
|
|
} = useQuery<CategoryResponse>({
|
|
queryKey: ['categories'],
|
|
queryFn: async () => products_api.getCategorys(),
|
|
select(data) {
|
|
setCurrentCategories(data.data.data);
|
|
setCurrentParentId(null);
|
|
return data;
|
|
},
|
|
});
|
|
|
|
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 yuklashda xato:', err);
|
|
},
|
|
});
|
|
|
|
const toggleCategory = (category: Category) => {
|
|
if (category.is_leaf) {
|
|
setSelectedCategories((prev) => {
|
|
const exists = prev.find((c) => c.id === category.id);
|
|
if (exists) return prev.filter((c) => c.id !== category.id);
|
|
return [...prev, 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.some((c) => c.id === category.id);
|
|
return (
|
|
<TouchableOpacity
|
|
style={[
|
|
styles.chip,
|
|
{
|
|
backgroundColor: isSelected ? theme.primaryBg : theme.cardBg,
|
|
borderColor: isSelected ? theme.primaryBg : theme.cardBorder,
|
|
shadowColor: isSelected ? theme.primaryBg : theme.shadow,
|
|
},
|
|
isSelected && styles.chipSelected,
|
|
]}
|
|
onPress={() => toggleCategory(category)}
|
|
>
|
|
<Text
|
|
style={[
|
|
styles.chipText,
|
|
{ color: isSelected ? theme.textSelected : theme.text },
|
|
isSelected && styles.chipTextSelected,
|
|
]}
|
|
>
|
|
{category.name}
|
|
</Text>
|
|
{!category.is_leaf && (
|
|
<ChevronRight size={20} color={isSelected ? theme.textSelected : theme.chevronColor} />
|
|
)}
|
|
</TouchableOpacity>
|
|
);
|
|
};
|
|
|
|
if (isLoading && currentCategories.length === 0) {
|
|
return (
|
|
<View style={[styles.centerContainer]}>
|
|
<ActivityIndicator size="small" color={theme.primary} />
|
|
</View>
|
|
);
|
|
}
|
|
|
|
if (error && currentCategories.length === 0) {
|
|
return (
|
|
<View style={[styles.centerContainer]}>
|
|
<Text style={{ color: theme.error }}>Ma'lumot yuklashda xatolik yuz berdi</Text>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
if (currentCategories.length === 0) {
|
|
return (
|
|
<View style={[styles.centerContainer]}>
|
|
<Text style={{ color: theme.text }}>Kategoriyalar topilmadi</Text>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<View style={[styles.container]}>
|
|
<View style={styles.header}>
|
|
{history.length > 0 && (
|
|
<TouchableOpacity
|
|
onPress={goBack}
|
|
style={[styles.backButton, { backgroundColor: theme.backButtonBg }]}
|
|
>
|
|
<ChevronLeft color={theme.primary} size={20} />
|
|
</TouchableOpacity>
|
|
)}
|
|
</View>
|
|
|
|
<FlatList
|
|
data={currentCategories}
|
|
renderItem={renderCategory}
|
|
scrollEnabled={false}
|
|
keyExtractor={(item) => item.id.toString()}
|
|
showsVerticalScrollIndicator={true}
|
|
ItemSeparatorComponent={() => <View style={{ height: 10 }} />}
|
|
/>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
marginBottom: 20,
|
|
},
|
|
centerContainer: {
|
|
flex: 1,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
},
|
|
header: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: 12,
|
|
marginBottom: 12,
|
|
paddingHorizontal: 12,
|
|
},
|
|
backButton: {
|
|
paddingHorizontal: 12,
|
|
paddingVertical: 6,
|
|
borderRadius: 8,
|
|
},
|
|
chip: {
|
|
paddingHorizontal: 16,
|
|
paddingVertical: 12,
|
|
borderRadius: 20,
|
|
borderWidth: 1,
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
shadowOffset: { width: 0, height: 1 },
|
|
shadowOpacity: 0.05,
|
|
shadowRadius: 2,
|
|
elevation: 1,
|
|
},
|
|
chipSelected: {
|
|
shadowOffset: { width: 0, height: 2 },
|
|
shadowOpacity: 0.3,
|
|
shadowRadius: 4,
|
|
elevation: 3,
|
|
},
|
|
chipText: {
|
|
fontSize: 14,
|
|
fontWeight: '500',
|
|
flex: 1,
|
|
},
|
|
chipTextSelected: {
|
|
fontWeight: '600',
|
|
},
|
|
});
|