complated project

This commit is contained in:
Samandar Turgunboyev
2026-02-02 18:51:53 +05:00
parent f0183e4573
commit a7419929f8
57 changed files with 3035 additions and 477 deletions

View File

@@ -1,6 +1,7 @@
import { useTheme } from '@/components/ThemeContext';
import { products_api } from '@/screens/home/lib/api';
import { useInfiniteQuery } from '@tanstack/react-query';
import { Image } from 'expo-image';
import { Building2, ChevronDown, ChevronUp } from 'lucide-react-native';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
@@ -74,7 +75,7 @@ export default function CountriesList({ search }: { search: string }) {
<FlatList
data={allCountries}
keyExtractor={(item) => item.id.toString()}
contentContainerStyle={{ gap: 12 }}
contentContainerStyle={{ gap: 5 }}
onEndReached={loadMore}
onEndReachedThreshold={0.4}
ListFooterComponent={
@@ -83,7 +84,7 @@ export default function CountriesList({ search }: { search: string }) {
showsVerticalScrollIndicator={false}
renderItem={({ item }) => {
const isOpen = openedCountryId === item.id;
const flagCode = item.flag ? item.flag.toLowerCase() : ''; // "uz"
return (
<View style={[styles.countryCard, isDark ? styles.darkCard : styles.lightCard]}>
{/* Davlat sarlavhasi */}
@@ -92,9 +93,24 @@ export default function CountriesList({ search }: { search: string }) {
onPress={() => toggleAccordion(item.id)}
activeOpacity={0.8}
>
<Text style={[styles.countryName, isDark ? styles.darkText : styles.lightText]}>
{item.name}
</Text>
<View
style={{
flexDirection: 'row',
gap: 10,
alignContent: 'center',
alignItems: 'center',
}}
>
<Image
source={{ uri: `https://flagcdn.com/w320/${flagCode}.png` }}
style={{ width: 40, height: 20 }}
resizeMode="cover" // objectFit o'rniga resizeMode ishlatildi
/>
<Text style={[styles.countryName, isDark ? styles.darkText : styles.lightText]}>
{item.name}
</Text>
</View>
<View style={styles.rightSide}>
<Text
style={[styles.companyCount, isDark ? styles.darkSubText : styles.lightSubText]}
@@ -166,6 +182,7 @@ const styles = StyleSheet.create({
shadowRadius: 6,
elevation: 2,
marginLeft: 2,
marginBottom: 5,
},
darkCard: {
backgroundColor: '#1e293b',

View File

@@ -1,18 +1,18 @@
import { useTheme } from '@/components/ThemeContext';
import { products_api } from '@/screens/home/lib/api';
import BottomSheet, {
BottomSheetBackdrop,
BottomSheetFlatList,
BottomSheetScrollView,
} from '@gorhom/bottom-sheet';
import { useMutation, useQuery } from '@tanstack/react-query';
import { AxiosError } from 'axios';
import { XIcon } from 'lucide-react-native';
import React, { Dispatch, SetStateAction, useMemo, useState } from 'react';
import { Image } from 'expo-image';
import { CheckIcon, ChevronRight, XIcon } from 'lucide-react-native';
import React, { Dispatch, SetStateAction, useCallback, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
ActivityIndicator,
ScrollView,
StyleSheet,
Text,
TouchableOpacity,
View,
} from 'react-native';
import { ActivityIndicator, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import CategorySelect from './CategorySelect';
interface FilterUIProps {
@@ -32,9 +32,32 @@ interface Category {
icon_name: string | null;
}
interface Country {
id: number;
name: string;
region: Region[];
}
interface Region {
id: number;
name: string;
districts: District[];
}
interface District {
id: number;
name: string;
}
type SheetType = 'country' | 'region' | 'district' | 'category' | null;
export default function FilterUI({ back, onApply, setStep, setFiltered }: FilterUIProps) {
const { isDark } = useTheme();
const { t } = useTranslation();
const bottomSheetRef = useRef<BottomSheet>(null);
const snapPoints = useMemo(() => ['60%', '85%'], []);
const [activeSheet, setActiveSheet] = useState<SheetType>(null);
const [selectedCategories, setSelectedCategories] = useState<Category | null>(null);
const [selectedCountry, setSelectedCountry] = useState<string>('all');
const [selectedRegion, setSelectedRegion] = useState<string>('all');
@@ -46,7 +69,7 @@ export default function FilterUI({ back, onApply, setStep, setFiltered }: Filter
select: (res) => res.data?.data || [],
});
const { mutate } = useMutation({
const { mutate, isPending } = useMutation({
mutationFn: (params: any) => products_api.businessAbout(params),
onSuccess: (data) => {
setStep('items');
@@ -80,177 +103,409 @@ export default function FilterUI({ back, onApply, setStep, setFiltered }: Filter
return region?.districts || [];
}, [regions, selectedRegion]);
const openSheet = useCallback((type: SheetType) => {
setActiveSheet(type);
setTimeout(() => {
bottomSheetRef.current?.snapToIndex(0);
}, 100);
}, []);
const closeSheet = useCallback(() => {
bottomSheetRef.current?.close();
setTimeout(() => setActiveSheet(null), 300);
}, []);
const renderBackdrop = useCallback(
(props: any) => (
<BottomSheetBackdrop
{...props}
appearsOnIndex={0}
disappearsOnIndex={-1}
opacity={0.6}
pressBehavior="close"
/>
),
[]
);
const getSelectedLabel = useCallback(
(type: SheetType) => {
switch (type) {
case 'country':
if (selectedCountry === 'all') return t('Barchasi');
return (
countryResponse?.find((c) => c.id?.toString() === selectedCountry)?.name || t('Tanlang')
);
case 'region':
if (selectedRegion === 'all') return t('Barchasi');
return regions.find((r) => r.id?.toString() === selectedRegion)?.name || t('Tanlang');
case 'district':
if (selectedDistrict === 'all') return t('Barchasi');
return districts.find((d) => d.id?.toString() === selectedDistrict)?.name || t('Tanlang');
case 'category':
return selectedCategories?.name || t('Tanlang');
default:
return t('Tanlang');
}
},
[
selectedCountry,
selectedRegion,
selectedDistrict,
selectedCategories,
countryResponse,
regions,
districts,
t,
]
);
const FilterButton = useCallback(
({
label,
value,
onPress,
disabled = false,
}: {
label: string;
value: string;
onPress: () => void;
disabled?: boolean;
}) => (
<TouchableOpacity
style={[
styles.filterBtn,
isDark ? styles.darkFilterBtn : styles.lightFilterBtn,
disabled && styles.disabledBtn,
]}
onPress={onPress}
disabled={disabled}
activeOpacity={0.7}
>
<View style={styles.filterBtnContent}>
<Text style={[styles.filterLabel, isDark ? styles.darkText : styles.lightText]}>
{label}
</Text>
<View style={styles.filterValueContainer}>
<Text
style={[
styles.filterValue,
isDark ? styles.darkValueText : styles.lightValueText,
disabled && styles.disabledText,
]}
numberOfLines={1}
>
{value}
</Text>
<ChevronRight size={20} color={isDark ? '#94a3b8' : '#64748b'} />
</View>
</View>
</TouchableOpacity>
),
[isDark]
);
const renderListItem = useCallback(
({
item,
onSelect,
selectedId,
}: {
item: any;
onSelect: (id: string) => void;
selectedId: string;
}) => {
const isSelected = selectedId === (item.id?.toString() || 'all');
const flagCode = item.flag ? item.flag.toLowerCase() : ''; // "uz"
return (
<TouchableOpacity
style={[
styles.listItem,
isDark ? styles.darkListItem : styles.lightListItem,
isSelected && styles.selectedListItem,
]}
onPress={() => onSelect(item.id?.toString() || 'all')}
activeOpacity={0.7}
>
<View
style={{
flexDirection: 'row',
gap: 10,
alignContent: 'center',
alignItems: 'center',
}}
>
{item.flag && (
<Image
source={{ uri: `https://flagcdn.com/w320/${flagCode}.png` }}
style={{ width: 34, height: 20 }}
resizeMode="cover" // objectFit o'rniga resizeMode ishlatildi
/>
)}
<Text
style={[
styles.listItemText,
isDark ? styles.darkText : styles.lightText,
isSelected && styles.selectedListItemText,
]}
>
{item.name}
</Text>
</View>
{isSelected && (
<View style={styles.checkmark}>
<CheckIcon color={'#3b82f6'} strokeWidth={'2.5'} size={18} />
</View>
)}
</TouchableOpacity>
);
},
[isDark]
);
const renderSheetContent = useCallback(() => {
if (activeSheet === 'category') {
return (
<BottomSheetScrollView
contentContainerStyle={styles.scrollViewContent}
showsVerticalScrollIndicator={false}
bounces={true}
>
<CategorySelect
selectedCategories={selectedCategories}
setSelectedCategories={setSelectedCategories}
/>
</BottomSheetScrollView>
);
}
let data: any[] = [];
let onSelect: (id: string) => void = () => {};
let selectedId = '';
switch (activeSheet) {
case 'country':
data = [{ id: 'all', name: t('Barchasi') }, ...(countryResponse || [])];
onSelect = (id) => {
setSelectedCountry(id);
setSelectedRegion('all');
setSelectedDistrict('all');
closeSheet();
};
selectedId = selectedCountry;
break;
case 'region':
data = [{ id: 'all', name: t('Barchasi') }, ...regions];
onSelect = (id) => {
setSelectedRegion(id);
setSelectedDistrict('all');
closeSheet();
};
selectedId = selectedRegion;
break;
case 'district':
data = [{ id: 'all', name: t('Barchasi') }, ...districts];
onSelect = (id) => {
setSelectedDistrict(id);
closeSheet();
};
selectedId = selectedDistrict;
break;
}
return (
<BottomSheetFlatList
data={data}
keyExtractor={(item: any) => item.id?.toString() || 'all'}
contentContainerStyle={styles.listContainer}
showsVerticalScrollIndicator={false}
bounces={true}
overScrollMode="always"
renderItem={({ item }: { item: any }) => renderListItem({ item, onSelect, selectedId })}
/>
);
}, [
activeSheet,
selectedCategories,
countryResponse,
regions,
districts,
selectedCountry,
selectedRegion,
selectedDistrict,
t,
closeSheet,
renderListItem,
]);
const getSheetTitle = useCallback(() => {
switch (activeSheet) {
case 'country':
return t('Davlat');
case 'region':
return t('Viloyat');
case 'district':
return t('Tuman');
case 'category':
return t('Sohalar');
default:
return '';
}
}, [activeSheet, t]);
if (isLoading) {
return (
<View
style={[
styles.container,
isDark ? styles.darkBg : styles.lightBg,
{ justifyContent: 'center', alignItems: 'center' },
]}
>
<ActivityIndicator size="large" color="#3b82f6" />
<View style={[styles.container, isDark ? styles.darkBg : styles.lightBg]}>
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color="#3b82f6" />
</View>
</View>
);
}
// Single Tag Component
const Tag = ({
label,
selected,
onPress,
}: {
label: string;
selected: boolean;
onPress: () => void;
}) => (
<TouchableOpacity
style={[
styles.tag,
isDark ? styles.darkTag : styles.lightTag,
selected && styles.tagSelected,
]}
onPress={onPress}
activeOpacity={0.7}
>
<Text
style={[
styles.tagText,
isDark ? styles.darkTagText : styles.lightTagText,
selected && styles.tagTextSelected,
]}
>
{label}
</Text>
</TouchableOpacity>
);
return (
<View style={[styles.container, isDark ? styles.darkBg : styles.lightBg]}>
{/* Header */}
<View style={styles.btn}>
<TouchableOpacity
onPress={back}
style={[styles.backBtn, isDark ? styles.darkBackBtn : styles.lightBackBtn]}
>
<XIcon color={isDark ? 'white' : '#0f172a'} fontSize={24} />
</TouchableOpacity>
</View>
<GestureHandlerRootView style={{ flex: 1 }}>
<View style={[styles.container, isDark ? styles.darkBg : styles.lightBg]}>
{/* Header */}
<View style={[styles.header, { borderBottomColor: isDark ? '#334155' : '#e2e8f0' }]}>
<Text style={[styles.headerTitle, isDark ? styles.darkText : styles.lightText]}>
{t('Filter')}
</Text>
<TouchableOpacity
onPress={back}
style={[styles.closeBtn, isDark ? styles.darkCloseBtn : styles.lightCloseBtn]}
>
<XIcon color={isDark ? '#f1f5f9' : '#0f172a'} size={20} />
</TouchableOpacity>
</View>
{/* Scrollable Content */}
<ScrollView
style={{ padding: 16 }}
showsVerticalScrollIndicator={false}
contentContainerStyle={{ paddingBottom: 140 }}
>
{/* Country Filter */}
<Text style={[styles.sectionTitle, isDark ? styles.darkText : styles.lightText]}>
{t('Davlat')}
</Text>
<ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.scrollRow}>
<Tag
label={t('Barchasi')}
selected={selectedCountry === 'all'}
onPress={() => setSelectedCountry('all')}
{/* Filter Options */}
<View style={styles.content}>
<FilterButton
label={t('Davlat')}
value={getSelectedLabel('country')}
onPress={() => openSheet('country')}
/>
{countryResponse?.map((c) => (
<Tag
key={c.id}
label={c.name}
selected={selectedCountry === c.id?.toString()}
onPress={() => {
setSelectedCountry(c.id?.toString() || 'all');
setSelectedRegion('all');
setSelectedDistrict('all');
}}
/>
))}
</ScrollView>
{/* Region Filter */}
{regions.length > 0 && (
<>
<Text style={[styles.sectionTitle, isDark ? styles.darkText : styles.lightText]}>
{t('Viloyat')}
<FilterButton
label={t('Viloyat')}
value={getSelectedLabel('region')}
onPress={() => openSheet('region')}
disabled={selectedCountry === 'all' || regions.length === 0}
/>
<FilterButton
label={t('Tuman')}
value={getSelectedLabel('district')}
onPress={() => openSheet('district')}
disabled={selectedRegion === 'all' || districts.length === 0}
/>
<FilterButton
label={t('Sohalar')}
value={getSelectedLabel('category')}
onPress={() => openSheet('category')}
/>
</View>
{/* Apply Button */}
<View style={[styles.applyBtnWrapper, { borderTopColor: isDark ? '#334155' : '#e2e8f0' }]}>
<TouchableOpacity
style={[styles.applyBtn, isPending && styles.applyBtnDisabled]}
onPress={handleApply}
disabled={isPending}
activeOpacity={0.8}
>
{isPending ? (
<ActivityIndicator color="#ffffff" />
) : (
<Text style={styles.applyBtnText}>{t("Natijalarni ko'rish")}</Text>
)}
</TouchableOpacity>
</View>
{/* Bottom Sheet */}
<BottomSheet
ref={bottomSheetRef}
index={-1}
snapPoints={snapPoints}
enablePanDownToClose={true}
enableDynamicSizing={false}
enableOverDrag={false}
onClose={() => setActiveSheet(null)}
backdropComponent={renderBackdrop}
backgroundStyle={[styles.bottomSheetBackground, isDark ? styles.darkBg : styles.lightBg]}
handleIndicatorStyle={[
styles.handleIndicator,
isDark ? styles.darkHandleIndicator : styles.lightHandleIndicator,
]}
android_keyboardInputMode="adjustResize"
keyboardBehavior="interactive"
keyboardBlurBehavior="restore"
>
<View style={[styles.sheetHeader, { borderBottomColor: isDark ? '#334155' : '#e2e8f0' }]}>
<Text style={[styles.sheetTitle, isDark ? styles.darkText : styles.lightText]}>
{getSheetTitle()}
</Text>
<ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.scrollRow}>
<Tag
label={t('Barchasi')}
selected={selectedRegion === 'all'}
onPress={() => setSelectedRegion('all')}
/>
{regions.map((r) => (
<Tag
key={r.id}
label={r.name}
selected={selectedRegion === r.id?.toString()}
onPress={() => {
setSelectedRegion(r.id?.toString() || 'all');
setSelectedDistrict('all');
}}
/>
))}
</ScrollView>
</>
)}
{/* District Filter */}
{districts.length > 0 && (
<>
<Text style={[styles.sectionTitle, isDark ? styles.darkText : styles.lightText]}>
{t('Tuman')}
</Text>
<ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.scrollRow}>
<Tag
label={t('Barchasi')}
selected={selectedDistrict === 'all'}
onPress={() => setSelectedDistrict('all')}
/>
{districts.map((d) => (
<Tag
key={d.id}
label={d.name}
selected={selectedDistrict === d.id?.toString()}
onPress={() => setSelectedDistrict(d.id?.toString() || 'all')}
/>
))}
</ScrollView>
</>
)}
{/* Industry Selection */}
<Text style={[styles.sectionTitle, isDark ? styles.darkText : styles.lightText]}>
{t('Sohalar')}
</Text>
<CategorySelect
selectedCategories={selectedCategories}
setSelectedCategories={setSelectedCategories}
/>
</ScrollView>
{/* Fixed Apply Button */}
<View style={styles.applyBtnWrapper}>
<TouchableOpacity style={styles.applyBtn} onPress={handleApply}>
<Text style={styles.applyBtnText}>{t("Natijalarni ko'rish")}</Text>
</TouchableOpacity>
<TouchableOpacity onPress={closeSheet} style={styles.sheetCloseBtn}>
<XIcon color={isDark ? '#94a3b8' : '#64748b'} size={20} />
</TouchableOpacity>
</View>
{renderSheetContent()}
</BottomSheet>
</View>
</View>
</GestureHandlerRootView>
);
}
const styles = StyleSheet.create({
container: { flex: 1 },
container: {
flex: 1,
},
darkBg: {
backgroundColor: '#0f172a',
},
lightBg: {
backgroundColor: '#f8fafc',
},
backBtn: {
paddingHorizontal: 10,
paddingVertical: 10,
borderRadius: 10,
marginTop: 10,
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: 20,
paddingVertical: 16,
borderBottomWidth: 1,
},
headerTitle: {
fontSize: 20,
fontWeight: '700',
},
closeBtn: {
padding: 8,
borderRadius: 8,
borderWidth: 1,
},
darkCloseBtn: {
backgroundColor: '#1e293b',
borderColor: '#334155',
},
lightCloseBtn: {
backgroundColor: '#ffffff',
borderColor: '#e2e8f0',
},
content: {
flex: 1,
padding: 20,
gap: 12,
},
loadingContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
filterBtn: {
padding: 16,
borderRadius: 12,
borderWidth: 1,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
@@ -258,81 +513,56 @@ const styles = StyleSheet.create({
shadowRadius: 2,
elevation: 1,
},
darkBackBtn: {
darkFilterBtn: {
backgroundColor: '#1e293b',
borderColor: '#334155',
},
lightBackBtn: {
lightFilterBtn: {
backgroundColor: '#ffffff',
borderColor: '#e2e8f0',
},
btn: {
justifyContent: 'flex-end',
alignItems: 'flex-end',
paddingHorizontal: 16,
disabledBtn: {
opacity: 0.5,
},
sectionTitle: {
fontSize: 16,
fontWeight: '700',
marginBottom: 10,
filterBtnContent: {
flexDirection: 'column',
gap: 8, // 4 o'rniga 8 qildim
},
filterLabel: {
fontSize: 13,
fontWeight: '600',
marginBottom: 2, // 4 o'rniga 2 qildim
},
filterValueContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
gap: 8, // ChevronRight va text orasida gap
},
filterValue: {
fontSize: 15,
fontWeight: '500',
flex: 1,
},
darkText: {
color: '#f1f5f9',
},
lightText: {
color: '#0f172a',
},
scrollRow: { flexDirection: 'row', marginBottom: 12, gap: 10 },
tag: {
paddingHorizontal: 16,
paddingVertical: 10,
borderRadius: 12,
marginRight: 10,
borderWidth: 1,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.05,
shadowRadius: 2,
elevation: 1,
},
darkTag: {
backgroundColor: '#1e293b',
borderColor: '#334155',
},
lightTag: {
backgroundColor: '#ffffff',
borderColor: '#e2e8f0',
},
tagSelected: {
backgroundColor: '#3b82f6',
borderColor: '#3b82f6',
shadowColor: '#3b82f6',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.3,
shadowRadius: 4,
elevation: 5,
},
tagText: {
fontWeight: '500',
},
darkTagText: {
darkValueText: {
color: '#cbd5e1',
},
lightTagText: {
lightValueText: {
color: '#64748b',
},
tagTextSelected: {
color: '#ffffff',
fontWeight: '600',
disabledText: {
opacity: 0.5,
},
applyBtnWrapper: {
position: 'absolute',
bottom: 55,
left: 16,
right: 16,
zIndex: 10,
padding: 20,
borderTopWidth: 1,
},
applyBtn: {
backgroundColor: '#3b82f6',
@@ -344,7 +574,98 @@ const styles = StyleSheet.create({
shadowOpacity: 0.3,
shadowRadius: 4,
elevation: 5,
marginBottom: 20,
},
applyBtnText: { color: '#ffffff', fontWeight: '700', fontSize: 16 },
applyBtnDisabled: {
opacity: 0.7,
},
applyBtnText: {
color: '#ffffff',
fontWeight: '700',
fontSize: 16,
},
bottomSheetBackground: {
borderTopLeftRadius: 24,
borderTopRightRadius: 24,
shadowColor: '#000',
shadowOffset: { width: 0, height: -2 },
shadowOpacity: 0.1,
shadowRadius: 8,
elevation: 5,
},
handleIndicator: {
width: 40,
height: 4,
borderRadius: 2,
},
darkHandleIndicator: {
backgroundColor: '#475569',
},
lightHandleIndicator: {
backgroundColor: '#cbd5e1',
},
sheetHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: 20,
paddingVertical: 16,
borderBottomWidth: 1,
},
sheetTitle: {
fontSize: 18,
fontWeight: '700',
},
sheetCloseBtn: {
padding: 4,
},
listContainer: {
padding: 16,
paddingBottom: 40,
},
scrollViewContent: {
padding: 16,
paddingBottom: 40,
},
listItem: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
padding: 16,
borderRadius: 12,
marginBottom: 8,
borderWidth: 1,
},
darkListItem: {
backgroundColor: '#1e293b',
borderColor: '#334155',
},
lightListItem: {
backgroundColor: '#ffffff',
borderColor: '#e2e8f0',
},
selectedListItem: {
backgroundColor: '#3b82f6',
borderColor: '#3b82f6',
},
listItemText: {
fontSize: 15,
fontWeight: '500',
},
selectedListItemText: {
color: '#ffffff',
fontWeight: '600',
},
checkmark: {
width: 24,
height: 24,
borderRadius: 12,
backgroundColor: '#ffffff',
justifyContent: 'center',
alignItems: 'center',
},
checkmarkText: {
color: '#3b82f6',
fontSize: 16,
fontWeight: '700',
},
});

View File

@@ -42,10 +42,7 @@ export default function FilteredItems({ data, back }: FilteredItemsProps) {
if (selectedItem) {
return (
<ScrollView
style={[styles.container, isDark ? styles.darkBg : styles.lightBg]}
contentContainerStyle={{ paddingBottom: 70 }}
>
<ScrollView style={[styles.container, isDark ? styles.darkBg : styles.lightBg]}>
<View style={styles.content}>
{/* Back Button */}
<TouchableOpacity
@@ -203,7 +200,7 @@ export default function FilteredItems({ data, back }: FilteredItemsProps) {
{data && data.length > 0 ? (
<FlatList
data={data}
style={{ marginBottom: 120 }}
style={{ marginBottom: 70 }}
keyExtractor={(item) => item.id.toString()}
renderItem={({ item }) => (
<TouchableOpacity

View File

@@ -1,30 +1,59 @@
import Logo from '@/assets/images/logo.png';
import LogoText from '@/assets/images/navbar.png';
import { useTheme } from '@/components/ThemeContext';
import { LogOut } from 'lucide-react-native';
import { useTranslation } from 'react-i18next';
import { user_api } from '@/screens/profile/lib/api';
import { useQuery } from '@tanstack/react-query';
import { router } from 'expo-router';
import { Bell, LogOut } from 'lucide-react-native';
import { Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { useAuth } from '../AuthProvider';
export const CustomHeader = ({ logoutbtn = false }: { logoutbtn?: boolean }) => {
export const CustomHeader = ({
logoutbtn = false,
notif = true,
}: {
logoutbtn?: boolean;
notif?: boolean;
}) => {
const { isDark } = useTheme();
const { t } = useTranslation();
const { logout } = useAuth();
const { data, isLoading } = useQuery({
queryKey: ['notification-list'],
queryFn: () => user_api.notification_list({ page: 1, page_size: 1 }),
});
const unreadCount = data?.data?.data.unread_count ?? 0;
return (
<View style={[styles.container, isDark ? styles.darkBg : styles.lightBg]}>
<View style={styles.logoWrapper}>
<View style={styles.logoContainer}>
<Image source={Logo} style={styles.logo} />
</View>
<Text style={[styles.title, isDark ? styles.darkText : styles.lightText]}>
{t('app.name', 'InfoTarget')}
</Text>
<Image source={LogoText} style={{ width: 160, height: 40, objectFit: 'cover' }} />
</View>
{logoutbtn && (
<TouchableOpacity onPress={logout}>
<LogOut color={'red'} />
</TouchableOpacity>
)}
{notif && (
<TouchableOpacity
onPress={() => router.push('/profile/notification')} // yoki '/notifications' sahifasiga
style={styles.bellContainer}
>
<Bell color={isDark ? '#e2e8f0' : '#334155'} size={24} />
{unreadCount > 0 && (
<View style={styles.badge}>
<Text style={styles.badgeText}>{unreadCount > 99 ? '99+' : unreadCount}</Text>
</View>
)}
{/* Agar yuklanayotgan bo'lsa kichik indikator (ixtiyoriy) */}
{isLoading && unreadCount === 0 && <View style={styles.loadingDot} />}
</TouchableOpacity>
)}
</View>
);
};
@@ -57,7 +86,8 @@ const styles = StyleSheet.create({
logoWrapper: {
flexDirection: 'row',
alignItems: 'center',
gap: 12,
alignContent: 'center',
gap: 5,
},
logoContainer: {
@@ -89,4 +119,38 @@ const styles = StyleSheet.create({
lightText: {
color: '#0f172a',
},
bellContainer: {
position: 'relative',
},
badge: {
position: 'absolute',
top: -6,
right: -6,
minWidth: 18,
height: 18,
borderRadius: 9,
backgroundColor: '#ef4444', // qizil badge
justifyContent: 'center',
alignItems: 'center',
paddingHorizontal: 4,
borderWidth: 1.5,
},
badgeText: {
color: '#ffffff',
fontSize: 10,
fontWeight: 'bold',
},
loadingDot: {
position: 'absolute',
top: 0,
right: 0,
width: 8,
height: 8,
borderRadius: 4,
backgroundColor: '#3b82f6',
opacity: 0.7,
},
});