Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add noise sensor activity list #633

Merged
merged 59 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
2267b7e
Add `TabSet`
xplato May 15, 2024
48f7ce1
ci: Format code
seambot May 15, 2024
eafb327
ci: Format code
seambot May 15, 2024
0c3d879
Recalculate on window resize
xplato May 15, 2024
874f692
Merge branch 'add-noise-sensor-events' of github.com:seamapi/react in…
xplato May 15, 2024
d12da0d
ci: Format code
seambot May 15, 2024
33d9105
Format
xplato May 15, 2024
00383c6
Merge branch 'add-noise-sensor-events' of github.com:seamapi/react in…
xplato May 15, 2024
acfce3f
ci: Format code
seambot May 15, 2024
e080073
Move ContentHeader, add on-fly adjustment
xplato May 16, 2024
9c533fe
Merge branch 'add-noise-sensor-events' of github.com:seamapi/react in…
xplato May 16, 2024
c028e68
Add noise activity list (empty)
xplato May 16, 2024
9995bd7
ci: Format code
seambot May 16, 2024
f2e0a34
Add useEvents
xplato May 16, 2024
e24cb2a
Merge branch 'add-noise-sensor-events' of github.com:seamapi/react in…
xplato May 16, 2024
4393fa0
ci: Format code
seambot May 16, 2024
e938a59
Begin style improvements
xplato May 16, 2024
a9a7bf0
Merge branch 'add-noise-sensor-events' of github.com:seamapi/react in…
xplato May 16, 2024
cb0d044
Fix merge issues
xplato May 16, 2024
530ceb0
ci: Format code
seambot May 16, 2024
273dfc7
More style fixes
xplato May 16, 2024
56d737b
Merge branch 'add-noise-sensor-events' of github.com:seamapi/react in…
xplato May 16, 2024
509e710
ci: Format code
seambot May 16, 2024
378ef0b
Update item style
xplato May 16, 2024
a67efcd
Merge branch 'add-noise-sensor-events' of github.com:seamapi/react in…
xplato May 16, 2024
527234c
Annotate return type on `TabSet`
xplato May 16, 2024
fc28744
Remove console.log
xplato May 16, 2024
44246ed
Update effect deps with `calculateHighlightStyle`
xplato May 16, 2024
32645d0
Lint fixes
xplato May 16, 2024
092cd27
ci: Format code
seambot May 16, 2024
beac826
Update types and row layout style
xplato May 17, 2024
3b4269b
Merge branch 'add-noise-sensor-events' of github.com:seamapi/react in…
xplato May 17, 2024
df72852
ci: Format code
seambot May 17, 2024
e4f855b
Update layout style
xplato May 17, 2024
860f96d
Merge branch 'add-noise-sensor-events' of github.com:seamapi/react in…
xplato May 17, 2024
129fa67
Lint fixes
xplato May 17, 2024
70a26f5
Merge branch 'main' into add-noise-sensor-events
xplato May 17, 2024
d6dcffc
Remove string template
xplato May 20, 2024
e2adad0
Remove string typeof checks
xplato May 20, 2024
559000f
Use `globalThis`
xplato May 20, 2024
4fc8500
Use `globalThis` (again)
xplato May 20, 2024
1f5dc86
Use `tabTitles`
xplato May 20, 2024
181ded4
Remove dates global func
xplato May 20, 2024
b9f7dfe
ci: Format code
seambot May 20, 2024
5f45c4d
`useNow`
xplato May 20, 2024
83b2a36
Merge branch 'add-noise-sensor-events' of github.com:seamapi/react in…
xplato May 20, 2024
867eebb
ci: Format code
seambot May 20, 2024
bf0bee1
Remove `noise_detection.detected_noise`
xplato May 20, 2024
68b7f6c
Merge branch 'add-noise-sensor-events' of github.com:seamapi/react in…
xplato May 20, 2024
6ded29a
Replace with Luxon constants
xplato May 20, 2024
1ef458c
Add expect err comment
xplato May 20, 2024
87d1b1f
Add return type
xplato May 21, 2024
6842ad9
Format fixes
xplato May 21, 2024
2c0ac2f
Change var names
xplato May 21, 2024
adb3674
Remove TS comment
xplato May 21, 2024
11710bc
Add refetchInterval
xplato May 21, 2024
cffa03a
Update comment
xplato May 21, 2024
f25dde1
Add issue link
xplato May 21, 2024
68896c3
ci: Format code
seambot May 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .storybook/seed-fake.js
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,26 @@ export const seedFake = (db) => {
name: 'Active Hours',
})

db.addEvent({
device_id: device7.device_id,
workspace_id: ws2.workspace_id,
created_at: '2024-05-16T00:16:12.000',
event_type: 'noise_sensor.noise_threshold_triggered',
noise_level_decibels: 75,
noise_threshold_id: 2,
noise_threshold_name: 'Active Hours',
})

