Added announcement disposal:

Added ann details button
Added modal shown on its click
Moved trashbox selection there
Added trashboxes mock while testing in restricted area
This commit is contained in:
2023-08-01 18:23:56 +03:00
parent 47fca02858
commit b93ab9794d
15 changed files with 383 additions and 126 deletions

View File

@ -1,13 +1,15 @@
import { Modal, Button } from 'react-bootstrap'
import { MapContainer, Marker, Popup, TileLayer } from 'react-leaflet'
import { CSSProperties } from 'react'
import { CSSProperties, useState } from 'react'
import LineDot from './LineDot'
import { categoryNames } from '../assets/category'
import { useBook, useRemoveAnnouncement } from '../hooks/api'
import { useBook, useDispose, useRemoveAnnouncement } from '../hooks/api'
import { Announcement } from '../api/announcement/types'
import { iconItem } from '../utils/markerIcons'
import { useId } from '../hooks'
import SelectDisposalTrashbox from './SelectDisposalTrashbox'
import { LatLng } from 'leaflet'
type AnnouncementDetailsProps = {
close: () => void,
@ -27,26 +29,85 @@ const styles = {
} as CSSProperties,
}
function AnnouncementDetails({ close, refresh, announcement: {
id, name, category, bestBy, description, lat, lng, address, metro, bookedBy, userId,
} }: AnnouncementDetailsProps) {
const View = ({
announcement: { name, category, bestBy, description, lat, lng, address, metro },
}: { announcement: Announcement }) => (
<>
<h1>{name}</h1>
<span>{categoryNames[category]}</span>
<span className='m-2'>&#x2022;</span>{/* dot */}
<span>Годен до {new Date(bestBy).toLocaleString('ru-RU')}</span>
<p className='mb-3'>{description}</p>
<MapContainer style={styles.map} center={[lat, lng]} zoom={16} >
<TileLayer
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
/>
<Marker icon={iconItem} position={[lat, lng]}>
<Popup>
{address}
<br />
<LineDot station={metro} /> {metro}
</Popup>
</Marker>
</MapContainer>
</>
)
type ControlProps = {
closeRefresh: () => void,
announcement: Announcement,
showDispose: () => void
}
function Control({
closeRefresh,
announcement: { bookedBy, id, userId },
showDispose
}: ControlProps) {
const { handleBook, bookButton } = useBook()
const removeRefresh = () => {
const { handleRemove, removeButton } = useRemoveAnnouncement(closeRefresh)
const myId = useId()
return (
<>
<p>Забронировали {bookedBy} чел.</p>
{(myId === userId) ? (
<>
<Button variant='success' onClick={showDispose}>Утилизировать</Button>
<Button variant='success' onClick={() => void handleRemove(id)} {...removeButton} />
</>
) : (
<Button variant='success' onClick={() => void handleBook(id)} {...bookButton} />
)}
</>
)
}
function AnnouncementDetails({
close,
refresh,
announcement,
}: AnnouncementDetailsProps) {
const closeRefresh = () => {
close()
refresh()
}
const { handleRemove, removeButton } = useRemoveAnnouncement(removeRefresh)
const myId = useId()
const [disposeShow, setDisposeShow] = useState(false)
return (
<div
className='modal'
style={styles.container}
>
<Modal.Dialog style={{ minWidth: '50vw' }}>
<Modal.Dialog centered className='modal-dialog'>
<Modal.Header closeButton onHide={close}>
<Modal.Title>
Подробнее
@ -54,39 +115,26 @@ function AnnouncementDetails({ close, refresh, announcement: {
</Modal.Header>
<Modal.Body>
<h1>{name}</h1>
<span>{categoryNames[category]}</span>
<span className='m-2'>&#x2022;</span>{/* dot */}
<span>Годен до {new Date(bestBy).toLocaleString('ru-RU')}</span>
<p className='mb-3'>{description}</p>
<MapContainer style={styles.map} center={[lat, lng]} zoom={16} >
<TileLayer
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
/>
<Marker icon={iconItem} position={[lat, lng]}>
<Popup>
{address}
<br />
<LineDot station={metro} /> {metro}
</Popup>
</Marker>
</MapContainer>
<View announcement={announcement} />
</Modal.Body>
<Modal.Footer>
<p>Забронировали {bookedBy} чел.</p>
{(myId === userId) ? (
<Button variant='success' onClick={() => void handleRemove(id)} {...removeButton} />
) : (
<Button variant='success' onClick={() => void handleBook(id)} {...bookButton} />
)}
<Control closeRefresh={closeRefresh} showDispose={() => setDisposeShow(true)} announcement={announcement} />
</Modal.Footer>
</Modal.Dialog>
<Modal centered show={disposeShow} onHide={() => setDisposeShow(false)} style={{ zIndex: 100000 }}>
<Modal.Header closeButton>
<Modal.Title>
Утилизация
</Modal.Title>
</Modal.Header>
<SelectDisposalTrashbox
annId={announcement.id}
category={announcement.category}
address={new LatLng(announcement.lat, announcement.lng)}
closeRefresh={closeRefresh}
/>
</Modal>
</div>
)
}

View File

@ -0,0 +1,119 @@
import { Button, Modal } from 'react-bootstrap'
import { MapContainer, TileLayer } from 'react-leaflet'
import { CSSProperties, useState } from 'react'
import { LatLng } from 'leaflet'
import { useDispose, useTrashboxes } from '../hooks/api'
import { UseFetchReturn, gotError, gotResponse } from '../hooks/useFetch'
import TrashboxMarkers from './TrashboxMarkers'
import { Category } from '../assets/category'
import { Trashbox } from '../api/trashbox/types'
type SelectDisposalTrashboxProps = {
annId: number,
category: Category,
address: LatLng,
closeRefresh: () => void,
}
type SelectedTrashbox = {
index: number,
category: string,
}
const styles = {
map: {
width: '100%',
height: 400,
} as CSSProperties,
}
function SelectDisposalTrashbox({ annId, category, address, closeRefresh }: SelectDisposalTrashboxProps) {
const trashboxes = useTrashboxes(address, category)
const [selectedTrashbox, setSelectedTrashbox] = useState<SelectedTrashbox>({ index: -1, category: '' })
const { handleDispose, disposeButton } = useDispose(closeRefresh)
return (
<>
<Modal.Body>
<div className='mb-3'>
{gotResponse(trashboxes)
? (
gotError(trashboxes) ? (
<p
style={styles.map}
className='text-danger'
>{trashboxes.error}</p>
) : (
<MapContainer
scrollWheelZoom={false}
style={styles.map}
center={address}
zoom={13}
className=''
>
<TileLayer
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
/>
<TrashboxMarkers
trashboxes={trashboxes.data}
selectTrashbox={setSelectedTrashbox}
/>
</MapContainer>
)
) : (
<div style={styles.map}>
<p>Загрузка...</p>
</div>
)
}
</div>
<DisplaySelected trashboxes={trashboxes} selectedTrashbox={selectedTrashbox} />
</Modal.Body>
<Modal.Footer>
<Button
{...disposeButton}
disabled={disposeButton.disabled || gotError(trashboxes) || !gotResponse(trashboxes) || selectedTrashbox.index < 0}
variant='success'
onClick={() => {
if (gotResponse(trashboxes) && !gotError(trashboxes)) {
const { Lat, Lng, Name } = trashboxes.data[selectedTrashbox.index]
void handleDispose(annId, {
Category: selectedTrashbox.category,
Lat,
Lng,
Name,
})
}
}}
/>
</Modal.Footer>
</>
)
}
type DisplaySelectedProps = {
trashboxes: UseFetchReturn<Trashbox[]>,
selectedTrashbox: SelectedTrashbox,
}
function DisplaySelected({ trashboxes, selectedTrashbox }: DisplaySelectedProps) {
if (gotResponse(trashboxes) && !gotError(trashboxes) && selectedTrashbox.index > -1) {
return (
<>
<p className='mb-0'>Выбран пункт сбора мусора на {trashboxes.data[selectedTrashbox.index].Address}</p>
<p className='mb-0'>с категорией "{selectedTrashbox.category}"</p>
</>
)
}
return (
<p className='mb-0'>Выберите пункт сбора мусора и категорию</p>
)
}
export default SelectDisposalTrashbox

View File

@ -11,27 +11,32 @@ type TrashboxMarkersProps = {
}) => void,
}
function TrashboxMarkers({ trashboxes, selectTrashbox }: TrashboxMarkersProps) {
return (
<>{trashboxes.map((trashbox, index) => (
<Marker icon={iconTrash} key={`${trashbox.Lat}${trashbox.Lng}`} position={[trashbox.Lat, trashbox.Lng]}>
<Popup>
<p>{trashbox.Address}</p>
<p>Тип мусора: <>
{trashbox.Categories.map((category, j) =>
<span key={trashbox.Address + category}>
<a href='#' onClick={() => selectTrashbox({ index, category })}>
{category}
</a>
{(j < trashbox.Categories.length - 1) ? ', ' : ''}
</span>
)}
</></p>
<p>{trashbox.Lat.toFixed(4)}, {trashbox.Lng.toFixed(4)}</p>
</Popup>
</Marker>
))}</>
)
}
const TrashboxMarkers = ({ trashboxes, selectTrashbox }: TrashboxMarkersProps) => (
<>{trashboxes.map((trashbox, index) => (
<Marker icon={iconTrash} key={`${trashbox.Lat}${trashbox.Lng}`} position={[trashbox.Lat, trashbox.Lng]}>
<Popup>
<p className='fw-bold m-0'>{trashbox.Name}</p>
<p className='m-0'>{trashbox.Address}</p>
<p>Тип мусора:{' '}
{trashbox.Categories.map((category, j) =>
<span key={trashbox.Address + category}>
<a href='#' onClick={(e) => {
e.preventDefault()
e.stopPropagation()
selectTrashbox({ index, category })
}}>
{category}
</a>
{(j < trashbox.Categories.length - 1) ? ', ' : ''}
</span>
)}
</p>
<p className='m-0'>
{trashbox.Lat.toFixed(4)}, {trashbox.Lng.toFixed(4)}
</p>
</Popup>
</Marker>
))}</>
)
export default TrashboxMarkers

View File

@ -13,3 +13,4 @@ export { default as StoriesPreview } from './StoriesPreview'
export { default as Points } from './Points'
export { default as SignOut } from './SignOut'
export { default as Poetry } from './Poetry'
export { default as SelectDisposalTrashbox } from './SelectDisposalTrashbox'