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'],
rules: {
'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 WithToken from './components/WithToken'
import { WithToken } from './components'
import 'leaflet/dist/leaflet.css'

View File

@ -1,5 +1,5 @@
const metros = {
red: [
const stations = {
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 { MapContainer, Marker, Popup, TileLayer } from 'react-leaflet'
import LineDot from './LineDot'
import { categoryNames } from '../assets/category'
import { useBook } from '../hooks/api'
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 (
<div
className="modal show"
style={{ display: 'flex', position: 'initial', alignItems: "center" }}
className="modal"
style={{ display: 'flex', alignItems: "center", justifyContent: "center" }}
>
<Modal.Dialog style={{minWidth: "50vw"}}>
<Modal.Dialog style={{ minWidth: "50vw" }}>
<Modal.Header closeButton onHide={close}>
<Modal.Title>
Подробнее
@ -35,14 +36,18 @@ function AnnouncementDetails({ close, announcement: { id, name, category, bestBy
/>
<Marker position={[lat, lng]}>
<Popup>{address + "\n" + metro}</Popup>
<Popup>
{address}
<br />
<LineDot station={metro} /> {metro}
</Popup>
</Marker>
</MapContainer>
</Modal.Body>
<Modal.Footer>
<Button variant='success' onClick={handleBook}>
{bookStatus || "Забронировать"}
{bookStatus || "Забронировать"}
</Button>
</Modal.Footer>
</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 { categoryNames } from "../assets/category"
import { metros } from '../assets/metro'
import { stations, lines } from '../assets/metro'
function Filters({ filter, setFilter, filterShown, setFilterShown }) {
@ -55,11 +55,13 @@ function Filters({ filter, setFilter, filterShown, setFilterShown }) {
<option value="">
Выберите станцию метро
</option>
{Object.entries(metros).map(
([branch, stations]) =>
stations.map(metro =>
<option key={metro} value={metro}>{metro}</option>
)
{Object.entries(stations).map(
([line, stations]) =>
<optgroup key={line} label={lines[line]}>
{Array.from(stations).map(metro =>
<option key={metro} value={metro}>{metro}</option>
)}
</optgroup>
)}
</Form.Select>
</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 { Form, Button, Card } from "react-bootstrap"
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 { latLng } from "leaflet"
import { metros } from "../assets/metro"
import LocationMarker from "../components/LocationMarker"
import TrashboxMarkers from "../components/TrashboxMarkers"
import { useAddAnnouncement, useTrashboxes } from "../hooks/api"
import { stations, lines } from "../assets/metro"
function AddPage() {
const [addressPosition, setAddressPosition] = useState(latLng(59.972, 30.3227))
@ -46,7 +46,7 @@ function AddPage() {
})()
}, [addressPosition])
const {doAdd, status} = useAddAnnouncement()
const { doAdd, status } = useAddAnnouncement()
const handleSubmit = (event) => {
event.preventDefault()
@ -107,6 +107,9 @@ function AddPage() {
position={addressPosition}
setPosition={setAddressPosition}
/>
<ClickHandler
setPosition={setAddressPosition}
/>
</MapContainer>
</div>
<p>Адрес: {address}</p>
@ -134,11 +137,13 @@ function AddPage() {
<option value="">
Укажите ближайщую станцию метро
</option>
{Object.entries(metros).map(
([branch, stations]) =>
stations.map(metro =>
<option key={metro} value={metro}>{metro}</option>
)
{Object.entries(stations).map(
([line, stations]) =>
<optgroup key={line} label={lines[line]}>
{Array.from(stations).map(metro =>
<option key={metro} value={metro}>{metro}</option>
)}
</optgroup>
)}
</Form.Select>
</Form.Group>

View File

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

View File

@ -1,5 +1,6 @@
import { Form, Button, Card, Tabs, Tab } from "react-bootstrap"
import { useNavigate } from "react-router-dom";
import { useAuth } from "../hooks/api";
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 }