Compare commits
No commits in common. "cb848739e5e4478b31bc6477081d8a1db1516293" and "7b0ccc525caf639c8bdd49c62b5dc8b8119c2f3c" have entirely different histories.
cb848739e5
...
7b0ccc525c
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 114 KiB |
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 83 KiB |
Before Width: | Height: | Size: 123 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 61 KiB |
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 89 KiB |
@ -1,59 +0,0 @@
|
||||
import { isObject } from '../../utils/types'
|
||||
import { Category, isCategory } from '../../assets/category'
|
||||
|
||||
type AnnouncementResponse = {
|
||||
id: number,
|
||||
user_id: number,
|
||||
name: string,
|
||||
category: Category,
|
||||
best_by: number,
|
||||
address: string,
|
||||
longtitude: number,
|
||||
latitude: number,
|
||||
description: string,
|
||||
src: string | null,
|
||||
metro: string,
|
||||
trashId: number | null,
|
||||
booked_by: number
|
||||
}
|
||||
|
||||
const isAnnouncementResponse = (obj: unknown): obj is AnnouncementResponse => isObject(obj, {
|
||||
'id': 'number',
|
||||
'user_id': 'number',
|
||||
'name': 'string',
|
||||
'category': isCategory,
|
||||
'best_by': 'number',
|
||||
'address': 'string',
|
||||
'longtitude': 'number',
|
||||
'latitude': 'number',
|
||||
'description': 'string',
|
||||
'src': 'string?',
|
||||
'metro': 'string',
|
||||
'trashId': 'number?',
|
||||
'booked_by': 'number'
|
||||
})
|
||||
|
||||
type Announcement = {
|
||||
id: number,
|
||||
userId: number,
|
||||
name: string,
|
||||
category: Category,
|
||||
bestBy: number,
|
||||
address: string,
|
||||
lng: number,
|
||||
lat: number,
|
||||
description: string | null,
|
||||
src: string | null,
|
||||
metro: string,
|
||||
trashId: number | null,
|
||||
bookedBy: number
|
||||
}
|
||||
|
||||
export type {
|
||||
Announcement,
|
||||
AnnouncementResponse,
|
||||
}
|
||||
|
||||
export {
|
||||
isAnnouncementResponse,
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
import { API_URL } from '../../config'
|
||||
import { FiltersType, URLEncodeFilters } from '../../utils/filters'
|
||||
import { Announcement } from '../announcement/types'
|
||||
import { AnnouncementsResponse } from './types'
|
||||
|
||||
const initialAnnouncements: Announcement[] = []
|
||||
|
||||
const composeAnnouncementsURL = (filters: FiltersType) =>
|
||||
API_URL + '/announcements?' + new URLSearchParams(URLEncodeFilters(filters)).toString()
|
||||
|
||||
const processAnnouncements = (data: AnnouncementsResponse): Announcement[] => {
|
||||
const annList = data.list_of_announcements
|
||||
|
||||
return annList.map(ann => ({
|
||||
...ann,
|
||||
lat: ann.latitude,
|
||||
lng: ann.longtitude,
|
||||
bestBy: ann.best_by,
|
||||
bookedBy: ann.booked_by,
|
||||
userId: ann.user_id
|
||||
}))
|
||||
}
|
||||
|
||||
export { initialAnnouncements, composeAnnouncementsURL, processAnnouncements }
|
@ -1,20 +0,0 @@
|
||||
import { isArrayOf, isObject } from '../../utils/types'
|
||||
import { AnnouncementResponse, isAnnouncementResponse } from '../announcement/types'
|
||||
|
||||
type AnnouncementsResponse = {
|
||||
list_of_announcements: AnnouncementResponse[],
|
||||
Success: boolean
|
||||
}
|
||||
|
||||
const isAnnouncementsResponse = (obj: unknown): obj is AnnouncementsResponse => isObject(obj, {
|
||||
'list_of_announcements': obj => isArrayOf<AnnouncementResponse>(obj, isAnnouncementResponse),
|
||||
'Success': 'boolean'
|
||||
})
|
||||
|
||||
export type {
|
||||
AnnouncementsResponse,
|
||||
}
|
||||
|
||||
export {
|
||||
isAnnouncementsResponse,
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
import { LatLng } from 'leaflet'
|
||||
import { OsmAddressResponse } from './types'
|
||||
|
||||
const initialOsmAddress = ''
|
||||
|
||||
const composeOsmAddressURL = (addressPosition: LatLng) =>
|
||||
`${location.protocol}//nominatim.openstreetmap.org/reverse?format=json&accept-language=ru&lat=${addressPosition.lat}&lon=${addressPosition.lng}`
|
||||
|
||||
const processOsmAddress = (data: OsmAddressResponse): string =>
|
||||
data.display_name
|
||||
|
||||
export { initialOsmAddress, composeOsmAddressURL, processOsmAddress }
|
@ -1,17 +0,0 @@
|
||||
import { isObject } from '../../utils/types'
|
||||
|
||||
type OsmAddressResponse = {
|
||||
display_name: string
|
||||
}
|
||||
|
||||
const isOsmAddressResponse = (obj: unknown): obj is OsmAddressResponse => isObject(obj, {
|
||||
'display_name': 'string',
|
||||
})
|
||||
|
||||
export type {
|
||||
OsmAddressResponse,
|
||||
}
|
||||
|
||||
export {
|
||||
isOsmAddressResponse,
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
import { LatLng } from 'leaflet'
|
||||
|
||||
import { API_URL } from '../../config'
|
||||
|
||||
const composeTrashboxURL = (position: LatLng) =>
|
||||
API_URL + '/trashbox?' + new URLSearchParams({
|
||||
lat: position.lat.toString(),
|
||||
lng: position.lng.toString()
|
||||
}).toString()
|
||||
|
||||
export { composeTrashboxURL }
|
@ -1,22 +0,0 @@
|
||||
import { isArrayOf, isObject, isString } from '../../utils/types'
|
||||
|
||||
type Trashbox = {
|
||||
Lat: number,
|
||||
Lng: number,
|
||||
Address: string,
|
||||
Categories: string[]
|
||||
}
|
||||
|
||||
const isTrashbox = (obj: unknown): obj is Trashbox => isObject(obj, {
|
||||
'Lat': 'number',
|
||||
'Lng': 'number',
|
||||
'Address': 'string',
|
||||
'Categories': obj => isArrayOf<string>(obj, isString)
|
||||
})
|
||||
|
||||
type TrashboxResponse = Trashbox[]
|
||||
|
||||
const isTrashboxResponse = (obj: unknown): obj is Trashbox[] => isArrayOf(obj, isTrashbox)
|
||||
|
||||
export type { Trashbox, TrashboxResponse }
|
||||
export { isTrashbox, isTrashboxResponse }
|
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 5.2 KiB |
@ -4,8 +4,7 @@ import { MapContainer, Marker, Popup, TileLayer } from 'react-leaflet'
|
||||
import LineDot from './LineDot'
|
||||
import { categoryNames } from '../assets/category'
|
||||
import { useBook } from '../hooks/api'
|
||||
import { Announcement } from '../api/announcement/types'
|
||||
import { iconItem } from '../utils/markerIcons'
|
||||
import { Announcement } from '../hooks/api/useHomeAnnouncementList'
|
||||
|
||||
type AnnouncementDetailsProps = {
|
||||
close: () => void,
|
||||
@ -42,7 +41,7 @@ function AnnouncementDetails({ close, announcement: { id, name, category, bestBy
|
||||
url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
|
||||
/>
|
||||
|
||||
<Marker icon={iconItem} position={[lat, lng]}>
|
||||
<Marker position={[lat, lng]}>
|
||||
<Popup>
|
||||
{address}
|
||||
<br />
|
||||
|
@ -2,7 +2,6 @@ import { Marker, Popup, useMapEvents } from 'react-leaflet'
|
||||
import { LatLng } from 'leaflet'
|
||||
|
||||
import { SetState } from '../utils/types'
|
||||
import { iconItem } from '../utils/markerIcons'
|
||||
|
||||
type LocationMarkerProps = {
|
||||
address: string,
|
||||
@ -25,7 +24,7 @@ const LocationMarker = ({ address, position, setPosition }: LocationMarkerProps)
|
||||
})
|
||||
|
||||
return (
|
||||
<Marker icon={iconItem} position={position}>
|
||||
<Marker position={position}>
|
||||
<Popup>
|
||||
{address}
|
||||
{position.lat.toFixed(4)}, {position.lng.toFixed(4)}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { Marker, Popup } from 'react-leaflet'
|
||||
|
||||
import { Trashbox } from '../api/trashbox/types'
|
||||
import { iconTrash } from '../utils/markerIcons'
|
||||
import { Trashbox } from '../hooks/api/useTrashboxes'
|
||||
|
||||
type TrashboxMarkersProps = {
|
||||
trashboxes: Trashbox[],
|
||||
@ -11,7 +10,7 @@ type TrashboxMarkersProps = {
|
||||
const TrashboxMarkers = ({ trashboxes, selectTrashbox }: TrashboxMarkersProps) => {
|
||||
return (
|
||||
<>{trashboxes.map((trashbox, index) => (
|
||||
<Marker icon={iconTrash} key={`${trashbox.Lat}${trashbox.Lng}`} position={[trashbox.Lat, trashbox.Lng]}>
|
||||
<Marker key={`${trashbox.Lat}${trashbox.Lng}`} position={[trashbox.Lat, trashbox.Lng]}>
|
||||
<Popup>
|
||||
<p>{trashbox.Address}</p>
|
||||
<p>Тип мусора: <>
|
||||
|
@ -1,6 +1,5 @@
|
||||
export { default as useAnnouncements } from './useAnnouncements'
|
||||
export { default as useHomeAnnouncementList } from './useHomeAnnouncementList'
|
||||
export { default as useBook } from './useBook'
|
||||
export { default as useAuth } from './useAuth'
|
||||
export { default as useTrashboxes } from './useTrashboxes'
|
||||
export { default as useAddAnnouncement } from './useAddAnnouncement'
|
||||
export { default as useOsmAddresses } from './useOsmAddress'
|
||||
|
@ -1,18 +0,0 @@
|
||||
import useFetch from './useFetch'
|
||||
import { FiltersType } from '../../utils/filters'
|
||||
import { composeAnnouncementsURL, initialAnnouncements, processAnnouncements } from '../../api/announcements'
|
||||
|
||||
import { isAnnouncementsResponse } from '../../api/announcements/types'
|
||||
|
||||
const useAnnouncements = (filters: FiltersType) =>
|
||||
useFetch(
|
||||
composeAnnouncementsURL(filters),
|
||||
'GET',
|
||||
false,
|
||||
isAnnouncementsResponse,
|
||||
processAnnouncements,
|
||||
initialAnnouncements
|
||||
)
|
||||
|
||||
|
||||
export default useAnnouncements
|
@ -1,57 +1,11 @@
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
|
||||
import { handleHTTPErrors, isAborted } from '../../utils'
|
||||
import { getToken } from '../../utils/auth'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { SetState } from '../../utils/types'
|
||||
|
||||
type UseFetchShared = {
|
||||
loading: boolean,
|
||||
abort?: () => void,
|
||||
}
|
||||
|
||||
type UseFetchSucced<T> = {
|
||||
error: null,
|
||||
data: T,
|
||||
} & UseFetchShared
|
||||
|
||||
type UseFetchErrored = {
|
||||
error: string,
|
||||
data: undefined
|
||||
} & UseFetchShared
|
||||
|
||||
const gotError = <T>(res: UseFetchErrored | UseFetchSucced<T>): res is UseFetchErrored =>
|
||||
typeof res.error === 'string'
|
||||
|
||||
const fallbackError = <T>(res: UseFetchSucced<T> | UseFetchErrored) =>
|
||||
gotError(res) ? res.error : res.data
|
||||
|
||||
type UseFetchReturn<T> = ({
|
||||
error: null,
|
||||
data: T
|
||||
} | {
|
||||
error: string,
|
||||
data: undefined
|
||||
}) & {
|
||||
loading: boolean,
|
||||
setData: SetState<T | undefined>
|
||||
abort?: (() => void)
|
||||
}
|
||||
|
||||
const useFetch = <R, T>(
|
||||
url: string,
|
||||
method: RequestInit['method'],
|
||||
needAuth: boolean,
|
||||
guardResponse: (data: unknown) => data is R,
|
||||
processData: (data: R) => T,
|
||||
initialData?: T,
|
||||
params?: RequestInit
|
||||
): UseFetchReturn<T> => {
|
||||
const useFetch = <T>(url: string, params: RequestInit | undefined, initialData: T, dataGuard: (obj: unknown) => obj is T) => {
|
||||
const [data, setData] = useState(initialData)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
const navigate = useNavigate()
|
||||
const [error, setError] = useState('')
|
||||
|
||||
const abortControllerRef = useRef<AbortController>()
|
||||
|
||||
@ -63,66 +17,39 @@ const useFetch = <R, T>(
|
||||
const abortController = new AbortController()
|
||||
abortControllerRef.current = abortController
|
||||
|
||||
const headers = new Headers({
|
||||
...params?.headers
|
||||
})
|
||||
|
||||
if (needAuth) {
|
||||
const token = getToken()
|
||||
|
||||
if (token === null) {
|
||||
return navigate('/login')
|
||||
}
|
||||
|
||||
headers.append('Auth', `Bearer ${token}`)
|
||||
}
|
||||
|
||||
fetch(url, {
|
||||
method,
|
||||
...params,
|
||||
headers,
|
||||
signal: abortControllerRef.current.signal,
|
||||
})
|
||||
fetch(url, { ...params, signal: abortControllerRef.current.signal })
|
||||
.then(res => {
|
||||
handleHTTPErrors(res)
|
||||
|
||||
return res.json()
|
||||
})
|
||||
.then(data => {
|
||||
if (!guardResponse(data)) {
|
||||
throw new Error('Malformed server response')
|
||||
if (!dataGuard(data)) {
|
||||
throw new Error('Неверный ответ от сервера')
|
||||
}
|
||||
|
||||
setData(processData(data))
|
||||
setData(data)
|
||||
setLoading(false)
|
||||
})
|
||||
.catch(err => {
|
||||
if (err instanceof Error && !isAborted(err)) {
|
||||
setError('Ошибка сети')
|
||||
}
|
||||
|
||||
setLoading(false)
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
console.log(url, params, err)
|
||||
}
|
||||
}
|
||||
|
||||
setLoading(false)
|
||||
})
|
||||
|
||||
return () => abortControllerRef.current?.abort()
|
||||
}, [url, method, needAuth, params, guardResponse, processData, navigate])
|
||||
}, [url, params, dataGuard])
|
||||
|
||||
return {
|
||||
...(
|
||||
error === null ? ({
|
||||
data: data!, error: null
|
||||
}) : ({ data: undefined, error })
|
||||
),
|
||||
loading,
|
||||
setData,
|
||||
data, loading, error,
|
||||
abort: abortControllerRef.current?.abort.bind(abortControllerRef.current)
|
||||
}
|
||||
}
|
||||
|
||||
export default useFetch
|
||||
|
||||
export { gotError, fallbackError }
|
||||
|
96
front/src/hooks/api/useHomeAnnouncementList.ts
Normal file
@ -0,0 +1,96 @@
|
||||
import useFetch from './useFetch'
|
||||
import { FiltersType, filterNames } from '../../utils/filters'
|
||||
import { isArrayOf, isObject } from '../../utils/types'
|
||||
import { API_URL } from '../../config'
|
||||
import { Category, isCategory } from '../../assets/category'
|
||||
|
||||
const initialAnnouncements = { list_of_announcements: [], Success: true }
|
||||
|
||||
type AnnouncementsListResponse = {
|
||||
list_of_announcements: AnnouncementResponse[],
|
||||
Success: boolean
|
||||
}
|
||||
|
||||
const isAnnouncementsListResponse = (obj: unknown): obj is AnnouncementsListResponse => isObject(obj, {
|
||||
'list_of_announcements': obj => isArrayOf<AnnouncementResponse>(obj, isAnnouncementResponse),
|
||||
'Success': 'boolean'
|
||||
})
|
||||
|
||||
type AnnouncementResponse = {
|
||||
id: number,
|
||||
user_id: number,
|
||||
name: string,
|
||||
category: Category,
|
||||
best_by: number,
|
||||
address: string,
|
||||
longtitude: number,
|
||||
latitude: number,
|
||||
description: string,
|
||||
src: string | null,
|
||||
metro: string,
|
||||
trashId: number | null,
|
||||
booked_by: number
|
||||
}
|
||||
|
||||
const isAnnouncementResponse = (obj: unknown): obj is AnnouncementResponse => isObject(obj, {
|
||||
'id': 'number',
|
||||
'user_id': 'number',
|
||||
'name': 'string',
|
||||
'category': isCategory,
|
||||
'best_by': 'number',
|
||||
'address': 'string',
|
||||
'longtitude': 'number',
|
||||
'latitude': 'number',
|
||||
'description': 'string',
|
||||
'src': 'string?',
|
||||
'metro': 'string',
|
||||
'trashId': 'number?',
|
||||
'booked_by': 'number'
|
||||
})
|
||||
|
||||
type Announcement = {
|
||||
id: number,
|
||||
userId: number,
|
||||
name: string,
|
||||
category: Category,
|
||||
bestBy: number,
|
||||
address: string,
|
||||
lng: number,
|
||||
lat: number,
|
||||
description: string | null,
|
||||
src: string | null,
|
||||
metro: string,
|
||||
trashId: number | null,
|
||||
bookedBy: number
|
||||
}
|
||||
|
||||
const composeFilters = (filters: FiltersType) => Object.fromEntries(
|
||||
filterNames.map(
|
||||
fName => [fName, filters[fName]?.toString()]
|
||||
).filter((p): p is [string, string] => typeof p[1] !== 'undefined')
|
||||
)
|
||||
|
||||
const useHomeAnnouncementList = (filters: FiltersType) => {
|
||||
const { data, loading, error } = useFetch(
|
||||
API_URL + '/announcements?' + new URLSearchParams(composeFilters(filters)).toString(),
|
||||
undefined,
|
||||
initialAnnouncements,
|
||||
isAnnouncementsListResponse
|
||||
)
|
||||
|
||||
const annList = data.list_of_announcements
|
||||
|
||||
const res: Announcement[] = annList.map(ann => ({
|
||||
...ann,
|
||||
lat: ann.latitude,
|
||||
lng: ann.longtitude,
|
||||
bestBy: ann.best_by,
|
||||
bookedBy: ann.booked_by,
|
||||
userId: ann.user_id
|
||||
}))
|
||||
|
||||
return { data: error ? [] : res, loading, error }
|
||||
}
|
||||
|
||||
export type { Announcement, AnnouncementsListResponse }
|
||||
export default useHomeAnnouncementList
|
@ -1,17 +0,0 @@
|
||||
import { LatLng } from 'leaflet'
|
||||
|
||||
import useFetch from './useFetch'
|
||||
import { composeOsmAddressURL, processOsmAddress } from '../../api/osmAddress'
|
||||
import { isOsmAddressResponse } from '../../api/osmAddress/types'
|
||||
|
||||
const useOsmAddresses = (addressPosition: LatLng) =>
|
||||
useFetch(
|
||||
composeOsmAddressURL(addressPosition),
|
||||
'GET',
|
||||
false,
|
||||
isOsmAddressResponse,
|
||||
processOsmAddress,
|
||||
''
|
||||
)
|
||||
|
||||
export default useOsmAddresses
|
@ -1,18 +1,35 @@
|
||||
import { LatLng } from 'leaflet'
|
||||
|
||||
import { API_URL } from '../../config'
|
||||
import { isArrayOf, isObject } from '../../utils/types'
|
||||
import useFetch from './useFetch'
|
||||
import { composeTrashboxURL } from '../../api/trashbox'
|
||||
import { isTrashboxResponse } from '../../api/trashbox/types'
|
||||
import { isString } from '../../utils/types'
|
||||
|
||||
const useTrashboxes = (position: LatLng) =>
|
||||
useFetch(
|
||||
composeTrashboxURL(position),
|
||||
'GET',
|
||||
true,
|
||||
isTrashboxResponse,
|
||||
(data) => data,
|
||||
[]
|
||||
type Trashbox = {
|
||||
Lat: number,
|
||||
Lng: number,
|
||||
Address: string,
|
||||
Categories: string[]
|
||||
}
|
||||
|
||||
const isTrashbox = (obj: unknown): obj is Trashbox => isObject(obj, {
|
||||
'Lat': 'number',
|
||||
'Lng': 'number',
|
||||
'Address': 'string',
|
||||
'Categories': obj => isArrayOf<string>(obj, isString)
|
||||
})
|
||||
|
||||
const useTrashboxes = (position: LatLng) => {
|
||||
return useFetch(
|
||||
API_URL + '/trashbox?' + new URLSearchParams({
|
||||
lat: position.lat.toString(),
|
||||
lng: position.lng.toString()
|
||||
}).toString(),
|
||||
undefined,
|
||||
[],
|
||||
(obj): obj is Trashbox[] => isArrayOf(obj, isTrashbox)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export type { Trashbox }
|
||||
export default useTrashboxes
|
||||
|
@ -5,25 +5,22 @@ import { latLng } from 'leaflet'
|
||||
|
||||
import { ClickHandler, LocationMarker, TrashboxMarkers } from '../components'
|
||||
import { useAddAnnouncement, useTrashboxes } from '../hooks/api'
|
||||
import { isObject } from '../utils/types'
|
||||
import { handleHTTPErrors } from '../utils'
|
||||
import { categories, categoryNames } from '../assets/category'
|
||||
import { stations, lines, lineNames } from '../assets/metro'
|
||||
import { fallbackError, gotError } from '../hooks/api/useFetch'
|
||||
import { useOsmAddresses } from '../hooks/api'
|
||||
|
||||
function AddPage() {
|
||||
const [addressPosition, setAddressPosition] = useState(latLng(59.972, 30.3227))
|
||||
const [address, setAddress] = useState('')
|
||||
|
||||
const trashboxes = useTrashboxes(addressPosition)
|
||||
const [selectedTrashbox, setSelectedTrashbox] = useState({ index: -1, category: '' })
|
||||
|
||||
const address = useOsmAddresses(addressPosition)
|
||||
|
||||
useEffect(() => {
|
||||
if (!gotError(address))
|
||||
void (async () => {
|
||||
try {
|
||||
const res = await fetch(location.protocol + '//nominatim.openstreetmap.org/search?format=json&q=' + encodeURIComponent(address.data))
|
||||
const res = await fetch(location.protocol + '//nominatim.openstreetmap.org/search?format=json&q=' + address)
|
||||
|
||||
handleHTTPErrors(res)
|
||||
|
||||
@ -37,6 +34,27 @@ function AddPage() {
|
||||
})()
|
||||
}, [address])
|
||||
|
||||
useEffect(() => {
|
||||
void (async () => {
|
||||
try {
|
||||
const res = await fetch(`${location.protocol}//nominatim.openstreetmap.org/reverse?format=json&accept-language=ru&lat=${addressPosition.lat}&lon=${addressPosition.lng}`)
|
||||
|
||||
handleHTTPErrors(res)
|
||||
|
||||
const fetchData: unknown = await res.json()
|
||||
|
||||
if (!isObject<{ display_name: string }>(fetchData, { 'display_name': 'string' })) {
|
||||
throw new Error('Malformed server response')
|
||||
}
|
||||
|
||||
setAddress(fetchData.display_name)
|
||||
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
})()
|
||||
}, [addressPosition])
|
||||
|
||||
const { doAdd, status } = useAddAnnouncement()
|
||||
|
||||
const handleSubmit: FormEventHandler<HTMLFormElement> = (event) => {
|
||||
@ -47,7 +65,7 @@ function AddPage() {
|
||||
|
||||
formData.append('latitude', addressPosition.lat.toString())
|
||||
formData.append('longtitude', addressPosition.lng.toString())
|
||||
formData.append('address', address.data || '') // if address.error
|
||||
formData.append('address', address)
|
||||
formData.set('bestBy', new Date((formData.get('bestBy') as number | null) || 0).getTime().toString())
|
||||
|
||||
void doAdd(formData)
|
||||
@ -93,7 +111,7 @@ function AddPage() {
|
||||
url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
|
||||
/>
|
||||
<LocationMarker
|
||||
address={fallbackError(address)}
|
||||
address={address}
|
||||
position={addressPosition}
|
||||
setPosition={setAddressPosition}
|
||||
/>
|
||||
@ -102,7 +120,7 @@ function AddPage() {
|
||||
/>
|
||||
</MapContainer>
|
||||
</div>
|
||||
<p>Адрес: {fallbackError(address)}</p>
|
||||
<p>Адрес: {address}</p>
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group className='mb-3' controlId='description'>
|
||||
@ -147,7 +165,7 @@ function AddPage() {
|
||||
<p>Загрузка...</p>
|
||||
</div>
|
||||
) : (
|
||||
gotError(trashboxes) ? (
|
||||
trashboxes.error ? (
|
||||
<p
|
||||
style={{ height: 400 }}
|
||||
className='text-danger'
|
||||
@ -173,7 +191,7 @@ function AddPage() {
|
||||
)
|
||||
}
|
||||
</div>
|
||||
{!gotError(trashboxes) && selectedTrashbox.index > -1 ? (
|
||||
{selectedTrashbox.index > -1 ? (
|
||||
<p>Выбран пункт сбора мусора на {
|
||||
trashboxes.data[selectedTrashbox.index].Address
|
||||
} с категорией {selectedTrashbox.category}</p>
|
||||
|
@ -4,13 +4,11 @@ import { Story } from 'react-insta-stories/dist/interfaces'
|
||||
|
||||
import { BottomNavBar, AnnouncementDetails, Filters } from '../components'
|
||||
import { useStoryDimensions } from '../hooks'
|
||||
import { useAnnouncements } from '../hooks/api'
|
||||
import { useHomeAnnouncementList } from '../hooks/api'
|
||||
import { defaultFilters } from '../utils/filters'
|
||||
import { Announcement } from '../api/announcement/types'
|
||||
import { categoryGraphics } from '../assets/category'
|
||||
|
||||
import { Announcement } from '../hooks/api/useHomeAnnouncementList'
|
||||
import puffSpinner from '../assets/puff.svg'
|
||||
import { gotError } from '../hooks/api/useFetch'
|
||||
import { categoryGraphics } from '../assets/category'
|
||||
|
||||
function generateStories(announcements: Announcement[]): Story[] {
|
||||
return announcements.map(announcement => {
|
||||
@ -23,15 +21,15 @@ function generateStories(announcements: Announcement[]): Story[] {
|
||||
})
|
||||
}
|
||||
|
||||
function fallbackGenerateStories(announcementsFetch: ReturnType<typeof useAnnouncements>) {
|
||||
function fallbackGenerateStories(announcementsFetch: ReturnType<typeof useHomeAnnouncementList>) {
|
||||
const stories = generateStories(announcementsFetch.data)
|
||||
|
||||
if (announcementsFetch.loading)
|
||||
return fallbackStory()
|
||||
|
||||
if (gotError(announcementsFetch))
|
||||
if (announcementsFetch.error)
|
||||
return fallbackStory(announcementsFetch.error, true)
|
||||
|
||||
const stories = generateStories(announcementsFetch.data)
|
||||
|
||||
if (stories.length === 0)
|
||||
return fallbackStory('Здесь пока пусто')
|
||||
|
||||
@ -51,25 +49,19 @@ const fallbackStory = (text = '', isError = false): Story[] => [{
|
||||
},
|
||||
}]
|
||||
|
||||
const storiesContainerCSS = {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: 'rgb(17, 17, 17)'
|
||||
}
|
||||
|
||||
function HomePage() {
|
||||
const { height, width } = useStoryDimensions(16 / 10)
|
||||
|
||||
const [filterShown, setFilterShown] = useState(false)
|
||||
const [filter, setFilter] = useState(defaultFilters)
|
||||
|
||||
const announcementsFetch = useAnnouncements(filter)
|
||||
const announcementsFetch = useHomeAnnouncementList(filter)
|
||||
|
||||
const stories = fallbackGenerateStories(announcementsFetch)
|
||||
|
||||
return (<>
|
||||
<Filters filter={filter} setFilter={setFilter} filterShown={filterShown} setFilterShown={setFilterShown} />
|
||||
<div style={storiesContainerCSS}>
|
||||
<div style={{ display: 'flex', justifyContent: 'center', backgroundColor: 'rgb(17, 17, 17)' }}>
|
||||
<Stories
|
||||
stories={stories}
|
||||
defaultInterval={11000}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Announcement } from '../api/announcement/types'
|
||||
import { Announcement } from '../hooks/api/useHomeAnnouncementList'
|
||||
|
||||
const filterNames = ['userId', 'category', 'metro', 'bookedBy'] as const
|
||||
type FilterNames = typeof filterNames[number]
|
||||
@ -7,11 +7,5 @@ type FiltersType = Partial<Pick<Announcement, FilterNames>>
|
||||
|
||||
const defaultFilters: FiltersType = { userId: undefined, category: undefined, metro: undefined, bookedBy: undefined }
|
||||
|
||||
const URLEncodeFilters = (filters: FiltersType) => Object.fromEntries(
|
||||
filterNames.map(
|
||||
fName => [fName, filters[fName]?.toString()]
|
||||
).filter((p): p is [string, string] => typeof p[1] !== 'undefined')
|
||||
)
|
||||
|
||||
export type { FilterNames, FiltersType }
|
||||
export { defaultFilters, filterNames, URLEncodeFilters }
|
||||
export { defaultFilters, filterNames }
|
||||
|
@ -1,20 +0,0 @@
|
||||
import L from 'leaflet'
|
||||
|
||||
import itemMarker from '../assets/itemMarker.png'
|
||||
import trashMarker from '../assets/trashMarker.png'
|
||||
|
||||
const iconItem = new L.Icon({
|
||||
iconUrl: itemMarker,
|
||||
iconRetinaUrl: itemMarker,
|
||||
popupAnchor: [0, 0],
|
||||
iconSize: [41, 41],
|
||||
})
|
||||
|
||||
const iconTrash = new L.Icon({
|
||||
iconUrl: trashMarker,
|
||||
iconRetinaUrl: trashMarker,
|
||||
popupAnchor: [0, 0],
|
||||
iconSize: [34, 41],
|
||||
})
|
||||
|
||||
export { iconItem, iconTrash }
|