240 lines
6.8 KiB
TypeScript
240 lines
6.8 KiB
TypeScript
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 LayoutTwo from 'components/LayoutTwo';
|
||
import LoadingScreen from 'components/LoadingScreen';
|
||
import React, { useEffect, useRef, useState } from 'react';
|
||
import { useTranslation } from 'react-i18next';
|
||
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||
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 (
|
||
<LayoutTwo title={t('Filiallar ro’yxati')}>
|
||
{!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>
|
||
</LayoutTwo>
|
||
);
|
||
};
|
||
|
||
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',
|
||
},
|
||
});
|