Redesigned header, added loaders
This commit is contained in:
parent
52469eda4d
commit
39641194aa
|
|
@ -2,7 +2,13 @@ import { QueryClient, QueryClientProvider } from 'react-query'
|
||||||
import { AppContextProvider } from './contexts/AppContext'
|
import { AppContextProvider } from './contexts/AppContext'
|
||||||
import { Router } from './pages/Router'
|
import { Router } from './pages/Router'
|
||||||
|
|
||||||
const queryClient = new QueryClient()
|
const queryClient = new QueryClient({
|
||||||
|
defaultOptions: {
|
||||||
|
queries: {
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
export const Root = () => {
|
export const Root = () => {
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,17 @@ input, select {
|
||||||
padding: 0.75rem 1rem;
|
padding: 0.75rem 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.settings-modal .actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-modal .actions .remove {
|
||||||
|
margin-right: auto;
|
||||||
|
background-color: transparent;
|
||||||
|
color: #ff0000;
|
||||||
|
}
|
||||||
|
|
||||||
form {
|
form {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
@ -152,12 +163,19 @@ form.horizontal .input label {
|
||||||
|
|
||||||
.dashboard-head .shadow {
|
.dashboard-head .shadow {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background: linear-gradient(0deg, rgba(255,255,255,0) 5%, rgba(190,190,190,1) 100%);
|
background: linear-gradient(0deg, rgba(255,255,255,0) 5%, rgba(190,190,190,0.6) 100%);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 12px;
|
height: 8px;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dashboard-head .spacer {
|
||||||
|
margin: 0 1rem;
|
||||||
|
width: 1px;
|
||||||
|
background: #ddd;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.checkbox-label {
|
.checkbox-label {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
@ -167,10 +185,18 @@ form.horizontal .input label {
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-sensors {
|
.grid-sensors-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 0.25rem;
|
padding: 0.25rem;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
overflow: auto;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-sensors {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-sensors .grid-box {
|
.grid-sensors .grid-box {
|
||||||
|
|
@ -188,6 +214,33 @@ form.horizontal .input label {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-sensors .grid-box .box .body {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-sensors .grid-box .box .box-loader {
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(128, 128, 128, 0.3);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 300%;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-sensors .grid-box .box .box-loader .svg-icon {
|
||||||
|
animation-name: rotate;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
animation-duration: 0.75s;
|
||||||
|
animation-timing-function: linear;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-sensors .grid-box .box .resize-h {
|
.grid-sensors .grid-box .box .resize-h {
|
||||||
|
|
@ -294,3 +347,8 @@ form.horizontal .input label {
|
||||||
height: 1em;
|
height: 1em;
|
||||||
stroke: currentColor;
|
stroke: currentColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes rotate {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(-360deg); }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
export { ReactComponent as SettingsIcon } from '@/assets/icons/settings.svg'
|
||||||
|
export { ReactComponent as RefreshIcon } from '@/assets/icons/refresh.svg'
|
||||||
|
|
@ -6,7 +6,7 @@ import {
|
||||||
import { createDashboardContent } from '@/utils/createDashboardContent'
|
import { createDashboardContent } from '@/utils/createDashboardContent'
|
||||||
import { parseDashboard } from '@/utils/parseDashboard'
|
import { parseDashboard } from '@/utils/parseDashboard'
|
||||||
import { useEffect, useMemo, useState } from 'preact/hooks'
|
import { useEffect, useMemo, useState } from 'preact/hooks'
|
||||||
import { useQuery } from 'react-query'
|
import { useQuery, useQueryClient } from 'react-query'
|
||||||
import { DashboardGrid } from './components/DashboardGrid'
|
import { DashboardGrid } from './components/DashboardGrid'
|
||||||
import { DashboardHeader } from './components/DashboardHeader'
|
import { DashboardHeader } from './components/DashboardHeader'
|
||||||
import { GRID_H_SNAP, GRID_WIDTH } from './constants'
|
import { GRID_H_SNAP, GRID_WIDTH } from './constants'
|
||||||
|
|
@ -14,6 +14,7 @@ import { DashboardContextProvider } from './contexts/DashboardContext'
|
||||||
import { BoxDefinition } from './types'
|
import { BoxDefinition } from './types'
|
||||||
|
|
||||||
export const NewDashboardPage = () => {
|
export const NewDashboardPage = () => {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
const dashboards = useQuery(['/dashboards'], getDashboards)
|
const dashboards = useQuery(['/dashboards'], getDashboards)
|
||||||
const dashboard = dashboards.data?.find((i) => i.id === 'default')
|
const dashboard = dashboards.data?.find((i) => i.id === 'default')
|
||||||
|
|
||||||
|
|
@ -41,7 +42,8 @@ export const NewDashboardPage = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleRefresh = () => {
|
const handleRefresh = () => {
|
||||||
console.log('Nothing to refresh right now')
|
queryClient.invalidateQueries(['/sensor/values'])
|
||||||
|
queryClient.invalidateQueries(['/sensor/values/latest'])
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleNewBox = () => {
|
const handleNewBox = () => {
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,19 @@
|
||||||
import { getLatestSensorValue } from '@/api/sensorValues'
|
import { getLatestSensorValue } from '@/api/sensorValues'
|
||||||
import { DashboardDialData } from '@/utils/parseDashboard'
|
import { DashboardDialData } from '@/utils/parseDashboard'
|
||||||
|
import { RefObject } from 'preact'
|
||||||
import { useMemo } from 'preact/hooks'
|
import { useMemo } from 'preact/hooks'
|
||||||
import { useQuery } from 'react-query'
|
import { useQuery } from 'react-query'
|
||||||
import { useDashboardContext } from '../contexts/DashboardContext'
|
import { useDashboardContext } from '../contexts/DashboardContext'
|
||||||
import { BoxDefinition } from '../types'
|
import { BoxDefinition } from '../types'
|
||||||
|
import { BoxLoader } from './BoxLoader'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
box: BoxDefinition
|
box: BoxDefinition
|
||||||
data: DashboardDialData
|
data: DashboardDialData
|
||||||
|
refreshRef: RefObject<() => void>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BoxDialContent = ({ box, data }: Props) => {
|
export const BoxDialContent = ({ box, data, refreshRef }: Props) => {
|
||||||
const { filter } = useDashboardContext()
|
const { filter } = useDashboardContext()
|
||||||
|
|
||||||
const valuesQuery = {
|
const valuesQuery = {
|
||||||
|
|
@ -22,6 +25,10 @@ export const BoxDialContent = ({ box, data }: Props) => {
|
||||||
getLatestSensorValue(valuesQuery)
|
getLatestSensorValue(valuesQuery)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
refreshRef.current = () => {
|
||||||
|
value.refetch()
|
||||||
|
}
|
||||||
|
|
||||||
const displayValue = useMemo(() => {
|
const displayValue = useMemo(() => {
|
||||||
if (!value.data) {
|
if (!value.data) {
|
||||||
return ''
|
return ''
|
||||||
|
|
@ -40,13 +47,16 @@ export const BoxDialContent = ({ box, data }: Props) => {
|
||||||
}, [value.data, data])
|
}, [value.data, data])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="dial">
|
<>
|
||||||
{value.data && (
|
{value.isFetching && <BoxLoader />}
|
||||||
<>
|
<div className="dial">
|
||||||
{displayValue}
|
{value.data && (
|
||||||
{` ${data.unit}`}
|
<>
|
||||||
</>
|
{displayValue}
|
||||||
)}
|
{` ${data.unit}`}
|
||||||
</div>
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,19 @@
|
||||||
import { getSensorValues } from '@/api/sensorValues'
|
import { getSensorValues } from '@/api/sensorValues'
|
||||||
import { DashboardGraphData } from '@/utils/parseDashboard'
|
import { DashboardGraphData } from '@/utils/parseDashboard'
|
||||||
|
import { RefObject } from 'preact'
|
||||||
import { useEffect, useRef } from 'preact/hooks'
|
import { useEffect, useRef } from 'preact/hooks'
|
||||||
import { useQuery } from 'react-query'
|
import { useQuery } from 'react-query'
|
||||||
import { useDashboardContext } from '../contexts/DashboardContext'
|
import { useDashboardContext } from '../contexts/DashboardContext'
|
||||||
import { BoxDefinition } from '../types'
|
import { BoxDefinition } from '../types'
|
||||||
|
import { BoxLoader } from './BoxLoader'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
box: BoxDefinition
|
box: BoxDefinition
|
||||||
data: DashboardGraphData
|
data: DashboardGraphData
|
||||||
|
refreshRef: RefObject<() => void>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BoxGraphContent = ({ box, data }: Props) => {
|
export const BoxGraphContent = ({ box, data, refreshRef }: Props) => {
|
||||||
const { filter } = useDashboardContext()
|
const { filter } = useDashboardContext()
|
||||||
|
|
||||||
const bodyRef = useRef<HTMLDivElement>(null)
|
const bodyRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
@ -25,6 +28,10 @@ export const BoxGraphContent = ({ box, data }: Props) => {
|
||||||
getSensorValues(valuesQuery)
|
getSensorValues(valuesQuery)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
refreshRef.current = () => {
|
||||||
|
values.refetch()
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// TODO: These should be probably returned by server, could be outdated
|
// TODO: These should be probably returned by server, could be outdated
|
||||||
const from = filter.customFrom
|
const from = filter.customFrom
|
||||||
|
|
@ -80,5 +87,10 @@ export const BoxGraphContent = ({ box, data }: Props) => {
|
||||||
}
|
}
|
||||||
}, [values.data, box, data])
|
}, [values.data, box, data])
|
||||||
|
|
||||||
return <div ref={bodyRef} />
|
return (
|
||||||
|
<>
|
||||||
|
{values.isFetching && <BoxLoader />}
|
||||||
|
<div ref={bodyRef} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { RefreshIcon } from '@/icons'
|
||||||
|
|
||||||
|
export const BoxLoader = () => {
|
||||||
|
return (
|
||||||
|
<div className="box-loader">
|
||||||
|
<RefreshIcon />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -8,11 +8,12 @@ import { GraphSettings } from './components/GraphSettings'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
value: BoxDefinition
|
value: BoxDefinition
|
||||||
|
onRemove: () => void
|
||||||
onSave: (newValue: BoxDefinition) => void
|
onSave: (newValue: BoxDefinition) => void
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BoxSettings = ({ value, onSave, onClose }: Props) => {
|
export const BoxSettings = ({ value, onSave, onClose, onRemove }: Props) => {
|
||||||
const sensors = useQuery(['/sensors'], getSensors)
|
const sensors = useQuery(['/sensors'], getSensors)
|
||||||
|
|
||||||
const [formState, setFormState] = useState(() => ({
|
const [formState, setFormState] = useState(() => ({
|
||||||
|
|
@ -74,41 +75,50 @@ export const BoxSettings = ({ value, onSave, onClose }: Props) => {
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div className="input">
|
|
||||||
<label>Title</label>
|
|
||||||
<input
|
|
||||||
name="title"
|
|
||||||
value={formState.title}
|
|
||||||
onChange={handleChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="input">
|
|
||||||
<label>Type</label>
|
|
||||||
<select
|
|
||||||
name="type"
|
|
||||||
value={formState.type}
|
|
||||||
onChange={handleChange}
|
|
||||||
>
|
|
||||||
<option value="graph">Graph</option>
|
|
||||||
<option value="dial">Dial</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{formState.type === 'graph' && (
|
{formState.sensor && (
|
||||||
<GraphSettings
|
<>
|
||||||
value={data as DashboardGraphData}
|
<div className="input">
|
||||||
onChange={setData}
|
<label>Title</label>
|
||||||
/>
|
<input
|
||||||
)}
|
name="title"
|
||||||
|
value={formState.title}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="input">
|
||||||
|
<label>Type</label>
|
||||||
|
<select
|
||||||
|
name="type"
|
||||||
|
value={formState.type}
|
||||||
|
onChange={handleChange}
|
||||||
|
>
|
||||||
|
<option value="graph">Graph</option>
|
||||||
|
<option value="dial">Dial</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
{formState.type === 'dial' && (
|
{formState.type === 'graph' && (
|
||||||
<DialSettings
|
<GraphSettings
|
||||||
value={data as DashboardDialData}
|
value={data as DashboardGraphData}
|
||||||
onChange={setData}
|
onChange={setData}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{formState.type === 'dial' && (
|
||||||
|
<DialSettings
|
||||||
|
value={data as DashboardDialData}
|
||||||
|
onChange={setData}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="actions">
|
<div className="actions">
|
||||||
|
<button className="remove" type="button" onClick={onRemove}>
|
||||||
|
Remove
|
||||||
|
</button>
|
||||||
|
|
||||||
<button className="cancel" onClick={onClose} type="button">
|
<button className="cancel" onClick={onClose} type="button">
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -9,37 +9,44 @@ type Props = {
|
||||||
|
|
||||||
export const DashboardGrid = ({ boxes, onChange }: Props) => {
|
export const DashboardGrid = ({ boxes, onChange }: Props) => {
|
||||||
return (
|
return (
|
||||||
<div className="grid-sensors">
|
<div className="grid-sensors-container">
|
||||||
{boxes.map((b) => (
|
<div className="grid-sensors">
|
||||||
<EditableBox
|
{boxes.map((b) => (
|
||||||
box={b}
|
<EditableBox
|
||||||
key={b.id}
|
box={b}
|
||||||
boxes={boxes}
|
key={b.id}
|
||||||
onPosition={(p) => {
|
boxes={boxes}
|
||||||
onChange((previous) =>
|
onPosition={(p) => {
|
||||||
normalizeBoxes(
|
onChange((previous) =>
|
||||||
previous.map((pb) =>
|
normalizeBoxes(
|
||||||
pb.id === b.id ? { ...pb, x: p.x, y: p.y } : pb
|
previous.map((pb) =>
|
||||||
|
pb.id === b.id ? { ...pb, x: p.x, y: p.y } : pb
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
}}
|
||||||
}}
|
onResize={(s) => {
|
||||||
onResize={(s) => {
|
onChange((previous) =>
|
||||||
onChange((previous) =>
|
normalizeBoxes(
|
||||||
normalizeBoxes(
|
previous.map((pb) =>
|
||||||
previous.map((pb) =>
|
pb.id === b.id ? { ...pb, w: s.w, h: s.h } : pb
|
||||||
pb.id === b.id ? { ...pb, w: s.w, h: s.h } : pb
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
}}
|
||||||
}}
|
onEdit={(newB) => {
|
||||||
onEdit={(newB) => {
|
onChange((previous) =>
|
||||||
onChange((previous) =>
|
previous.map((b) => (b.id === newB.id ? newB : b))
|
||||||
previous.map((b) => (b.id === newB.id ? newB : b))
|
)
|
||||||
)
|
}}
|
||||||
}}
|
onRemove={() => {
|
||||||
/>
|
onChange((previous) =>
|
||||||
))}
|
normalizeBoxes(previous.filter((pb) => pb.id !== b.id))
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { RefreshIcon } from '@/icons'
|
||||||
import { Filters } from './Filters'
|
import { Filters } from './Filters'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
@ -10,8 +11,12 @@ export const DashboardHeader = ({ onNewBox, onRefresh }: Props) => {
|
||||||
<div className="dashboard-head">
|
<div className="dashboard-head">
|
||||||
<div className="inner">
|
<div className="inner">
|
||||||
<button onClick={onNewBox}>Add box</button>
|
<button onClick={onNewBox}>Add box</button>
|
||||||
|
<div className="spacer" />
|
||||||
<Filters />
|
<Filters />
|
||||||
<button onClick={onRefresh}>Refresh all</button>
|
<div className="spacer" />
|
||||||
|
<button onClick={onRefresh}>
|
||||||
|
<RefreshIcon /> Refresh all
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="shadow"></div>
|
<div className="shadow"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useWindowEvent } from '@/utils/hooks/useWindowEvent'
|
import { useWindowEvent } from '@/utils/hooks/useWindowEvent'
|
||||||
import { useState } from 'preact/hooks'
|
import { useRef, useState } from 'preact/hooks'
|
||||||
import { GRID_WIDTH } from '../constants'
|
import { GRID_WIDTH } from '../constants'
|
||||||
import { useDashboardContext } from '../contexts/DashboardContext'
|
import { useDashboardContext } from '../contexts/DashboardContext'
|
||||||
import { useDragging } from '../hooks/useDragging'
|
import { useDragging } from '../hooks/useDragging'
|
||||||
|
|
@ -8,9 +8,8 @@ import { BoxDefinition } from '../types'
|
||||||
import { BoxDialContent } from './BoxDialContent'
|
import { BoxDialContent } from './BoxDialContent'
|
||||||
import { BoxGraphContent } from './BoxGraphContent'
|
import { BoxGraphContent } from './BoxGraphContent'
|
||||||
import { BoxSettings } from './BoxSettings/BoxSettings'
|
import { BoxSettings } from './BoxSettings/BoxSettings'
|
||||||
import { ReactComponent as SettingsIcon } from '@/assets/icons/settings.svg'
|
|
||||||
import { ReactComponent as RefreshIcon } from '@/assets/icons/refresh.svg'
|
|
||||||
import { useElementOffsets } from '@/utils/hooks/useElementOffsets'
|
import { useElementOffsets } from '@/utils/hooks/useElementOffsets'
|
||||||
|
import { RefreshIcon, SettingsIcon } from '@/icons'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
box: BoxDefinition
|
box: BoxDefinition
|
||||||
|
|
@ -18,6 +17,7 @@ type Props = {
|
||||||
onPosition: (p: { x: number; y: number }) => void
|
onPosition: (p: { x: number; y: number }) => void
|
||||||
onResize: (p: { w: number; h: number }) => void
|
onResize: (p: { w: number; h: number }) => void
|
||||||
onEdit: (box: BoxDefinition) => void
|
onEdit: (box: BoxDefinition) => void
|
||||||
|
onRemove: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EditableBox = ({
|
export const EditableBox = ({
|
||||||
|
|
@ -26,8 +26,11 @@ export const EditableBox = ({
|
||||||
onPosition,
|
onPosition,
|
||||||
onResize,
|
onResize,
|
||||||
onEdit,
|
onEdit,
|
||||||
|
onRemove,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const [boxRef, setBoxRef] = useState<HTMLDivElement>()
|
const [boxRef, setBoxRef] = useState<HTMLDivElement>()
|
||||||
|
const refreshRef = useRef<() => void>(null)
|
||||||
|
|
||||||
const { verticalMode } = useDashboardContext()
|
const { verticalMode } = useDashboardContext()
|
||||||
|
|
||||||
const [editing, setEditing] = useState(false)
|
const [editing, setEditing] = useState(false)
|
||||||
|
|
@ -124,7 +127,7 @@ export const EditableBox = ({
|
||||||
<div className="name">{box.title ?? box.sensor ?? ''}</div>
|
<div className="name">{box.title ?? box.sensor ?? ''}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="actions">
|
<div className="actions">
|
||||||
<div className="action">
|
<div className="action" onClick={() => refreshRef.current?.()}>
|
||||||
<RefreshIcon />
|
<RefreshIcon />
|
||||||
</div>
|
</div>
|
||||||
<div className="action" onClick={() => setEditing(true)}>
|
<div className="action" onClick={() => setEditing(true)}>
|
||||||
|
|
@ -134,10 +137,18 @@ export const EditableBox = ({
|
||||||
</div>
|
</div>
|
||||||
<div className="body">
|
<div className="body">
|
||||||
{box.sensor && box.data?.type === 'graph' && (
|
{box.sensor && box.data?.type === 'graph' && (
|
||||||
<BoxGraphContent box={box} data={box.data} />
|
<BoxGraphContent
|
||||||
|
box={box}
|
||||||
|
data={box.data}
|
||||||
|
refreshRef={refreshRef}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
{box.sensor && box.data?.type === 'dial' && (
|
{box.sensor && box.data?.type === 'dial' && (
|
||||||
<BoxDialContent box={box} data={box.data} />
|
<BoxDialContent
|
||||||
|
box={box}
|
||||||
|
data={box.data}
|
||||||
|
refreshRef={refreshRef}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -185,6 +196,7 @@ export const EditableBox = ({
|
||||||
value={box}
|
value={box}
|
||||||
onSave={onEdit}
|
onSave={onEdit}
|
||||||
onClose={() => setEditing(false)}
|
onClose={() => setEditing(false)}
|
||||||
|
onRemove={onRemove}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { DateTimeInput } from '@/components/DateTimeInput'
|
import { DateTimeInput } from '@/components/DateTimeInput'
|
||||||
import { intervalToRange } from '@/utils/intervalToRange'
|
import { intervalToRange } from '@/utils/intervalToRange'
|
||||||
import { useState } from 'preact/hooks'
|
import { useEffect, useState } from 'preact/hooks'
|
||||||
import { useDashboardContext } from '../contexts/DashboardContext'
|
import { useDashboardContext } from '../contexts/DashboardContext'
|
||||||
|
|
||||||
export type FilterInterval =
|
export type FilterInterval =
|
||||||
|
|
@ -44,6 +44,10 @@ export const Filters = () => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setFilter(value)
|
||||||
|
}, [value])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="filter-form">
|
<div className="filter-form">
|
||||||
<form className="horizontal" onSubmit={handleSubmit}>
|
<form className="horizontal" onSubmit={handleSubmit}>
|
||||||
|
|
@ -85,8 +89,6 @@ export const Filters = () => {
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<button>Apply</button>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ export function normalizeBoxes(boxes: BoxDefinition[]) {
|
||||||
// TODO: This is not optimized at all
|
// TODO: This is not optimized at all
|
||||||
while (!sorted) {
|
while (!sorted) {
|
||||||
// Sort boxes to have the lowest ones first
|
// Sort boxes to have the lowest ones first
|
||||||
boxes.sort((a, b) => a.y - b.y)
|
boxes.sort((a, b) => (a.y === b.y ? a.x - b.x : a.y - b.y))
|
||||||
|
|
||||||
sorted = true
|
sorted = true
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue