diff --git a/client/src/assets/components/_input.scss b/client/src/assets/components/_input.scss index 56ea08a..024799a 100644 --- a/client/src/assets/components/_input.scss +++ b/client/src/assets/components/_input.scss @@ -15,6 +15,11 @@ input { outline: none; } + &:disabled { + background-color: var(--input-disabled-bg-color); + color: var(--input-disabled-fg-color); + } + &.small { padding: 0.1rem 0.25rem; } @@ -28,3 +33,28 @@ input { .checkbox-label input[type="checkbox"] { margin-top: 6px; } + +.input.buttons { + .button-picker { + > button { + margin: 0.1rem; + } + } +} + +.input.date-time { + > div { + display: flex; + align-items: center; + } + + input[type="date"] { + margin-right: 0.25rem; + flex: 1; + } + + input[type="time"] { + flex-grow: 0; + flex-shrink: 0; + } +} diff --git a/client/src/assets/icons/chevron-down.svg b/client/src/assets/icons/chevron-down.svg new file mode 100644 index 0000000..926a96f --- /dev/null +++ b/client/src/assets/icons/chevron-down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/assets/pages/_dashboard-page.scss b/client/src/assets/pages/_dashboard-page.scss index 209ae81..231bab2 100644 --- a/client/src/assets/pages/_dashboard-page.scss +++ b/client/src/assets/pages/_dashboard-page.scss @@ -23,6 +23,10 @@ display: flex; align-items: center; + > select { + min-width: 10rem; + } + button { margin-left: 0.25rem; font-size: 125%; @@ -51,7 +55,7 @@ top: 0; bottom: 0; z-index: 3; - width: 15rem; + width: 16rem; display: flex; max-width: 100%; overflow: auto; @@ -60,6 +64,17 @@ background-color: var(--box-bg-color); padding: 0.25rem 0.5rem; flex: 1; + + .dashboard-switch { + padding-bottom: 1rem; + margin: 1rem 0rem; + border-bottom: 1px solid var(--box-border-color); + + > select { + flex: 1; + min-width: 0; + } + } } .shadow { @@ -74,3 +89,32 @@ font-size: 150%; } } + +.filter-toggle { + .current-value { + display: flex; + align-items: center; + cursor: pointer; + + > .svg-icon { + margin-left: 0.5rem; + } + } + + .filter-popup { + display: none; + position: absolute; + z-index: 3; + top: 35px; + right: 0; + width: 20rem; + + > .box { + padding: 1rem; + } + + &.show { + display: block; + } + } +} diff --git a/client/src/assets/themes/_basic.scss b/client/src/assets/themes/_basic.scss index 38b1741..f5c5744 100644 --- a/client/src/assets/themes/_basic.scss +++ b/client/src/assets/themes/_basic.scss @@ -36,6 +36,8 @@ --input-focus-border-color: #3988ff; --input-bg-color: #fff; --input-fg-color: #000; + --input-disabled-fg-color: #000; + --input-disabled-bg-color: #fff; } @media (prefers-color-scheme: dark) { @@ -62,6 +64,8 @@ --input-focus-border-color: #666; --input-bg-color: #222; --input-fg-color: #ccc; + --input-disabled-fg-color: #bbb; + --input-disabled-bg-color: #2a2a2a; } .sensor-item .auth .auth-value .label { diff --git a/client/src/components/DateTimeInput.tsx b/client/src/components/DateTimeInput.tsx index aa29148..ff730b1 100644 --- a/client/src/components/DateTimeInput.tsx +++ b/client/src/components/DateTimeInput.tsx @@ -1,11 +1,12 @@ import { splitDateTime } from '@/utils/splitDateTime' +import { HTMLAttributes } from 'preact/compat' type Props = { value: Date onChange: (value: Date) => void -} +} & Omit, 'value' | 'onChange'> -export const DateTimeInput = ({ value, onChange }: Props) => { +export const DateTimeInput = ({ value, onChange, ...inputProps }: Props) => { const splitValue = splitDateTime(value) const handleDateChange = (e: Event) => { @@ -30,8 +31,18 @@ export const DateTimeInput = ({ value, onChange }: Props) => { return ( <> - - + + ) } diff --git a/client/src/icons.ts b/client/src/icons.ts index 967879f..1627d10 100644 --- a/client/src/icons.ts +++ b/client/src/icons.ts @@ -9,3 +9,4 @@ export { ReactComponent as EyeOffIcon } from '@/assets/icons/eye-off.svg' export { ReactComponent as EditIcon } from '@/assets/icons/edit.svg' export { ReactComponent as ClipboardCopyIcon } from '@/assets/icons/clipboard-copy.svg' export { ReactComponent as ClipboardCheckIcon } from '@/assets/icons/clipboard-check.svg' +export { ReactComponent as ChevronDown } from '@/assets/icons/chevron-down.svg' diff --git a/client/src/pages/dashboard/components/DashboardHeader/DashboardHeader.tsx b/client/src/pages/dashboard/components/DashboardHeader/DashboardHeader.tsx index 9de3456..3f52e54 100644 --- a/client/src/pages/dashboard/components/DashboardHeader/DashboardHeader.tsx +++ b/client/src/pages/dashboard/components/DashboardHeader/DashboardHeader.tsx @@ -5,6 +5,7 @@ import { useState } from 'preact/hooks' import { GRID_H_SNAP, GRID_WIDTH } from '../../constants' import { useDashboardContext } from '../../contexts/DashboardContext' import { DashboardFilters } from './components/DashboardFilters' +import { DashboardHeaderFilterToggle } from './components/DashboardHeaderFilterToggle' import { DashboardSwitch } from './components/DashboardSwitch' export const DashboardHeader = () => { @@ -86,7 +87,7 @@ export const DashboardHeader = () => {
- +
+ ))} +
- {isCustomSelected && ( - <> -
- -
- setValue({ ...value, customFrom: v })} - /> -
-
-
- -
- setValue({ ...value, customTo: v })} - /> -
-
- - )} +
+ +
+ + setValue({ ...value, customFrom: v, interval: 'custom' }) + } + /> +
+
+
+ +
+ + setValue({ ...value, customTo: v, interval: 'custom' }) + } + /> +
+
+ + ) diff --git a/client/src/pages/dashboard/components/DashboardHeader/components/DashboardHeaderFilterToggle.tsx b/client/src/pages/dashboard/components/DashboardHeader/components/DashboardHeaderFilterToggle.tsx new file mode 100644 index 0000000..cab2531 --- /dev/null +++ b/client/src/pages/dashboard/components/DashboardHeader/components/DashboardHeaderFilterToggle.tsx @@ -0,0 +1,37 @@ +import { ChevronDown } from '@/icons' +import { useDashboardContext } from '@/pages/dashboard/contexts/DashboardContext' +import { useFilterIntervals } from '@/pages/dashboard/hooks/useFilterIntervals' +import { cn } from '@/utils/cn' +import { useState } from 'preact/hooks' +import { DashboardFilters } from './DashboardFilters' + +const formatDate = (d: Date) => + Intl.DateTimeFormat([], { dateStyle: 'short', timeStyle: 'short' }).format(d) + +export const DashboardHeaderFilterToggle = () => { + const { filter } = useDashboardContext() + const intervals = useFilterIntervals() + + const [show, setShow] = useState(false) + + const selected = + intervals.find((i) => i.value === filter.interval)?.label ?? + `${formatDate(filter.customFrom)} - ${formatDate(filter.customTo)}` + + return ( +
+
setShow((v) => !v)}> + {selected} + +
+
+
+ setShow(false)} /> +
+
+ {show && ( +
setShow((v) => !v)}>
+ )} +
+ ) +} diff --git a/client/src/pages/dashboard/hooks/useFilterIntervals.ts b/client/src/pages/dashboard/hooks/useFilterIntervals.ts new file mode 100644 index 0000000..84551d6 --- /dev/null +++ b/client/src/pages/dashboard/hooks/useFilterIntervals.ts @@ -0,0 +1,13 @@ +import { useMemo } from 'preact/hooks' + +export const useFilterIntervals = () => + useMemo( + () => [ + { value: 'hour', label: 'Last hour' }, + { value: 'day', label: 'Last 24 hours' }, + { value: 'week', label: 'Last 7 days' }, + { value: 'month', label: 'Last 30 days' }, + { value: 'year', label: 'Last 365 days' }, + ], + [] + ) diff --git a/client/tsconfig.json b/client/tsconfig.json index 3508d4c..91be2f9 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -63,7 +63,7 @@ /* Experimental Options */ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ - "lib": [ "DOM", "es2019" ] + "lib": [ "DOM", "es2019", "ES2020.Intl" ] }, "include": [ "src/**/*"