365 lines
11 KiB
TypeScript
365 lines
11 KiB
TypeScript
import { useTheme } from '@/components/ThemeContext';
|
|
import { products_api } from '@/screens/home/lib/api';
|
|
import { useQuery } from '@tanstack/react-query';
|
|
import React, { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import {
|
|
Dimensions,
|
|
FlatList,
|
|
ListRenderItemInfo,
|
|
ScrollView,
|
|
StyleSheet,
|
|
Text,
|
|
TouchableOpacity,
|
|
View,
|
|
} from 'react-native';
|
|
import { PriceCalculationRes } from '../lib/types';
|
|
import CategorySelectorBottomSheet from './CategorySelectorBottomSheet';
|
|
|
|
type StepProps = {
|
|
formData: any;
|
|
data: PriceCalculationRes | undefined;
|
|
updateForm: (key: string, value: any) => void;
|
|
};
|
|
|
|
const SCREEN_WIDTH = Dimensions.get('window').width;
|
|
const GAP = 8;
|
|
const NUM_COLUMNS = 6;
|
|
const ITEM_SIZE = (SCREEN_WIDTH - GAP * (NUM_COLUMNS + 1)) / NUM_COLUMNS;
|
|
|
|
const StepThree = forwardRef(({ formData, updateForm, data }: StepProps, ref) => {
|
|
const { isDark } = useTheme();
|
|
const { t } = useTranslation();
|
|
|
|
const theme = {
|
|
background: isDark ? '#0f172a' : '#ffffff',
|
|
cardBg: isDark ? '#1e293b' : '#f8fafc',
|
|
cardBorder: isDark ? '#334155' : '#e2e8f0',
|
|
text: isDark ? '#f8fafc' : '#0f172a',
|
|
textSecondary: isDark ? '#cbd5e1' : '#64748b',
|
|
primary: '#2563eb',
|
|
error: '#ef4444',
|
|
priceText: isDark ? '#dc2626' : '#ef4444',
|
|
companyBg: isDark ? '#1e293b' : '#f1f5f9',
|
|
companyBorder: isDark ? '#334155' : '#cbd5e1',
|
|
};
|
|
|
|
const { data: statesData } = useQuery({
|
|
queryKey: ['country-detail'],
|
|
queryFn: async () => products_api.getStates(),
|
|
select: (res) => res.data.data || [],
|
|
});
|
|
|
|
const [showCountry, setShowCountry] = useState(false);
|
|
const [showRegion, setShowRegion] = useState(false);
|
|
const [showDistrict, setShowDistrict] = useState(false);
|
|
const [regions, setRegions] = useState<any[]>([]);
|
|
const [districts, setDistricts] = useState<any[]>([]);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const corporations = Array.from({ length: 26 }).map((_, i) => ({
|
|
id: i + 1,
|
|
latter: String.fromCharCode(65 + i),
|
|
}));
|
|
|
|
const onCompanyPress = (item: { id: number; latter: string }) => {
|
|
const selected = formData.company || [];
|
|
const exists = selected.some((c: any) => c.id === item.id);
|
|
|
|
if (exists) {
|
|
updateForm(
|
|
'company',
|
|
selected.filter((c: any) => c.id !== item.id)
|
|
);
|
|
} else {
|
|
updateForm('company', [...selected, item]);
|
|
}
|
|
};
|
|
|
|
const toggleSelectAllCompanies = () => {
|
|
const selected = formData.company || [];
|
|
if (selected.length === corporations.length) {
|
|
// Deselect all
|
|
updateForm('company', []);
|
|
} else {
|
|
// Select all
|
|
updateForm('company', corporations);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
const country = statesData?.find((c) => c.code === formData.country);
|
|
setRegions(country?.region || []);
|
|
if (!country?.region.some((r) => r.code === formData.region)) {
|
|
updateForm('region', '');
|
|
updateForm('district', '');
|
|
setDistricts([]);
|
|
}
|
|
}, [formData.country, statesData]);
|
|
|
|
useEffect(() => {
|
|
const country = statesData?.find((c) => c.code === formData.country);
|
|
const region = country?.region.find((r) => r.code === formData.region);
|
|
setDistricts(region?.districts || []);
|
|
|
|
// If region is 'all', automatically set district to 'all'
|
|
if (formData.region === 'all') {
|
|
updateForm('district', 'all');
|
|
} else if (!region?.districts.some((d) => d.code === formData.district)) {
|
|
updateForm('district', '');
|
|
}
|
|
}, [formData.region, formData.country, statesData]);
|
|
|
|
const getLabel = (arr: { name: string; code: string }[], val: string) => {
|
|
if (val === 'all') return t('Hammasi');
|
|
return arr.find((item) => item.code === val)?.name || t('— Tanlang —');
|
|
};
|
|
|
|
const renderCompanyItem = ({ item }: ListRenderItemInfo<{ id: number; latter: string }>) => {
|
|
const isSelected = formData.company?.some((c: any) => c.id === item.id);
|
|
|
|
return (
|
|
<TouchableOpacity
|
|
style={[
|
|
styles.companyItem,
|
|
{
|
|
backgroundColor: isSelected ? theme.primary : theme.companyBg,
|
|
borderColor: isSelected ? theme.primary : theme.companyBorder,
|
|
},
|
|
]}
|
|
onPress={() => onCompanyPress(item)}
|
|
>
|
|
<Text
|
|
style={[
|
|
styles.companyText,
|
|
{ color: isSelected ? '#fff' : theme.text },
|
|
isSelected && styles.companyTextActive,
|
|
]}
|
|
>
|
|
{item.latter}
|
|
</Text>
|
|
</TouchableOpacity>
|
|
);
|
|
};
|
|
|
|
const validate = () => {
|
|
if (!formData.country) {
|
|
setError('Iltimos, davlat tanlang');
|
|
return false;
|
|
}
|
|
if (!formData.region) {
|
|
setError('Iltimos, viloyat tanlang');
|
|
return false;
|
|
}
|
|
if (!formData.district) {
|
|
setError('Iltimos, tuman/shahar tanlang');
|
|
return false;
|
|
}
|
|
setError(null);
|
|
return true;
|
|
};
|
|
|
|
useImperativeHandle(ref, () => ({
|
|
validate,
|
|
}));
|
|
|
|
return (
|
|
<ScrollView
|
|
contentContainerStyle={[styles.container, { backgroundColor: theme.background }]}
|
|
showsVerticalScrollIndicator={false}
|
|
>
|
|
{error && <Text style={[styles.error, { color: theme.error }]}>{t(error)}</Text>}
|
|
|
|
<Text style={[styles.label, { color: theme.text }]}>{t('Davlat')}</Text>
|
|
<TouchableOpacity
|
|
style={[
|
|
styles.pickerButton,
|
|
{
|
|
backgroundColor: theme.cardBg,
|
|
borderColor: theme.cardBorder,
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: 10,
|
|
},
|
|
]}
|
|
onPress={() => setShowCountry(true)}
|
|
>
|
|
<Text style={[styles.pickerText, { color: theme.text }]}>
|
|
{statesData &&
|
|
getLabel(
|
|
statesData.map((c) => ({ name: c.name, code: c.code })),
|
|
formData.country
|
|
)}
|
|
</Text>
|
|
</TouchableOpacity>
|
|
|
|
<Text style={[styles.label, { color: theme.text }]}>{t('Viloyat')}</Text>
|
|
<TouchableOpacity
|
|
style={[
|
|
styles.pickerButton,
|
|
{ backgroundColor: theme.cardBg, borderColor: theme.cardBorder },
|
|
]}
|
|
onPress={() => setShowRegion(true)}
|
|
>
|
|
<Text style={[styles.pickerText, { color: theme.text }]}>
|
|
{getLabel(
|
|
regions.map((r) => ({ name: r.name, code: r.code })),
|
|
formData.region
|
|
)}
|
|
</Text>
|
|
</TouchableOpacity>
|
|
|
|
<Text style={[styles.label, { color: theme.text }]}>{t('Tuman / Shahar')}</Text>
|
|
<TouchableOpacity
|
|
style={[
|
|
styles.pickerButton,
|
|
{
|
|
backgroundColor: theme.cardBg,
|
|
borderColor: theme.cardBorder,
|
|
opacity: formData.region === 'all' ? 0.5 : 1,
|
|
},
|
|
]}
|
|
onPress={() => {
|
|
if (formData.region !== 'all') {
|
|
setShowDistrict(true);
|
|
}
|
|
}}
|
|
disabled={formData.region === 'all'}
|
|
>
|
|
<Text style={[styles.pickerText, { color: theme.text }]}>
|
|
{getLabel(
|
|
districts.map((d) => ({ name: d.name, code: d.code })),
|
|
formData.district
|
|
)}
|
|
</Text>
|
|
</TouchableOpacity>
|
|
|
|
<View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
<Text style={[styles.sectionTitle, { color: theme.text }]}>
|
|
{t('Reklama joylashtirish kompaniyasi')}
|
|
</Text>
|
|
<TouchableOpacity
|
|
style={[
|
|
styles.selectAllButton,
|
|
{
|
|
backgroundColor:
|
|
formData.company?.length === corporations.length ? theme.primary : theme.cardBg,
|
|
borderColor: theme.primary,
|
|
},
|
|
]}
|
|
onPress={toggleSelectAllCompanies}
|
|
>
|
|
<Text
|
|
style={[
|
|
styles.selectAllText,
|
|
{
|
|
color:
|
|
formData.company?.length === corporations.length ? '#fff' : theme.primary,
|
|
},
|
|
]}
|
|
>
|
|
{formData.company?.length === corporations.length ? t('Bekor qilish') : t('Hammasi')}
|
|
</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
<FlatList
|
|
data={corporations}
|
|
renderItem={renderCompanyItem}
|
|
keyExtractor={(item) => item.id.toString()}
|
|
numColumns={6}
|
|
columnWrapperStyle={{ gap: 2, marginBottom: GAP, justifyContent: 'flex-start' }}
|
|
scrollEnabled={false}
|
|
/>
|
|
|
|
<View
|
|
style={[styles.priceCard, { backgroundColor: theme.cardBg, borderColor: theme.cardBorder }]}
|
|
>
|
|
<Text style={[styles.priceLine, { color: theme.text }]}>
|
|
{t('Jami kampaniyalar soni')}: {data ? data.user_count : '0'}
|
|
</Text>
|
|
<Text style={[styles.priceLine, { color: theme.text }]}>
|
|
{t('Reklama narxi')}: {data ? data.one_person_price : '0'}
|
|
</Text>
|
|
<Text style={[styles.totalPrice, { color: theme.priceText }]}>
|
|
{t('Umumiy narx')}: {data ? data.total_price : '0'}
|
|
</Text>
|
|
</View>
|
|
|
|
<CategorySelectorBottomSheet
|
|
isOpen={showCountry}
|
|
onClose={() => setShowCountry(false)}
|
|
selectedValue={formData.country}
|
|
data={
|
|
statesData ? statesData.map((c) => ({ label: c.name, value: c.code, flag: c.flag })) : []
|
|
}
|
|
onSelect={(v) => updateForm('country', v)}
|
|
/>
|
|
<CategorySelectorBottomSheet
|
|
isOpen={showRegion}
|
|
onClose={() => setShowRegion(false)}
|
|
selectedValue={formData.region}
|
|
data={[
|
|
{ label: t('Hammasi'), value: 'all' },
|
|
...regions.map((r) => ({ label: r.name, value: r.code })),
|
|
]}
|
|
onSelect={(v) => updateForm('region', v)}
|
|
/>
|
|
<CategorySelectorBottomSheet
|
|
isOpen={showDistrict}
|
|
onClose={() => setShowDistrict(false)}
|
|
selectedValue={formData.district}
|
|
data={[
|
|
{ label: t('Hammasi'), value: 'all' },
|
|
...districts.map((d) => ({ label: d.name, value: d.code })),
|
|
]}
|
|
onSelect={(v) => updateForm('district', v)}
|
|
/>
|
|
</ScrollView>
|
|
);
|
|
});
|
|
|
|
export default StepThree;
|
|
|
|
const styles = StyleSheet.create({
|
|
container: { flexGrow: 1 },
|
|
label: { fontSize: 14, fontWeight: '700', marginBottom: 6, marginTop: 10 },
|
|
pickerButton: {
|
|
borderWidth: 1,
|
|
borderRadius: 12,
|
|
padding: 16,
|
|
marginBottom: 12,
|
|
},
|
|
pickerText: { fontSize: 16 },
|
|
sectionTitle: { fontSize: 16, fontWeight: '700', marginVertical: 12 },
|
|
companyItem: {
|
|
width: 55,
|
|
height: 55,
|
|
borderRadius: ITEM_SIZE / 2,
|
|
borderWidth: 1,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
companyText: { fontSize: 14 },
|
|
companyTextActive: { fontWeight: '600' },
|
|
priceCard: {
|
|
marginTop: 24,
|
|
padding: 20,
|
|
borderRadius: 16,
|
|
borderWidth: 1,
|
|
gap: 8,
|
|
},
|
|
priceLine: { fontSize: 15 },
|
|
totalPrice: { fontSize: 18, fontWeight: '700', marginTop: 6 },
|
|
error: { fontWeight: '600', marginBottom: 10 },
|
|
selectAllButton: {
|
|
paddingHorizontal: 16,
|
|
paddingVertical: 8,
|
|
borderRadius: 8,
|
|
borderWidth: 1,
|
|
},
|
|
selectAllText: {
|
|
fontSize: 14,
|
|
fontWeight: '600',
|
|
},
|
|
});
|