Initial commit

This commit is contained in:
Samandar Turgunboyev
2025-08-26 16:26:59 +05:00
commit fd95422447
318 changed files with 38301 additions and 0 deletions

View File

@@ -0,0 +1,243 @@
import { RouteProp, useNavigation, useRoute } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { useQuery } from '@tanstack/react-query';
import { Branch, branchApi } from 'api/branch';
import BottomModal from 'components/BottomModal';
import LoadingScreen from 'components/LoadingScreen';
import NavbarBack from 'components/NavbarBack';
import Navigation from 'components/Navigation';
import React, { useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import WebView from 'react-native-webview';
import Minus from 'svg/Minus';
import Plus from 'svg/Plus';
const ListBranches = () => {
const route = useRoute<RouteProp<any>>();
const webviewRef = useRef<WebView>(null);
const [webViewReady, setWebViewReady] = useState(false);
const [selectedBranch, setSelectedBranch] = useState<Branch | null>(null);
const [isModalVisible, setModalVisible] = useState(false);
const navigation = useNavigation<NativeStackNavigationProp<any>>();
const { t } = useTranslation();
const { data } = useQuery({
queryKey: ['branchList'],
queryFn: branchApi.branchList,
});
useEffect(() => {
if (webViewReady && route.params?.branchId) {
const branch = data && data.find(b => b.id === route?.params?.branchId);
if (branch) {
setSelectedBranch(branch);
setModalVisible(true);
const jsCode = `
map.setCenter([${branch.latitude}, ${branch.longitude}], 14);
placemark${branch.id}?.balloon.open();
true;
`;
webviewRef.current?.injectJavaScript(jsCode);
}
}
}, [webViewReady, route.params]);
const generatePlacemarks = () => {
if (!data || !data.length) return '';
return data
.map(
branch => `
var placemark${branch.id} = new ymaps.Placemark([${branch.latitude}, ${branch.longitude}], {
balloonContent: '${branch.name}'
}, {
iconLayout: 'default#image',
iconImageSize: [30, 30],
iconImageOffset: [-15, -30]
});
placemark${branch.id}.events.add('click', function () {
window.ReactNativeWebView.postMessage(JSON.stringify({
type: 'branch_click',
id: ${branch.id}
}));
});
map.geoObjects.add(placemark${branch.id});
`,
)
.join('\n');
};
const html = `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://api-maps.yandex.ru/2.1/?lang=ru_RU"></script>
<style>
html, body, #map {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<div id="map"></div>
<script>
let map;
ymaps.ready(function () {
map = new ymaps.Map("map", {
center: [40.5, 67.9],
zoom: 6,
controls: []
});
${generatePlacemarks()}
window.ReactNativeWebView.postMessage("map_ready");
});
function zoomIn() {
if (map) map.setZoom(map.getZoom() + 1);
}
function zoomOut() {
if (map) map.setZoom(map.getZoom() - 1);
}
</script>
</body>
</html>
`;
const handleZoom = (type: 'in' | 'out') => {
const command = type === 'in' ? 'zoomIn(); true;' : 'zoomOut(); true;';
webviewRef.current?.injectJavaScript(command);
};
if (!data) return <LoadingScreen />;
return (
<SafeAreaView style={styles.container}>
<NavbarBack title={t('Filiallar royxati')} />
{!webViewReady && (
<View style={{ width: '100%', height: '100%', margin: 'auto' }}>
<LoadingScreen />
</View>
)}
<View style={styles.container}>
<WebView
ref={webviewRef}
originWhitelist={['*']}
source={{ html }}
javaScriptEnabled
domStorageEnabled
onMessage={event => {
try {
const message = event.nativeEvent.data;
if (message === 'map_ready') {
setWebViewReady(true);
return;
}
const parsed = JSON.parse(message);
if (parsed.type === 'branch_click') {
const branchItem =
data && data.find((b: Branch) => b.id === parsed.id);
if (branchItem) {
setSelectedBranch(branchItem);
setModalVisible(true);
}
}
} catch (e) {
console.warn('WebView message parse error:', e);
}
}}
style={{ flex: 1 }}
/>
{webViewReady && (
<View style={{ position: 'absolute', bottom: 0 }}>
<View style={styles.zoomControls}>
<TouchableOpacity
style={styles.zoomButton}
onPress={() => handleZoom('in')}
>
<Text>
<Plus color="#DEDEDE" />
</Text>
</TouchableOpacity>
<View style={styles.divider} />
<TouchableOpacity
style={styles.zoomButton}
onPress={() => handleZoom('out')}
>
<Text>
<Minus color="#DEDEDE" />
</Text>
</TouchableOpacity>
</View>
<TouchableOpacity
style={styles.button}
onPress={() => navigation.navigate('Branches')}
>
<Text style={styles.buttonText}>{t('Manzilni tekshirish')}</Text>
</TouchableOpacity>
</View>
)}
<BottomModal
visible={isModalVisible}
onClose={() => setModalVisible(false)}
branch={selectedBranch}
/>
</View>
<Navigation />
</SafeAreaView>
);
};
export default ListBranches;
const styles = StyleSheet.create({
container: { flex: 1 },
zoomControls: {
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#373737',
borderRadius: 20,
width: '10%',
padding: 4,
marginLeft: '85%',
bottom: 20,
},
zoomButton: {
borderRadius: 8,
width: 40,
height: 40,
marginVertical: 4,
justifyContent: 'center',
alignItems: 'center',
},
divider: { height: 1, width: '100%', backgroundColor: '#DEDEDE' },
button: {
bottom: 10,
width: '95%',
margin: 'auto',
alignSelf: 'center',
backgroundColor: '#009CFF',
paddingVertical: 14,
paddingHorizontal: 28,
borderRadius: 10,
elevation: 4,
},
buttonText: {
color: '#fff',
fontWeight: '600',
fontSize: 16,
textAlign: 'center',
},
});