feat: add yandex-map

This commit is contained in:
Ильназ 2025-03-09 10:55:24 +03:00
parent 9349a41f84
commit fea4a54c83
15 changed files with 150 additions and 7 deletions

View File

@ -16,6 +16,7 @@ module.exports = {
'dry-wash.order.view': '/order/:orderId', 'dry-wash.order.view': '/order/:orderId',
'dry-wash.arm.master': 'master', 'dry-wash.arm.master': 'master',
'dry-wash.arm.order': 'order', 'dry-wash.arm.order': 'order',
'dry-wash.arm.map': 'map',
'dry-wash.arm': '/arm/*', 'dry-wash.arm': '/arm/*',
}, },
features: { features: {

View File

@ -111,5 +111,8 @@
"dry-wash.errorBoundary.title": "Something went wrong", "dry-wash.errorBoundary.title": "Something went wrong",
"dry-wash.errorBoundary.description": "We are already working on fixing the issue", "dry-wash.errorBoundary.description": "We are already working on fixing the issue",
"dry-wash.errorBoundary.button.reload": "Reload Page", "dry-wash.errorBoundary.button.reload": "Reload Page",
"dry-wash.washTime.timeSlot": "{{start}} - {{end}}" "dry-wash.washTime.timeSlot": "{{start}} - {{end}}",
"dry-wash.arm.map.title": "Map of orders",
"dry-wash.arm.map.carNumber": "Car Number",
"dry-wash.arm.map.status": "Status"
} }

View File

@ -117,5 +117,8 @@
"dry-wash.errorBoundary.title": "Что-то пошло не так", "dry-wash.errorBoundary.title": "Что-то пошло не так",
"dry-wash.errorBoundary.description": "Мы уже работаем над исправлением проблемы", "dry-wash.errorBoundary.description": "Мы уже работаем над исправлением проблемы",
"dry-wash.errorBoundary.button.reload": "Перезагрузить страницу", "dry-wash.errorBoundary.button.reload": "Перезагрузить страницу",
"dry-wash.washTime.timeSlot": "{{start}} - {{end}}" "dry-wash.washTime.timeSlot": "{{start}} - {{end}}",
"dry-wash.arm.map.title": " Карта заказов",
"dry-wash.arm.map.carNumber": " Номер автомобиля",
"dry-wash.arm.map.status": "Статус"
} }

1
package-lock.json generated
View File

@ -3610,7 +3610,6 @@
"version": "1.2.5", "version": "1.2.5",
"resolved": "https://registry.npmjs.org/@pbe/react-yandex-maps/-/react-yandex-maps-1.2.5.tgz", "resolved": "https://registry.npmjs.org/@pbe/react-yandex-maps/-/react-yandex-maps-1.2.5.tgz",
"integrity": "sha512-cBojin5e1fPx9XVCAqHQJsCnHGMeBNsP0TrNfpWCrPFfxb30ye+JgcGr2mn767Gbr1d+RufBLRiUcX2kaiAwjQ==", "integrity": "sha512-cBojin5e1fPx9XVCAqHQJsCnHGMeBNsP0TrNfpWCrPFfxb30ye+JgcGr2mn767Gbr1d+RufBLRiUcX2kaiAwjQ==",
"license": "MIT",
"dependencies": { "dependencies": {
"@types/yandex-maps": "2.1.29" "@types/yandex-maps": "2.1.29"
}, },

View File

