homeman-deno/islands/Dashboard.tsx

220 lines
5.1 KiB
TypeScript
Raw Normal View History

2024-01-11 16:23:09 -06:00
import { Todo, User, UserWithTodos } from '@homeman/models.ts'
import { Signal, useSignal } from '@preact/signals'
import { TodoList } from '@homeman/islands/TodoList.tsx'
import { Dialog } from '@homeman/components/Dialog.tsx'
import { Label } from '@homeman/components/Label.tsx'
import { Input } from '@homeman/components/Input.tsx'
import { Avatar } from '@homeman/components/Avatar.tsx'
import { Button } from '@homeman/components/Button.tsx'
import { JSX } from 'preact'
2024-01-17 15:10:28 -06:00
import { useEffect } from 'preact/hooks'
import { excitement } from '@homeman/common.ts'
2024-01-11 16:23:09 -06:00
interface Props {
users: Record<string, UserWithTodos>
2024-01-17 15:10:28 -06:00
todos: Record<string, Todo>
2024-01-11 16:23:09 -06:00
unassignedTodos: Todo[]
2024-01-17 15:10:28 -06:00
lastUserIdUpdated: { value: string; versionstamp: string }
lastTodoIdUpdated: { value: string; versionstamp: string }
2024-01-11 16:23:09 -06:00
}
const unassignedUserPlaceholder: User = {
id: '',
createdAt: new Date(),
name: 'Shared',
avatarUrl: 'http://placekitten.com/512/512',
color: '888888',
}
2024-01-11 18:57:29 -06:00
interface UserSelectButtonProps extends JSX.HTMLAttributes<HTMLInputElement> {
2024-01-11 16:23:09 -06:00
user: User
}
function UserSelectButton(
2024-01-11 18:57:29 -06:00
{ user: { id, name, avatarUrl, color }, tabindex, ...props }:
UserSelectButtonProps,
2024-01-11 16:23:09 -06:00
) {
const eid = `assigneeUserId_${id}`
return (
<>
<input
aria-hidden='true'
type='radio'
2024-01-11 18:57:29 -06:00
class='peer sr-only'
2024-01-11 16:23:09 -06:00
id={eid}
name='assigneeUserId'
value={id}
2024-01-11 18:57:29 -06:00
{...props}
2024-01-11 16:23:09 -06:00
/>
<Label
for={eid}
2024-01-11 18:57:29 -06:00
style={`border-color: #${color};`}
tabindex={tabindex}
className='cursor-pointer peer-checked:border-t-2 peer-checked:bg-gray-500/20 hover:bg-gray-500/25 rounded p-2'
2024-01-11 16:23:09 -06:00
role='button'
>
<Avatar
className='mb-2'
src={avatarUrl}
/>
<span
style={`color: #${color};`}
class='font-semibold text-center'
>
2024-01-11 18:57:29 -06:00
{name}
2024-01-11 16:23:09 -06:00
</span>
</Label>
</>
)
}
2024-01-17 15:10:28 -06:00
export default function Dashboard(
2024-01-21 09:25:17 -06:00
props: Props,
2024-01-17 15:10:28 -06:00
) {
2024-01-11 16:23:09 -06:00
const todoAssignUserId: Signal<string | null> = useSignal(null)
const showAddTodoDialog = useSignal(false)
2024-01-21 09:25:17 -06:00
const data = useSignal(props)
const {
// todos,
users,
unassignedTodos,
// lastTodoIdUpdated,
// lastUserIdUpdated,
} = data.value
const reload = async () => {
console.log('reloading...')
const newData = await (await fetch(location.href, {
headers: { accept: 'application/json' },
})).json()
data.value = newData
console.log('new data:', newData)
}
2024-01-17 15:10:28 -06:00
useEffect(() => {
let es = new EventSource('/api/user')
2024-01-21 09:25:17 -06:00
es.addEventListener('message', async (e) => {
console.log('user event stream message:', e)
await reload()
excitement()
2024-01-17 15:10:28 -06:00
})
2024-01-21 09:25:17 -06:00
es.addEventListener('error', async (e) => {
// try and reconnect
console.log('Streaming user events error:', e)
2024-01-17 15:10:28 -06:00
es.close()
const backoff = 10000 + Math.random() * 5000
await new Promise((resolve) => setTimeout(resolve, backoff))
es = new EventSource('/api/user')
})
}, [])
useEffect(() => {
let es = new EventSource('/api/todo')
console.log('Streaming todo events...')
es.addEventListener('message', (e) => {
console.log('todo event from server:', e)
2024-01-21 09:25:17 -06:00
reload().then(excitement)
2024-01-17 15:10:28 -06:00
})
es.addEventListener('error', async (e) => {
2024-01-21 09:25:17 -06:00
// try and reconnect
2024-01-17 15:10:28 -06:00
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/todo')
})
}, [])
2024-01-11 16:23:09 -06:00
const unassignedUser: UserWithTodos = {
...unassignedUserPlaceholder,
assignedTodos: unassignedTodos,
}
return (
<main class='flex flex-col'>
<Dialog
headerTitle='Add todo'
show={showAddTodoDialog.value}
onClose={() => showAddTodoDialog.value = false}
>
<form
class='p-4 gap-4 flex flex-col'
action='/api/todo'
method='post'
encType='multipart/form-data'
>
<Label for='description'>
Description
<Input autofocus name='description' />
</Label>
<span>
Assignee
<ul class='flex gap-2'>
<li>
2024-01-11 18:57:29 -06:00
<UserSelectButton
checked={todoAssignUserId.value == null}
tabindex={0}
user={unassignedUser}
/>
2024-01-11 16:23:09 -06:00
</li>
{Object.values(users).map(
2024-01-11 18:57:29 -06:00
(user) => {
2024-01-11 16:23:09 -06:00
return (
<li>
2024-01-11 18:57:29 -06:00
<UserSelectButton
checked={todoAssignUserId.value == user.id}
user={user}
/>
2024-01-11 16:23:09 -06:00
</li>
)
},
)}
</ul>
</span>
<footer class='flex justify-end gap-2'>
<Button
type='button'
onClick={() => showAddTodoDialog.value = false}
>
Cancel
</Button>
<Input type='submit' value='Save' />
</footer>
</form>
</Dialog>
<h1 class='p-5 border-b-2 border-stone-500/20 text-2xl'>
Todos
</h1>
<ul class='p-4 relative flex overflow-x-scroll max-w-screen'>
<li>
<TodoList
onNewButtonClicked={() => {
console.log('shared new')
showAddTodoDialog.value = true
todoAssignUserId.value = null
}}
user={unassignedUser}
/>
</li>
{Object.values(users).map((u) => (
<li>
<TodoList
onNewButtonClicked={() => {
showAddTodoDialog.value = true
todoAssignUserId.value = u.id
}}
user={u}
/>
</li>
))}
</ul>
</main>
)
}