fitst commit
This commit is contained in:
236
components/ui/IndustrySelection.tsx
Normal file
236
components/ui/IndustrySelection.tsx
Normal file
@@ -0,0 +1,236 @@
|
||||
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',
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user