@ -3,6 +3,7 @@ import { FetchBaseQueryError } from '@reduxjs/toolkit/query';
import { BaseResponse } from '../../models/api'; import { BaseResponse } from '../../models/api';
export const extractBodyFromResponse = <Body>(response: BaseResponse<Body>) => { export const extractBodyFromResponse = <Body>(response: BaseResponse<Body>) => {
console.log('response', response);
if (response.success) { if (response.success) {
return response.body; return response.body;
} }

View File

@ -33,6 +33,18 @@ export const URLs = {
url: getNavigationValue('dry-wash.arm.order'), url: getNavigationValue('dry-wash.arm.order'),
isOn: Boolean(getNavigationValue('dry-wash.arm.order')), isOn: Boolean(getNavigationValue('dry-wash.arm.order')),
}, },
armMap: {
url: getNavigationValue('dry-wash.arm.map'),
isOn: Boolean(getNavigationValue('dry-wash.arm.map')),
getUrl({ lat, lon, currentDate }) {
return (
getFullUrls('/arm') +
'/' +
getNavigationValue('dry-wash.arm.map') +
`?lat=${lat}&lon=${lon}&currentDate=${currentDate}`
);
},
},
armBase: { armBase: {
url: getFullUrls(getNavigationValue('dry-wash.arm')), url: getFullUrls(getNavigationValue('dry-wash.arm')),
isOn: Boolean(getNavigationValue('dry-wash.arm')), isOn: Boolean(getNavigationValue('dry-wash.arm')),

View File

@ -42,6 +42,18 @@ const Header = () => {
{t('master')} {t('master')}
</Button> </Button>
)} )}
{URLs.armMap.isOn && (
<Button
as={Link}
to={URLs.armMap.url}
colorScheme={isActive(URLs.armMap.url) ? 'green' : 'blue'}
variant={isActive(URLs.armMap.url) ? 'outline' : 'ghost'}
data-testid='master-button'
>
Карта заказов
</Button>
)}
</HStack> </HStack>
</Flex> </Flex>
</Box> </Box>

View File

