Skip to content

Instantly share code, notes, and snippets.

@FallOutChonny
Created December 9, 2019 07:04
Show Gist options
  • Save FallOutChonny/f2b1a5328c9854c0c7329c74cd3f7e6f to your computer and use it in GitHub Desktop.
Save FallOutChonny/f2b1a5328c9854c0c7329c74cd3f7e6f to your computer and use it in GitHub Desktop.
import React, { useState } from 'react'
import { DrawingManager, Rectangle } from '@react-google-maps/api'
import { useHistory } from 'react-router-dom'
import { isNil, length } from 'ramda'
import theme from 'shared/theme'
import { OverlayType } from 'shared/constants/types'
import { isEmpty } from 'shared/utils/validation'
import message from 'shared/utils/message'
import { useStreetLights, StreetLight } from 'shared/graphql/streetLight'
import {
useDistrictClusters,
Marker,
DistrictInfo,
useAllMarkers,
} from 'shared/graphql/dashboard'
import { Alarm, AlarmModes } from 'shared/graphql/alarm'
import useModalVisible from 'shared/hooks/useModalVisible'
import useSubscribeAlarm from 'shared/hooks/useSubscribeAlarm'
import GoogleMap from 'shared/components/GoogleMap'
import MapMarker from 'shared/components/MapMarker'
import Box from 'shared/components/Box'
import { useAlarmDrawer } from 'src/hooks/useAlarmDrawer'
import useGoogleMap from 'src/hooks/useGoogleMap'
import { STREETLIGHT_MANAGEMENT } from 'src/constants/routes'
import SearchDrawer from './SearchDrawer'
import AlarmDrawer from './AlarmDrawer'
import IconSearch from './IconSearch'
import DrawingControl from './DrawingControl'
import LightDrawer from './LightDrawer'
import DistrictMarker from './DistrictMarker'
// const randomMarkers = Array.from({ length: 500 }).map((_, idx) => ({
// id: idx,
// deviceId: idx,
// ...getRandomMarkers(24.96251557085913, 121.2618106456357, 1000),
// isLight: true,
// }))
// console.log(randomMarkers)
function Dashboard() {
const {
alarMode,
isClusterMode,
setIsClusterMode,
handleToggleAlarmDrawer,
handleCloseAlarmDrawer,
handleOpenAlarmDrawer,
} = useAlarmDrawer()
const history = useHistory()
const { clusterOfDists, loading: loadingCluster } = useDistrictClusters()
const { dataSource, loading, refetch } = useAllMarkers()
// const [queryMarker, { querying }] = useQueryMarker()
const [toggleMarker, setToggleMarker] = useState<Marker | Alarm | null>(null)
const [drawingMode, setDrawingMode] = useState<OverlayType>()
const [center, setMapCenter] = useState<google.maps.LatLngLiteral | null>(
null,
)
const [rectangle, setRectangle] = useState<google.maps.Rectangle | null>()
const [markersInRect, setMarkersInRect] = React.useState<
Array<Marker | StreetLight>
>([])
// const { dataSource, loading, refetch } = useStreetLights({
// pageSize: 100,
// })
const [drawerVisible, handleDrawerVisible] = useModalVisible()
const { handleMapLoad, map } = useGoogleMap({
onMapLoad: (map: google.maps.Map) => {
// map.addListener('', () => {})
},
})
const handleZoomChange = () => {
console.log(map?.getZoom())
}
const handleMapClick = (evt: google.maps.MouseEvent) => {
console.log(evt.latLng.lat())
console.log(evt.latLng.lng())
}
const handleToLightManagement = (id: number) => {
history.push(`${STREETLIGHT_MANAGEMENT}?id=${id}`)
}
const handleRectangleComplete = (rectangle: google.maps.Rectangle) => {
const markersInRect = dataSource.content
.filter(x => !isNil(x.lat) && !isNil(x.lon))
.filter(x => rectangle.getBounds().contains({ lat: x.lat, lng: x.lon }))
const count = length(markersInRect)
const isTooMany = count > 100
const isZero = count === 0
if (isTooMany) {
message({ content: '框選路燈數需低於100盞' })
}
if (!isTooMany && !isZero) {
setRectangle(rectangle)
setDrawingMode(undefined)
setMarkersInRect(markersInRect)
}
rectangle.setMap(null)
}
const handleRectModeClick = () => {
if (!drawingMode) {
setRectangle(null)
setIsClusterMode(false)
setDrawingMode(OverlayType.RECTANGLE)
handleCloseAlarmDrawer()
} else {
setDrawingMode(undefined)
if (alarMode !== AlarmModes.NORMAL) {
handleOpenAlarmDrawer()
}
}
}
const handleDistMarkerClick = (data: DistrictInfo) => {
refetch({
variables: {
params: {
sessionIds: data.id,
},
},
})
handleToggleAlarmDrawer(data.type)
setMapCenter({ lat: data.lat, lng: data.lng })
if (map) {
map.setZoom(15)
}
}
const handleAlarmClick = (item: Alarm) => {
if (!item.lat || !item.lon) {
message({ content: '此警報內容的路燈尚未設定地理座標!' })
return
}
setMapCenter({ lat: item.lat, lng: item.lon })
setToggleMarker(item)
if (isClusterMode) {
setIsClusterMode(false)
}
if (map) {
map.setZoom(15)
}
}
const handleSearch = (values: any) => {
refetch(values)
handleDrawerVisible(false)
}
useSubscribeAlarm((alarm: Alarm) => {
const handleClick = (evt: React.MouseEvent<HTMLElement>) => {
hide()
handleAlarmClick(alarm)
}
const hide = message({
closable: true,
content: (
<Box
display="flex"
justifyContent="space-between"
flex="1"
className="mr-30">
<Box className="text-darkGrey">
<span>{alarm.remark}</span>
<span className="ml-12">{alarm.address}</span>
<span className="ml-20 text-sm">{alarm.createTimeStr}</span>
</Box>
<Box
className="text-info900 text-underline cursor--pointer"
onClick={handleClick}>
查看詳情
</Box>
</Box>
),
type: 'error',
duration: 15,
})
})
return (
<>
<GoogleMap
zoom={12}
{...(center ? { center } : {})}
loading={loading || loadingCluster}
onMapLoad={handleMapLoad}
onZoomChanged={handleZoomChange}
onClick={handleMapClick}>
<Box left={0} top={0} position="absolute">
<IconSearch
onClick={handleDrawerVisible}
{...(drawerVisible ? { style: { left: 276 } } : {})}
/>
<DrawingControl
onClick={handleRectModeClick}
drawingMode={!!drawingMode}
/>
</Box>
<DrawingManager
drawingMode={drawingMode}
options={drawingManagerOptions}
onRectangleComplete={handleRectangleComplete}
/>
{rectangle && (
<LightDrawer
visible
// loading={querying}
dataSource={markersInRect as StreetLight[]}
/>
)}
{rectangle && (
<Rectangle
bounds={rectangle.getBounds()}
options={rectangleOptions}
/>
)}
{isClusterMode &&
clusterOfDists.map((x: DistrictInfo) => (
<DistrictMarker
key={x.id}
position={{ lat: x.lat, lng: x.lng }}
data={{ ...x, type: alarMode }}
onClick={handleDistMarkerClick}
/>
))}
{!isClusterMode &&
dataSource.content
.filter(x => !isEmpty(x.lat) && !isEmpty(x.lon))
.map(item => (
<MapMarker
isLight
key={item.deviceId}
item={item}
position={{ lat: item.lat, lng: item.lon }}
toggle={toggleMarker?.deviceId === item.deviceId}
alarMode={alarMode}
onToLightManagement={handleToLightManagement}
visible={!isClusterMode}
map={map as NonNullable<google.maps.Map>}
/>
))}
<SearchDrawer
onClose={handleDrawerVisible}
visible={drawerVisible}
onSearch={handleSearch}
/>
<AlarmDrawer onClick={handleAlarmClick} />
</GoogleMap>
</>
)
}
const rectangleOptions = {
fillColor: theme.info200,
strokeColor: theme.info,
strokeWeight: 1,
}
const drawingManagerOptions = {
drawingControl: false,
rectangleOptions,
}
export default React.memo(Dashboard)
import React from 'react'
import { merge, isNil, pathOr } from 'ramda'
import { Spin } from 'antd'
import cx from 'classnames'
import styled from 'styled-components'
import {
Marker as MarkerComponent,
InfoBox,
MarkerProps,
} from '@react-google-maps/api'
import { imageUrlPrefix } from '../env'
import { StreetLight } from '../graphql/streetLight'
import { AlarmModes } from '../graphql/alarm'
import useModalVisible from '../hooks/useModalVisible'
import usePrevious from '../hooks/usePrevious'
// import useGoogleMap from '../hooks/useGoogleMap'
import { getDeviceType } from '../constants/types'
import Slider from './Slider'
import Button from './Button'
import Icon from './Icon'
import Box from './Box'
import { useQueryMarker } from 'shared/graphql/dashboard'
type MapMarkerProps<T> = MarkerProps & {
item?: T | any
toggle?: boolean
alarMode?: AlarmModes
isLight?: boolean
isBuilding?: boolean
map?: google.maps.Map
onInfoBoxClick?: () => any
onToLightManagement?: (id: number) => any
}
// NOTE: Debug時設成false
const options = { optimized: true }
let openedInfoBox: InfoBox[] = []
export default function MapMarker<T = StreetLight>({
toggle = false,
item,
position,
alarMode,
map,
onInfoBoxClick,
onToLightManagement,
isLight,
isBuilding,
...props
}: MapMarkerProps<T>) {
const [infoBoxVisible, handleInfoBoxVisible, _handleClose] = useModalVisible()
const prev = usePrevious(toggle)
let ref = React.useRef<InfoBox | null>(null)
React.useEffect(() => {
if (!!prev !== !!toggle) {
const _toggle = !!toggle
if (_toggle) {
handleInfoBoxVisible()
}
if (!_toggle) {
_handleClose()
}
}
}, [toggle, prev])
const [queryMarker, { loading, data }] = useQueryMarker()
const { icon } = getDeviceType(item)
const handleToggle = () => {
// 先關掉當前打開中的 InfoBox
openedInfoBox.forEach((x: any) => x.close())
if (!infoBoxVisible) {
console.log(item)
queryMarker({ variables: { params: { deviceIds: item.deviceId } } })
}
handleInfoBoxVisible()
}
const handleClose = (evt?: React.MouseEvent<HTMLElement>) => {
handleInfoBoxVisible()
}
const handleConfirm = () => {
handleClose()
if (onInfoBoxClick) {
onInfoBoxClick()
}
}
const handleInfoBoxLoad = (instance: any) => {
openedInfoBox.push(instance)
}
const handleDomReady = () => {
if (ref.current) {
ref.current.containerElement?.addEventListener('mouseup', evt => {
map?.setOptions({ gestureHandling: 'auto' })
})
ref.current.containerElement?.addEventListener('mousedown', evt => {
map?.setOptions({ gestureHandling: 'none' })
})
}
}
const handleToLightManagement = (evt: React.MouseEvent<HTMLElement>) => {
if (onToLightManagement) {
onToLightManagement(item.id)
}
}
let pixelOffset = React.useMemo(() => new google.maps.Size(20, -200), [item])
let className = React.useMemo(
() =>
cx({
'pb-8': item.isSwitchBox,
}),
[item],
)
let infoboxStyle = merge(
{},
{
...(item.isBuilding
? {
width: 324,
background: item.isBuilding ? '#fffcf1' : '#fff',
padding: '8px 16px 56px 24px',
}
: {}),
},
)
const status = React.useMemo(() => {
const isAlarm = alarMode === AlarmModes.ALARM
const isAbnormal = alarMode === AlarmModes.ABNORMAL
const isNormal = isNil(alarMode) || alarMode === AlarmModes.NORMAL
return {
isAlarm,
isAbnormal,
isNormal,
message: isAlarm ? '警報狀態' : isAbnormal ? '維修中' : '正常 (光度 xx%)',
}
}, [alarMode])
return (
<>
<MarkerComponent
position={position}
icon={`${imageUrlPrefix}/icon-${icon}.svg`}
options={options}
onClick={handleToggle}
{...props}>
{infoBoxVisible && (
<InfoBox
ref={ref as any}
onLoad={handleInfoBoxLoad}
position={position}
onDomReady={handleDomReady}
options={{
pixelOffset,
visible: true,
alignBottom: false,
disableAutoPan: false,
enableEventPropagation: true,
}}>
<Spin spinning={loading}>
<InfoBoxWrapper style={infoboxStyle} className={className}>
<CloseButton type="close" onClick={handleClose} />
{isLight && (
<>
<Box display="flex">
<Block className="mr-4 w144 text-white bg-secondary">
{/* 路燈編號 {item.lightId} */}
路燈編號 {pathOr('', ['deviceName'], data)}
</Block>
<Block
className={cx('w144 text-white', {
'bg-crimson': status.isAlarm,
'bg-grey': status.isAbnormal,
'bg-warning': status.isNormal,
})}>
{status.message}
</Block>
</Box>
<Box display="flex">
<Block className="w292 pt-14 pb-13">
<Box
display="flex"
alignItems="center"
justifyContent="cneter">
<Box
display="inline-block"
className="text-info text-500">
調光
</Box>
<Box display="inline-block">
<Slider
min={0}
max={130}
style={{ width: 130 }}
className="is--small"
defaultValue={pathOr(0, ['brightness'], data)}
/>
</Box>
<Box className="text-info text-500">100 W/m</Box>
</Box>
</Block>
</Box>
<Box display="flex">
<Block className="mr-4 w144 bg--light">
{/* 燈具編號: {item.lightId} */}
燈具編號 {pathOr('', ['lightNo'], data)}
</Block>
<Block className="w144 bg--light">
{/* 燈桿編號 {item.lightId} */}
燈具編號 {pathOr('', ['lightNo'], data)}
</Block>
</Box>
<Box display="flex">
<Block className="w292 bg--light">
地址: {pathOr('', ['address'], data)}
</Block>
</Box>
<Box display="flex">
<Block className="w292 bg--warning">
位置: {pathOr('', ['lat'], data)},
{pathOr('', ['lon'], data)}
</Block>
</Box>
<Box display="flex">
<Block className="mr-4 w144 bg--light">
燈具種類: {pathOr('', ['lampType'], data)}
</Block>
<Block className="w144 bg--light">
燈泡瓦特數: {pathOr('', ['lampWatt'], data)}
</Block>
</Box>
<Box display="flex">
<Block className="mr-4 w144 bg--warning">
方位: {item.direction}
</Block>
<Block className="w144 bg--warning">
耗電瓦特數: {item.watt}
</Block>
</Box>
<Box display="flex">
<Block className="mr-4 w144 bg--light">
電壓: {pathOr('', ['voltageOut'], data)}
</Block>
<Block className="w144 bg--light">
電流: {pathOr('', ['currentOut'], data)}
</Block>
</Box>
<Box display="flex">
<Block className="w292 bg--warning">
控制器編號: {pathOr('', ['controllerId'], data)}
</Block>
</Box>
<Box display="flex">
<Block className="mr-4 w144 bg--light">
燈桿類型:{pathOr('', ['poleType'], data)}
</Block>
<Block className="w144 bg--light">
開關箱: {pathOr('', ['switchBoxId'], data)}
</Block>
</Box>
<Box display="flex">
<Block className="w292 bg--warning">
累計點燈時間: {pathOr('', ['lampOnByMonth'], data)}
</Block>
</Box>
<Box
className="text-sm text-primary cursor--pointer mt-12 ml-6"
onClick={handleToLightManagement}>
看更多 >>
</Box>
</>
)}
{isBuilding && (
<div>
<div>
<img
src={`${imageUrlPrefix}/icon-building.svg`}
alt="building-icon"
/>
<div className="mb-8 ml-8 text-larger d-inline-block">
{item.address}
</div>
</div>
<div className="mb-26 ml-26 text-sm">
{(position as google.maps.LatLng).lat()} /{' '}
{(position as google.maps.LatLng).lng()}
</div>
<Button
color="primary"
className="pull-right"
style={{ minWidth: 80 }}
onClick={handleConfirm}>
確認
</Button>
</div>
)}
</InfoBoxWrapper>
</Spin>
</InfoBox>
)}
</MarkerComponent>
</>
)
}
const InfoBoxWrapper = styled.div`
width: 310px;
padding: 0 8px 40px 8px;
background: #fff;
border-radius: 4px;
border: 1px solid ${p => p.theme.primary};
font-size: 12px;
font-weight: 500;
color: ${p => p.theme.darkGrey};
&.is--large {
width: 480px;
}
.bg--warning {
background: ${p => p.theme.blue300};
}
.bg--light {
background: ${p => p.theme.blue200};
}
`
const Block = styled.div`
padding: 5px 8px;
`
const CloseButton = styled(Icon)`
display: flex;
justify-content: flex-end;
margin-top: 9px;
margin-bottom: 9px;
font-size: 18px;
color: ${p => p.theme.info};
cursor: pointer;
`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment