homeman-deno/islands/Routine.tsx

139 lines
3.4 KiB
TypeScript
Raw Permalink Normal View History

2024-01-21 15:52:46 -06:00
import { DailyPhase, DailyPhaseModel, Task, toPhase } from '@homeman/models.ts'
2024-01-21 20:38:24 -06:00
import { signal, useSignal } from '@preact/signals'
import { excitement } from '@homeman/common.ts'
2024-01-21 20:38:24 -06:00
import { IS_BROWSER } from '$fresh/runtime.ts'
import { useEffect } from 'preact/hooks'
export interface Props {
tasks: Task[]
}
interface TaskWithIndex extends Task {
index: number
}
const phaseEmoji: Record<DailyPhase, string> = {
Morning: '🌄',
Midday: '🌞',
Evening: '🌆',
Bedtime: '🛌',
Night: '🌙',
}
type TaskGroups = Record<DailyPhase, TaskWithIndex[]>
2024-01-21 20:38:24 -06:00
let lastPhaseSelection = new Date()
export function Routine(
props: Props,
) {
const tasks = useSignal(props.tasks)
const currentPhase = useSignal(toPhase())
2024-01-21 20:38:24 -06:00
2024-01-21 21:03:00 -06:00
const reload = async () => {
console.log('reloading...')
const newData = await (await fetch(location.href, {
headers: { accept: 'application/json' },
})).json()
tasks.value = newData
console.log('new tasks:', newData)
}
useEffect(() => {
let es = new EventSource('/api/tasks')
console.log('Streaming task events...')
es.addEventListener('message', (e) => {
console.log('task event from server:', e)
reload()
})
es.addEventListener('error', async (e) => {
// try and reconnect
console.log('Streaming todo events error:', e)
es.close()
const backoff = 10000 + Math.random() * 5000
await new Promise((resolve) => setTimeout(resolve, backoff))
es = new EventSource('/api/tasks')
})
}, [])
2024-01-21 20:38:24 -06:00
useEffect(() => {
if (IS_BROWSER) {
setInterval(() => {
const delta = (new Date()).getTime() - lastPhaseSelection.getTime()
const nowPhase = toPhase()
2024-01-21 20:39:24 -06:00
if (nowPhase != currentPhase.value && delta >= 1000 * 60 * 10) {
2024-01-21 20:38:24 -06:00
currentPhase.value = nowPhase
lastPhaseSelection = new Date()
}
2024-01-21 20:39:24 -06:00
}, 5_000)
2024-01-21 20:38:24 -06:00
}
}, [])
const taskGroups: TaskGroups = {
Morning: [],
Midday: [],
Evening: [],
Bedtime: [],
Night: [],
}
tasks.value.forEach((t, i) => taskGroups[t.phase].push({ ...t, index: i }))
return (
<>
<nav class='flex overflow-x-scroll'>
{DailyPhaseModel.options.map((phase) => (
<button
onClick={() => {
currentPhase.value = phase
2024-01-21 20:38:24 -06:00
lastPhaseSelection = new Date()
}}
class={`p-4 shrink-0 grow border-b-2 text-lg ${
currentPhase.value == phase
? 'border-purple-500'
: 'border-gray-500/20 hover:border-gray-500'
}`}
>
{phaseEmoji[phase]} {phase}
</button>
))}
</nav>
<main class='flex flex-col overflow-x-scroll'>
<ul>
{taskGroups[currentPhase.value].map((
2024-01-21 20:10:46 -06:00
task: TaskWithIndex,
) => {
const { emoji, description, doneAt, index } = task
return (
<li
role='button'
class={`text-lg cursor-pointer p-4 hover:bg-gray-500/20 ${
doneAt ? 'line-through opacity-30 hover:bg-gray-500' : ''
}`}
onClick={async () => {
tasks.value[index].doneAt = tasks.value[index].doneAt
? null
: new Date()
tasks.value = [...tasks.value]
if (tasks.value[index].doneAt) {
excitement()
}
await (await fetch('/api/tasks', {
method: 'PUT',
body: JSON.stringify({
...task,
done: !!tasks.value[index].doneAt,
}),
})).json()
}}
>
{emoji ? `${emoji.trim()} ` : ''}
{description.trim()}
</li>
)
})}
</ul>
</main>
</>
)
}