@ -6,6 +6,7 @@ import Orders from '../Orders';
import Masters from '../Masters'; import Masters from '../Masters';
import { URLs } from '../../__data__/urls'; import { URLs } from '../../__data__/urls';
import Header from '../Header'; import Header from '../Header';
import OrdersMap from '../Map';
const LayoutArm = () => { const LayoutArm = () => {
let defaultRedirect = null; let defaultRedirect = null;
@ -28,6 +29,9 @@ const LayoutArm = () => {
{URLs.armMaster.isOn && ( {URLs.armMaster.isOn && (
<Route path={URLs.armMaster.url} element={<Masters />} /> <Route path={URLs.armMaster.url} element={<Masters />} />
)} )}
{URLs.armMap.isOn && (
<Route path={URLs.armMap.url} element={<OrdersMap />} />
)}
</Routes> </Routes>
</Box> </Box>
</Flex> </Flex>

View File

@ -0,0 +1,82 @@
import React, { useMemo } from 'react';
import { Box, Flex, Heading, Spinner } from '@chakra-ui/react';
import { useTranslation } from 'react-i18next';
import { YMaps, Map, Placemark } from '@pbe/react-yandex-maps';
import { useLocation } from 'react-router-dom';
import { useGetOrdersQuery } from '../../__data__/service/api';
import getCoordinates from '../../utils/getCoordinates';
const OrdersMap = () => {
const { t } = useTranslation('~', {
keyPrefix: 'dry-wash.arm.map',
});
const location = useLocation();
const params = new URLSearchParams(location.search);
const latFromUrl = parseFloat(params.get('lat') || 55.78);
const lonFromUrl = parseFloat(params.get('lon') || 49.12);
const currentDate = useMemo(
() =>
params.get('currentDate')
? new Date(params.get('currentDate'))
: new Date(),
[],
);
const {
data: ordersData,
isLoading,
isSuccess,
} = useGetOrdersQuery({ date: currentDate });
// Получаем координаты из location
const orders = ordersData
?.map((order) => {
const coords = getCoordinates(order.location);
return coords ? { ...order, ...coords } : null;
})
.filter(Boolean);
return (
<Box p='8'>
<Heading size='lg' mb='5'>
{t('title')}
</Heading>
{isLoading && (
<Flex justifyContent='center' alignItems='center'>
<Spinner size='lg' />
</Flex>
)}
{isSuccess && (
<YMaps>
<Map
defaultState={{ center: [latFromUrl, lonFromUrl], zoom: 12 }}
width='100%'
height='70vh'
modules={['geoObject.addon.balloon']}
>
{orders.map(({ id, lat, lon, carNumber, status }) => (
<Placemark
key={id}
geometry={[lat, lon]}
options={{
preset: 'islands#blueAutoIcon',
iconColor: 'blue',
balloonPanelMaxMapArea: 0,
}}
properties={{
balloonContent: `<strong>${t('carNumber')}</strong> ${carNumber}<br/><strong>${t('status')}</strong> ${status}`,
}}
/>
))}
</Map>
</YMaps>
)}
</Box>
);
};
export default OrdersMap;

View File

@ -0,0 +1 @@
export { default } from './Map';

View File

@ -1,13 +1,15 @@
import React, { ChangeEvent, useState } from 'react'; import React, { ChangeEvent, useState } from 'react';
import { Td, Tr, Link, Select } from '@chakra-ui/react'; import { Td, Tr, Link, Select, Button } from '@chakra-ui/react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { ViewIcon } from '@chakra-ui/icons'; import { ViewIcon } from '@chakra-ui/icons';
import { Link as LinkRouter } from 'react-router-dom';
import { getTimeSlot } from '../../lib'; import { getTimeSlot } from '../../lib';
import { useUpdateOrdersMutation } from '../../__data__/service/api'; import { useUpdateOrdersMutation } from '../../__data__/service/api';
import { OrderArm, Status, statuses } from '../../models/api'; import { OrderArm, Status, statuses } from '../../models/api';
import PopoverTemplate from '../PopoverTemplate'; import getCoordinates from '../../utils/getCoordinates';
import { URLs } from '../../__data__/urls';
const statusColors: Record<Status, string> = { const statusColors: Record<Status, string> = {
pending: 'yellow.100', pending: 'yellow.100',
@ -27,6 +29,7 @@ const OrderItem = ({
master, master,
allMasters, allMasters,
id, id,
currentDate,
}: OrderArm) => { }: OrderArm) => {
const [updateOrders] = useUpdateOrdersMutation(); const [updateOrders] = useUpdateOrdersMutation();
const { t } = useTranslation('~', { const { t } = useTranslation('~', {
@ -61,6 +64,8 @@ const OrderItem = ({
(master) => master.id === masterSelectId, (master) => master.id === masterSelectId,
); );
const { lat = 55.78, lon = 49.12 } = getCoordinates(location);
return ( return (
<Tr> <Tr>
<Td>{carNumber}</Td> <Td>{carNumber}</Td>
@ -99,7 +104,12 @@ const OrderItem = ({
<Link href='tel:'>{phone}</Link> <Link href='tel:'>{phone}</Link>
</Td> </Td>
<Td> <Td>
<PopoverTemplate trigger={<ViewIcon />} description={location} /> <Button
as={LinkRouter}
to={URLs.armMap.getUrl({ lat, lon, currentDate })}
>
<ViewIcon />
</Button>
</Td> </Td>
</Tr> </Tr>
); );

View File

@ -112,6 +112,7 @@ const Orders = () => {
key={index} key={index}
{...order} {...order}
status={order.status as OrderArm['status']} status={order.status as OrderArm['status']}
currentDate={currentDate}
/> />
))} ))}
</Tbody> </Tbody>

View File

@ -56,4 +56,5 @@ export type OrderArm = {
notes: ''; notes: '';
allMasters: Master[]; allMasters: Master[];
id: string; id: string;
currentDate: Date;
}; };

View File

@ -0,0 +1,13 @@
const getCoordinates = (location: string) => {
if (!location) return null;
const [lat, lon] = location
.split(',')
.map((coord) => parseFloat(coord.trim()));
if (isNaN(lat) || isNaN(lon)) return null;
return { lat, lon };
};
export default getCoordinates;

View File

@ -21,7 +21,7 @@
"orderDate": "2024-11-24T07:40:46.366Z", "orderDate": "2024-11-24T07:40:46.366Z",
"status": "progress", "status": "progress",
"phone": "79001234567", "phone": "79001234567",
"location": "55.779905316526424,49.12446113769528 Республика Татарстан (Татарстан), Казань, озеро Нижний Кабан", "location": "55.75060416346278,48.746329576898944 Республика Татарстан (Татарстан), Верхнеуслонский район, Иннополис",
"master": [], "master": [],
"notes": "" "notes": ""
} }