db.addEvent({
device_id: device7.device_id,
workspace_id: ws2.workspace_id,
created_at: '2024-05-16T00:16:12.000',
event_type: 'noise_sensor.noise_threshold_triggered',
noise_level_decibels: 75,
noise_threshold_id: 2,
noise_threshold_name: 'Active Hours',
})

// add climate setting schedules
db.addClimateSettingSchedule({
device_id: device5.device_id,
Expand Down
8 changes: 8 additions & 0 deletions assets/icons/clock.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
36 changes: 36 additions & 0 deletions src/lib/icons/Clock.tsx

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const meta: Meta<typeof DeviceDetails> = {
type: 'figma',
url: 'https://www.figma.com/file/Su3VO6yupz4yxe88fv0Uqa/Seam-Components?type=design&node-id=358-39439&mode=design&t=4OQwfRB8Mw8kT1rw-4',
},
layout: 'fullscreen',
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Storybook adds a default padding to previews, this removes it.

I wonder how many other renders aren't perfectly accurate due to this...

},
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import classNames from 'classnames'
import { useState } from 'react'
import type { NoiseSensorDevice } from 'seamapi'

import type { NestedSpecificDeviceDetailsProps } from 'lib/seam/components/DeviceDetails/DeviceDetails.js'
Expand All @@ -8,7 +9,11 @@ import { DeviceImage } from 'lib/ui/device/DeviceImage.js'
import { NoiseLevelStatus } from 'lib/ui/device/NoiseLevelStatus.js'
import { OnlineStatus } from 'lib/ui/device/OnlineStatus.js'
import { ContentHeader } from 'lib/ui/layout/ContentHeader.js'
import { NoiseSensorActivityList } from 'lib/ui/noise-sensor/NoiseSensorActivityList.js'
import { NoiseThresholdsList } from 'lib/ui/noise-sensor/NoiseThresholdsList.js'
import { TabSet } from 'lib/ui/TabSet.js'

type TabType = 'details' | 'activity'

interface NoiseSensorDeviceDetailsProps
extends NestedSpecificDeviceDetailsProps {
Expand All @@ -22,38 +27,63 @@ export function NoiseSensorDeviceDetails({
onBack,
className,
}: NoiseSensorDeviceDetailsProps): JSX.Element | null {
const [tab, setTab] = useState<TabType>('details')

return (
<div className={classNames('seam-device-details', className)}>
<ContentHeader title={t.noiseSensor} onBack={onBack} />

<div className='seam-body'>
<div className='seam-summary'>
<div className='seam-content'>
<div className='seam-image'>
<DeviceImage device={device} />
</div>
<div className='seam-info'>
<span className='seam-label'>{t.noiseSensor}</span>
<h4 className='seam-device-name'>{device.properties.name}</h4>
<div className='seam-properties'>
<span className='seam-label'>{t.status}:</span>{' '}
<OnlineStatus device={device} />
<NoiseLevelStatus device={device} />
<DeviceModel device={device} />
<div className='seam-body seam-body-no-margin'>
<div className='seam-contained-summary'>
<ContentHeader
title={t.noiseSensor}
onBack={onBack}
className='seam-content-header-contained'
/>
<div className='seam-summary'>
<div className='seam-content'>
<div className='seam-image'>
<DeviceImage device={device} />
</div>
<div className='seam-info'>
<span className='seam-label'>{t.noiseSensor}</span>
<h4 className='seam-device-name'>{device.properties.name}</h4>
<div className='seam-properties'>
<span className='seam-label'>{t.status}:</span>{' '}
<OnlineStatus device={device} />
<NoiseLevelStatus device={device} />
<DeviceModel device={device} />
</div>
</div>
</div>
</div>

<TabSet<TabType>
tabs={['details', 'activity']}
tabTitles={{
details: t.details,
activity: t.activity,
}}
activeTab={tab}
onTabChange={(tab) => {
setTab(tab)
}}
/>
</div>

<NoiseThresholdsList device={device} />
{tab === 'details' && (
<div className='seam-padded-container'>
<NoiseThresholdsList device={device} />

<DeviceInfo
device={device}
disableConnectedAccountInformation={
disableConnectedAccountInformation
}
disableResourceIds={disableResourceIds}
/>
</div>
)}

<DeviceInfo
device={device}
disableConnectedAccountInformation={
disableConnectedAccountInformation
}
disableResourceIds={disableResourceIds}
/>
{tab === 'activity' && <NoiseSensorActivityList device={device} />}
</div>
</div>
)
Expand All @@ -63,4 +93,6 @@ const t = {
noiseSensor: 'Noise Sensor',
status: 'Status',
noiseLevel: 'Noise level',
details: 'Details',
activity: 'Activity',
}
44 changes: 44 additions & 0 deletions src/lib/seam/events/use-events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { useQuery, useQueryClient } from '@tanstack/react-query'
import type {
Event,
EventsListRequest,
EventsListResponse,
SeamError,
} from 'seamapi'

import { useSeamClient } from 'lib/seam/use-seam-client.js'
import type { UseSeamQueryResult } from 'lib/seam/use-seam-query-result.js'

export type UseEventsParams = EventsListRequest
export type UseEventsData = Event[]
export interface UseEventsOptions {
refetchInterval?: number
}

export function useEvents(
params?: UseEventsParams,
options?: UseEventsOptions
): UseSeamQueryResult<'events', UseEventsData> {
const { client } = useSeamClient()
const queryClient = useQueryClient()

const { data, ...rest } = useQuery<EventsListResponse['events'], SeamError>({
enabled: client != null,
queryKey: ['events', 'list', params],
queryFn: async () => {
if (client == null) return []
return await client.events.list(params)
},
onSuccess: (events) => {
for (const event of events) {
queryClient.setQueryData(
['events', 'get', { event_id: event.event_id }],
event
)
}
},
refetchInterval: options?.refetchInterval ?? 30_000,
})

return { ...rest, events: data }
}
110 changes: 110 additions & 0 deletions src/lib/ui/TabSet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import classNames from 'classnames'
import {
type MouseEventHandler,
useCallback,
useEffect,
useLayoutEffect,
useState,
} from 'react'

interface TabSetProps<TabType extends string> {
tabs: TabType[]
tabTitles: Record<TabType, string>
activeTab: TabType
onTabChange: (tab: TabType) => void
}

interface HighlightStyle {
left: number
width: number
}

export function TabSet<TabType extends string>({
tabs,
tabTitles,
activeTab,
onTabChange,
}: TabSetProps<TabType>): JSX.Element {
const [highlightStyle, setHighlightStyle] = useState<HighlightStyle>({
left: 0,
width: 140,
})

const calculateHighlightStyle = useCallback(() => {
const tabButton: HTMLButtonElement | null =
globalThis.document?.querySelector(
`.seam-tab-button:nth-of-type(${tabs.indexOf(activeTab) + 1})`
)

setHighlightStyle({
left: tabButton?.offsetLeft ?? 0,
width: tabButton?.offsetWidth ?? 140,
})
}, [activeTab, tabs])

useLayoutEffect(() => {
calculateHighlightStyle()
}, [activeTab, calculateHighlightStyle])

useEffect(() => {
globalThis.addEventListener?.('resize', calculateHighlightStyle)
return () => {
globalThis.removeEventListener?.('resize', calculateHighlightStyle)
}
}, [calculateHighlightStyle])

return (
<div className='seam-tab-set'>
<div className='seam-tab-set-buttons'>
<div className='seam-tab-set-highlight' style={highlightStyle} />

{tabs.map((tab) => (
<TabButton<TabType>
key={tab}
tab={tab}
title={tabTitles[tab]}
isActive={activeTab === tab}
onTabChange={onTabChange}
setHighlightStyle={setHighlightStyle}
/>
))}
</div>
</div>
)
}

interface TabButtonProps<TabType> {
tab: TabType
title: string
isActive: boolean
onTabChange: (tab: TabType) => void
setHighlightStyle: (style: HighlightStyle) => void
}

function TabButton<TabType extends string>({
tab,
title,
isActive,
onTabChange,
setHighlightStyle,
}: TabButtonProps<TabType>): JSX.Element {
const handleClick: MouseEventHandler<HTMLButtonElement> = (ev) => {
onTabChange(tab)
setHighlightStyle({
left: ev.currentTarget.offsetLeft,
width: ev.currentTarget.offsetWidth,
})
}

return (
<button
className={classNames(
'seam-tab-button',
isActive && 'seam-tab-button-active'
)}
onClick={handleClick}
>
<p className='seam-tab-button-label'>{title}</p>
</button>
)
}
7 changes: 5 additions & 2 deletions src/lib/ui/layout/ContentHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import classNames from 'classnames'

import { ArrowBackIcon } from 'lib/icons/ArrowBack.js'

interface ContentHeaderProps {
onBack: (() => void) | undefined
title?: string
subheading?: string
className?: string
}

export function ContentHeader(props: ContentHeaderProps): JSX.Element | null {
const { title, onBack, subheading } = props
const { title, onBack, subheading, className } = props
if (title == null && onBack == null) {
return null
}

return (
<div className='seam-content-header'>
<div className={classNames('seam-content-header', className)}>
<BackIcon onClick={onBack} />
<div>
<span className='seam-title'>{title}</span>
Expand Down
Loading
Loading