Compare commits

..

No commits in common. "cb848739e5e4478b31bc6477081d8a1db1516293" and "7b0ccc525caf639c8bdd49c62b5dc8b8119c2f3c" have entirely different histories.

35 changed files with 191 additions and 371 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

View File

@ -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,
}

View File

@ -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 }

View File

@ -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,
}

View File

@ -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 }

View File

@ -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,
}

View File

@ -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 }

View File

@ -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 }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

View File

@ -4,8 +4,7 @@ import { MapContainer, Marker, Popup, TileLayer } from 'react-leaflet'
import LineDot from './LineDot' import LineDot from './LineDot'
import { categoryNames } from '../assets/category' import { categoryNames } from '../assets/category'
import { useBook } from '../hooks/api' import { useBook } from '../hooks/api'
import { Announcement } from '../api/announcement/types' import { Announcement } from '../hooks/api/useHomeAnnouncementList'
import { iconItem } from '../utils/markerIcons'
type AnnouncementDetailsProps = { type AnnouncementDetailsProps = {
close: () => void, close: () => void,
@ -42,7 +41,7 @@ function AnnouncementDetails({ close, announcement: { id, name, category, bestBy
url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png' url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
/> />
<Marker icon={iconItem} position={[lat, lng]}> <Marker position={[lat, lng]}>
<Popup> <Popup>
{address} {address}
<br /> <br />

View File

@ -2,7 +2,6 @@ import { Marker, Popup, useMapEvents } from 'react-leaflet'
import { LatLng } from 'leaflet' import { LatLng } from 'leaflet'
import { SetState } from '../utils/types' import { SetState } from '../utils/types'
import { iconItem } from '../utils/markerIcons'
type LocationMarkerProps = { type LocationMarkerProps = {
address: string, address: string,
@ -25,7 +24,7 @@ const LocationMarker = ({ address, position, setPosition }: LocationMarkerProps)
}) })
return ( return (
<Marker icon={iconItem} position={position}> <Marker position={position}>
<Popup> <Popup>
{address} {address}
{position.lat.toFixed(4)}, {position.lng.toFixed(4)} {position.lat.toFixed(4)}, {position.lng.toFixed(4)}

View File

@ -1,7 +1,6 @@
import { Marker, Popup } from 'react-leaflet' import { Marker, Popup } from 'react-leaflet'
import { Trashbox } from '../api/trashbox/types' import { Trashbox } from '../hooks/api/useTrashboxes'
import { iconTrash } from '../utils/markerIcons'
type TrashboxMarkersProps = { type TrashboxMarkersProps = {
trashboxes: Trashbox[], trashboxes: Trashbox[],
@ -11,7 +10,7 @@ type TrashboxMarkersProps = {
const TrashboxMarkers = ({ trashboxes, selectTrashbox }: TrashboxMarkersProps) => { const TrashboxMarkers = ({ trashboxes, selectTrashbox }: TrashboxMarkersProps) => {
return ( return (
<>{trashboxes.map((trashbox, index) => ( <>{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> <Popup>
<p>{trashbox.Address}</p> <p>{trashbox.Address}</p>
<p>Тип мусора: <> <p>Тип мусора: <>

View File

@ -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 useBook } from './useBook'
export { default as useAuth } from './useAuth' export { default as useAuth } from './useAuth'
export { default as useTrashboxes } from './useTrashboxes' export { default as useTrashboxes } from './useTrashboxes'
export { default as useAddAnnouncement } from './useAddAnnouncement' export { default as useAddAnnouncement } from './useAddAnnouncement'
export { default as useOsmAddresses } from './useOsmAddress'

View File

@ -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

View File

@ -1,57 +1,11 @@
import { useEffect, useRef, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import { handleHTTPErrors, isAborted } from '../../utils' import { handleHTTPErrors, isAborted } from '../../utils'
import { getToken } from '../../utils/auth'
import { useNavigate } from 'react-router-dom'
import { SetState } from '../../utils/types'
type UseFetchShared = { const useFetch = <T>(url: string, params: RequestInit | undefined, initialData: T, dataGuard: (obj: unknown) => obj is T) => {
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 [data, setData] = useState(initialData) const [data, setData] = useState(initialData)
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null) const [error, setError] = useState('')
const navigate = useNavigate()
const abortControllerRef = useRef<AbortController>() const abortControllerRef = useRef<AbortController>()
@ -63,66 +17,39 @@ const useFetch = <R, T>(
const abortController = new AbortController() const abortController = new AbortController()
abortControllerRef.current = abortController abortControllerRef.current = abortController
const headers = new Headers({ fetch(url, { ...params, signal: abortControllerRef.current.signal })
...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,
})
.then(res => { .then(res => {
handleHTTPErrors(res) handleHTTPErrors(res)
return res.json() return res.json()
}) })
.then(data => { .then(data => {
if (!guardResponse(data)) { if (!dataGuard(data)) {
throw new Error('Malformed server response') throw new Error('Неверный ответ от сервера')
} }
setData(processData(data)) setData(data)
setLoading(false) setLoading(false)
}) })
.catch(err => { .catch(err => {
if (err instanceof Error && !isAborted(err)) { if (err instanceof Error && !isAborted(err)) {
setError('Ошибка сети') setError('Ошибка сети')
}
setLoading(false)
if (import.meta.env.DEV) { if (import.meta.env.DEV) {
console.log(url, params, err) console.log(url, params, err)
} }
}
setLoading(false)
}) })
return () => abortControllerRef.current?.abort() return () => abortControllerRef.current?.abort()
}, [url, method, needAuth, params, guardResponse, processData, navigate]) }, [url, params, dataGuard])
return { return {
...( data, loading, error,
error === null ? ({
data: data!, error: null
}) : ({ data: undefined, error })
),
loading,
setData,
abort: abortControllerRef.current?.abort.bind(abortControllerRef.current) abort: abortControllerRef.current?.abort.bind(abortControllerRef.current)
} }
} }
export default useFetch export default useFetch
export { gotError, fallbackError }

View 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

View File

@ -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

View File

@ -1,18 +1,35 @@
import { LatLng } from 'leaflet' import { LatLng } from 'leaflet'
import { API_URL } from '../../config'
import { isArrayOf, isObject } from '../../utils/types'
import useFetch from './useFetch' import useFetch from './useFetch'
import { composeTrashboxURL } from '../../api/trashbox' import { isString } from '../../utils/types'
import { isTrashboxResponse } from '../../api/trashbox/types'
const useTrashboxes = (position: LatLng) => type Trashbox = {
useFetch( Lat: number,
composeTrashboxURL(position), Lng: number,
'GET', Address: string,
true, Categories: string[]
isTrashboxResponse, }
(data) => data,
[] 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 export default useTrashboxes

View File

@ -5,25 +5,22 @@ import { latLng } from 'leaflet'
import { ClickHandler, LocationMarker, TrashboxMarkers } from '../components' import { ClickHandler, LocationMarker, TrashboxMarkers } from '../components'
import { useAddAnnouncement, useTrashboxes } from '../hooks/api' import { useAddAnnouncement, useTrashboxes } from '../hooks/api'
import { isObject } from '../utils/types'
import { handleHTTPErrors } from '../utils' import { handleHTTPErrors } from '../utils'
import { categories, categoryNames } from '../assets/category' import { categories, categoryNames } from '../assets/category'
import { stations, lines, lineNames } from '../assets/metro' import { stations, lines, lineNames } from '../assets/metro'
import { fallbackError, gotError } from '../hooks/api/useFetch'
import { useOsmAddresses } from '../hooks/api'
function AddPage() { function AddPage() {
const [addressPosition, setAddressPosition] = useState(latLng(59.972, 30.3227)) const [addressPosition, setAddressPosition] = useState(latLng(59.972, 30.3227))
const [address, setAddress] = useState('')
const trashboxes = useTrashboxes(addressPosition) const trashboxes = useTrashboxes(addressPosition)
const [selectedTrashbox, setSelectedTrashbox] = useState({ index: -1, category: '' }) const [selectedTrashbox, setSelectedTrashbox] = useState({ index: -1, category: '' })
const address = useOsmAddresses(addressPosition)
useEffect(() => { useEffect(() => {
if (!gotError(address))
void (async () => { void (async () => {
try { 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) handleHTTPErrors(res)
@ -37,6 +34,27 @@ function AddPage() {
})() })()
}, [address]) }, [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 { doAdd, status } = useAddAnnouncement()
const handleSubmit: FormEventHandler<HTMLFormElement> = (event) => { const handleSubmit: FormEventHandler<HTMLFormElement> = (event) => {
@ -47,7 +65,7 @@ function AddPage() {
formData.append('latitude', addressPosition.lat.toString()) formData.append('latitude', addressPosition.lat.toString())
formData.append('longtitude', addressPosition.lng.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()) formData.set('bestBy', new Date((formData.get('bestBy') as number | null) || 0).getTime().toString())
void doAdd(formData) void doAdd(formData)
@ -93,7 +111,7 @@ function AddPage() {
url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png' url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
/> />
<LocationMarker <LocationMarker
address={fallbackError(address)} address={address}
position={addressPosition} position={addressPosition}
setPosition={setAddressPosition} setPosition={setAddressPosition}
/> />
@ -102,7 +120,7 @@ function AddPage() {
/> />
</MapContainer> </MapContainer>
</div> </div>
<p>Адрес: {fallbackError(address)}</p> <p>Адрес: {address}</p>
</Form.Group> </Form.Group>
<Form.Group className='mb-3' controlId='description'> <Form.Group className='mb-3' controlId='description'>
@ -147,7 +165,7 @@ function AddPage() {
<p>Загрузка...</p> <p>Загрузка...</p>
</div> </div>
) : ( ) : (
gotError(trashboxes) ? ( trashboxes.error ? (
<p <p
style={{ height: 400 }} style={{ height: 400 }}
className='text-danger' className='text-danger'
@ -173,7 +191,7 @@ function AddPage() {
) )
} }
</div> </div>
{!gotError(trashboxes) && selectedTrashbox.index > -1 ? ( {selectedTrashbox.index > -1 ? (
<p>Выбран пункт сбора мусора на { <p>Выбран пункт сбора мусора на {
trashboxes.data[selectedTrashbox.index].Address trashboxes.data[selectedTrashbox.index].Address
} с категорией {selectedTrashbox.category}</p> } с категорией {selectedTrashbox.category}</p>

View File

@ -4,13 +4,11 @@ import { Story } from 'react-insta-stories/dist/interfaces'
import { BottomNavBar, AnnouncementDetails, Filters } from '../components' import { BottomNavBar, AnnouncementDetails, Filters } from '../components'
import { useStoryDimensions } from '../hooks' import { useStoryDimensions } from '../hooks'
import { useAnnouncements } from '../hooks/api' import { useHomeAnnouncementList } from '../hooks/api'
import { defaultFilters } from '../utils/filters' import { defaultFilters } from '../utils/filters'
import { Announcement } from '../api/announcement/types' import { Announcement } from '../hooks/api/useHomeAnnouncementList'
import { categoryGraphics } from '../assets/category'
import puffSpinner from '../assets/puff.svg' import puffSpinner from '../assets/puff.svg'
import { gotError } from '../hooks/api/useFetch' import { categoryGraphics } from '../assets/category'
function generateStories(announcements: Announcement[]): Story[] { function generateStories(announcements: Announcement[]): Story[] {
return announcements.map(announcement => { 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) if (announcementsFetch.loading)
return fallbackStory() return fallbackStory()
if (gotError(announcementsFetch)) if (announcementsFetch.error)
return fallbackStory(announcementsFetch.error, true) return fallbackStory(announcementsFetch.error, true)
const stories = generateStories(announcementsFetch.data)
if (stories.length === 0) if (stories.length === 0)
return fallbackStory('Здесь пока пусто') 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() { function HomePage() {
const { height, width } = useStoryDimensions(16 / 10) const { height, width } = useStoryDimensions(16 / 10)
const [filterShown, setFilterShown] = useState(false) const [filterShown, setFilterShown] = useState(false)
const [filter, setFilter] = useState(defaultFilters) const [filter, setFilter] = useState(defaultFilters)
const announcementsFetch = useAnnouncements(filter) const announcementsFetch = useHomeAnnouncementList(filter)
const stories = fallbackGenerateStories(announcementsFetch) const stories = fallbackGenerateStories(announcementsFetch)
return (<> return (<>
<Filters filter={filter} setFilter={setFilter} filterShown={filterShown} setFilterShown={setFilterShown} /> <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={stories} stories={stories}
defaultInterval={11000} defaultInterval={11000}

View File

@ -1,4 +1,4 @@
import { Announcement } from '../api/announcement/types' import { Announcement } from '../hooks/api/useHomeAnnouncementList'
const filterNames = ['userId', 'category', 'metro', 'bookedBy'] as const const filterNames = ['userId', 'category', 'metro', 'bookedBy'] as const
type FilterNames = typeof filterNames[number] 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 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 type { FilterNames, FiltersType }
export { defaultFilters, filterNames, URLEncodeFilters } export { defaultFilters, filterNames }

View File

@ -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 }