Split metro stations by lines and their indication

Fixed details dialog size
Added map location setting by click
Reorganized hooks and components imports with index.js
Removed orphane error indication on homepage
This commit is contained in:
Dmitriy Shishkov 2023-07-11 13:29:04 +03:00
parent cbbd714fbf
commit 960ad7ce0d
Signed by: dm1sh
GPG Key ID: 027994B0AA357688
13 changed files with 135 additions and 71 deletions

View File

@ -11,5 +11,6 @@ module.exports = {
plugins: ['react-refresh'], plugins: ['react-refresh'],
rules: { rules: {
'react-refresh/only-export-components': 'warn', 'react-refresh/only-export-components': 'warn',
'react/prop-types': 'off'
}, },
} }

View File

@ -2,7 +2,7 @@ import { BrowserRouter as Router, Route, Routes, Link } from 'react-router-dom'
import { HomePage, AddPage, LoginPage, UserPage } from './pages' import { HomePage, AddPage, LoginPage, UserPage } from './pages'
import WithToken from './components/WithToken' import { WithToken } from './components'
import 'leaflet/dist/leaflet.css' import 'leaflet/dist/leaflet.css'

View File

@ -1,5 +1,5 @@
const metros = { const stations = {
red: [ red: new Set([
"Девяткино", "Девяткино",
"Гражданский проспект", "Гражданский проспект",
"Академическая", "Академическая",
@ -19,8 +19,8 @@ const metros = {
"Автово", "Автово",
"Ленинский проспект", "Ленинский проспект",
"Проспект Ветеранов" "Проспект Ветеранов"
], ]),
blue: [ blue: new Set([
"Парнас", "Парнас",
"Проспект Просвещения", "Проспект Просвещения",
"Озерки", "Озерки",
@ -39,8 +39,8 @@ const metros = {
"Московская", "Московская",
"Звёздная", "Звёздная",
"Купчино" "Купчино"
], ]),
green: [ green: new Set([
"Приморская", "Приморская",
"Беговая", "Беговая",
"Василеостровская", "Василеостровская",
@ -52,11 +52,9 @@ const metros = {
"Пролетарская", "Пролетарская",
"Обухово", "Обухово",
"Рыбацкое" "Рыбацкое"
], ]),
orange: [ orange: new Set([
"Спасская", "Спасская",
"Горный институт",
"Театральная",
"Достоевская", "Достоевская",
"Лиговский проспект", "Лиговский проспект",
"Площадь Александра Невского", "Площадь Александра Невского",
@ -64,8 +62,8 @@ const metros = {
"Ладожская", "Ладожская",
"Проспект Большевиков", "Проспект Большевиков",
"Улица Дыбенко" "Улица Дыбенко"
], ]),
violet: [ violet: new Set([
"Комендантский проспект", "Комендантский проспект",
"Старая Деревня", "Старая Деревня",
"Крестовский остров", "Крестовский остров",
@ -81,15 +79,23 @@ const metros = {
"Проспект славы", "Проспект славы",
"Дунайскай", "Дунайскай",
"Шушары" "Шушары"
], ]),
gold: [
"Юго западная",
"Каретная",
"Путиловская",
"Броневая",
"Заставская",
"Боровая"
]
} }
export { metros } const colors = {
red: "#D6083B",
blue: "#0078C9",
green: "#009A49",
orange: "#EA7125",
violet: "#702785",
}
const lines = {
red: "Красная",
blue: "Синяя",
green: "Зелёная",
orange: "Оранжевая",
violet: "Фиолетовая",
}
export { stations, colors, lines }

View File

@ -1,18 +1,19 @@
import { Modal, Button } from 'react-bootstrap' import { Modal, Button } from 'react-bootstrap'
import { MapContainer, Marker, Popup, TileLayer } from 'react-leaflet' import { MapContainer, Marker, Popup, TileLayer } from 'react-leaflet'
import LineDot from './LineDot'
import { categoryNames } from '../assets/category' import { categoryNames } from '../assets/category'
import { useBook } from '../hooks/api' import { useBook } from '../hooks/api'
function AnnouncementDetails({ close, announcement: { id, name, category, bestBy, description, lat, lng, address, metro } }) { function AnnouncementDetails({ close, announcement: { id, name, category, bestBy, description, lat, lng, address, metro } }) {
const {handleBook, status: bookStatus} = useBook(id) const { handleBook, status: bookStatus } = useBook(id)
return ( return (
<div <div
className="modal show" className="modal"
style={{ display: 'flex', position: 'initial', alignItems: "center" }} style={{ display: 'flex', alignItems: "center", justifyContent: "center" }}
> >
<Modal.Dialog style={{minWidth: "50vw"}}> <Modal.Dialog style={{ minWidth: "50vw" }}>
<Modal.Header closeButton onHide={close}> <Modal.Header closeButton onHide={close}>
<Modal.Title> <Modal.Title>
Подробнее Подробнее
@ -35,14 +36,18 @@ function AnnouncementDetails({ close, announcement: { id, name, category, bestBy
/> />
<Marker position={[lat, lng]}> <Marker position={[lat, lng]}>
<Popup>{address + "\n" + metro}</Popup> <Popup>
{address}
<br />
<LineDot station={metro} /> {metro}
</Popup>
</Marker> </Marker>
</MapContainer> </MapContainer>
</Modal.Body> </Modal.Body>
<Modal.Footer> <Modal.Footer>
<Button variant='success' onClick={handleBook}> <Button variant='success' onClick={handleBook}>
{bookStatus || "Забронировать"} {bookStatus || "Забронировать"}
</Button> </Button>
</Modal.Footer> </Modal.Footer>
</Modal.Dialog> </Modal.Dialog>

View File

@ -0,0 +1,12 @@
import { useMapEvent } from "react-leaflet"
function ClickHandler({ setPosition }) {
const map = useMapEvent('click', (e) => {
setPosition(e.latlng)
map.setView(e.latlng)
})
return null
}
export default ClickHandler

View File

@ -1,7 +1,7 @@
import { Button, Form, Modal } from "react-bootstrap" import { Button, Form, Modal } from "react-bootstrap"
import { categoryNames } from "../assets/category" import { categoryNames } from "../assets/category"
import { metros } from '../assets/metro' import { stations, lines } from '../assets/metro'
function Filters({ filter, setFilter, filterShown, setFilterShown }) { function Filters({ filter, setFilter, filterShown, setFilterShown }) {
@ -55,11 +55,13 @@ function Filters({ filter, setFilter, filterShown, setFilterShown }) {
<option value=""> <option value="">
Выберите станцию метро Выберите станцию метро
</option> </option>
{Object.entries(metros).map( {Object.entries(stations).map(
([branch, stations]) => ([line, stations]) =>
stations.map(metro => <optgroup key={line} label={lines[line]}>
<option key={metro} value={metro}>{metro}</option> {Array.from(stations).map(metro =>
) <option key={metro} value={metro}>{metro}</option>
)}
</optgroup>
)} )}
</Form.Select> </Form.Select>
</Form.Group> </Form.Group>

View File

@ -0,0 +1,12 @@
import { colors, lines } from '../assets/metro'
import { lineByName } from '../utils/metro'
function LineDot({ station }) {
const line = lineByName(station)
const lineTitle = lines[line]
const color = colors[line]
return <span title={`${lineTitle} ветка`} style={{ color: color }}>&#11044;</span>
}
export default LineDot

View File

@ -0,0 +1,19 @@
import AnnouncementDetails from "./AnnouncementDetails"
import BottomNavBar from "./BottomNavBar"
import Filters from "./Filters"
import LineDot from "./LineDot"
import LocationMarker from './LocationMarker'
import TrashboxMarkers from './TrashboxMarkers'
import WithToken from './WithToken'
import ClickHandler from './ClickHandler'
export {
AnnouncementDetails,
BottomNavBar,
Filters,
LineDot,
LocationMarker,
TrashboxMarkers,
WithToken,
ClickHandler,
}

5
front/src/hooks/index.js Normal file
View File

@ -0,0 +1,5 @@
import useStoryDimensions from "./useStoryDimensions"
export {
useStoryDimensions,
}

View File

@ -1,13 +1,13 @@
import { useEffect, useState } from "react" import { useEffect, useState } from "react"
import { Form, Button, Card } from "react-bootstrap" import { Form, Button, Card } from "react-bootstrap"
import { MapContainer, TileLayer } from 'react-leaflet' import { MapContainer, TileLayer } from 'react-leaflet'
import { latLng } from "leaflet"
import { ClickHandler, LocationMarker, TrashboxMarkers } from "../components"
import { useAddAnnouncement, useTrashboxes } from "../hooks/api"
import { categoryNames } from "../assets/category" import { categoryNames } from "../assets/category"
import { latLng } from "leaflet" import { stations, lines } from "../assets/metro"
import { metros } from "../assets/metro"
import LocationMarker from "../components/LocationMarker"
import TrashboxMarkers from "../components/TrashboxMarkers"
import { useAddAnnouncement, useTrashboxes } 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))
@ -46,7 +46,7 @@ function AddPage() {
})() })()
}, [addressPosition]) }, [addressPosition])
const {doAdd, status} = useAddAnnouncement() const { doAdd, status } = useAddAnnouncement()
const handleSubmit = (event) => { const handleSubmit = (event) => {
event.preventDefault() event.preventDefault()
@ -107,6 +107,9 @@ function AddPage() {
position={addressPosition} position={addressPosition}
setPosition={setAddressPosition} setPosition={setAddressPosition}
/> />
<ClickHandler
setPosition={setAddressPosition}
/>
</MapContainer> </MapContainer>
</div> </div>
<p>Адрес: {address}</p> <p>Адрес: {address}</p>
@ -134,11 +137,13 @@ function AddPage() {
<option value=""> <option value="">
Укажите ближайщую станцию метро Укажите ближайщую станцию метро
</option> </option>
{Object.entries(metros).map( {Object.entries(stations).map(
([branch, stations]) => ([line, stations]) =>
stations.map(metro => <optgroup key={line} label={lines[line]}>
<option key={metro} value={metro}>{metro}</option> {Array.from(stations).map(metro =>
) <option key={metro} value={metro}>{metro}</option>
)}
</optgroup>
)} )}
</Form.Select> </Form.Select>
</Form.Group> </Form.Group>

View File

@ -1,11 +1,8 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import Stories from 'react-insta-stories' import Stories from 'react-insta-stories'
import BottomNavBar from '../components/BottomNavBar' import { BottomNavBar, AnnouncementDetails, Filters } from '../components'
import useStoryDimensions from '../hooks/useStoryDimensions' import { useStoryDimensions } from '../hooks'
import AnnouncementDetails from '../components/AnnouncementDetails'
import Filters from '../components/Filters'
import { useHomeAnnouncementList } from '../hooks/api' import { useHomeAnnouncementList } from '../hooks/api'
import puffSpinner from '../assets/puff.svg' import puffSpinner from '../assets/puff.svg'
@ -29,21 +26,21 @@ function fallbackGenerateStories(announcementsFetch) {
return fallbackStory() return fallbackStory()
if (announcementsFetch.error) if (announcementsFetch.error)
return fallbackStory(announcementsFetch.error) return fallbackStory(announcementsFetch.error, true)
if (stories.length == 0) if (stories.length === 0)
return fallbackStory("Здесь пока пусто") return fallbackStory("Здесь пока пусто")
return stories return stories
} }
const fallbackStory = (text) => [{ const fallbackStory = (text, isError = false) => [{
content: ({ action }) => { content: ({ action }) => {
// eslint-disable-next-line react-hooks/rules-of-hooks // eslint-disable-next-line react-hooks/rules-of-hooks
useEffect(() => { action('pause') }, [action]) useEffect(() => { action('pause') }, [action])
return ( return (
<div style={{ margin: 'auto' }}> <div style={{ margin: 'auto' }} className={isError ? "text-danger" : ''}>
{text || <img src={puffSpinner} />} {text || <img src={puffSpinner} />}
</div> </div>
) )
@ -66,22 +63,14 @@ function HomePage() {
return (<> return (<>
<Filters filter={filter} setFilter={setFilter} filterShown={filterShown} setFilterShown={setFilterShown} /> <Filters filter={filter} setFilter={setFilter} filterShown={filterShown} setFilterShown={setFilterShown} />
<div style={{ display: "flex", justifyContent: "center", backgroundColor: "rgb(17, 17, 17)" }}> <div style={{ display: "flex", justifyContent: "center", backgroundColor: "rgb(17, 17, 17)" }}>
{announcementsFetch.error ? <Stories
( stories={stories}
<div style={{ width, height, display: 'flex', alignItems: 'center', justifyContent: 'center' }}> defaultInterval={11000}
<p className='text-danger'>{announcementsFetch.error}</p> height={height}
</div> width={width}
) : ( loop={true}
<Stories keyboardNavigation={true}
stories={stories} />
defaultInterval={11000}
height={height}
width={width}
loop={true}
keyboardNavigation={true}
/>
)
}
</div> </div>
<BottomNavBar toggleFilters={setFilterShown} width={width} /> <BottomNavBar toggleFilters={setFilterShown} width={width} />
</>) </>)

View File

@ -1,5 +1,6 @@
import { Form, Button, Card, Tabs, Tab } from "react-bootstrap" import { Form, Button, Card, Tabs, Tab } from "react-bootstrap"
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { useAuth } from "../hooks/api"; import { useAuth } from "../hooks/api";
import { setToken } from "../utils/auth"; import { setToken } from "../utils/auth";

7
front/src/utils/metro.js Normal file
View File

@ -0,0 +1,7 @@
import { stations } from "../assets/metro"
function lineByName(name) {
return Object.keys(stations).find(line => stations[line].has(name))
}
export { lineByName }