140 lines
3.6 KiB
TypeScript
140 lines
3.6 KiB
TypeScript
|
import { Todo, User, UserWithTodos } from '@homeman/models.ts'
|
||
|
import { Button } from '@homeman/components/Button.tsx'
|
||
|
import { Dialog } from '@homeman/components/Dialog.tsx'
|
||
|
import { Avatar } from '@homeman/components/Avatar.tsx'
|
||
|
import { Signal, useSignal } from '@preact/signals'
|
||
|
import { Input } from '@homeman/components/Input.tsx'
|
||
|
import { Label } from '@homeman/components/Label.tsx'
|
||
|
|
||
|
export interface Props {
|
||
|
user: UserWithTodos
|
||
|
users: Record<string, User>
|
||
|
}
|
||
|
|
||
|
export function TodoList(
|
||
|
{ users, user: { id, avatarUrl, assignedTodos, name, color } }: Props,
|
||
|
) {
|
||
|
const todoAssignUserId: Signal<string | null> = useSignal(null)
|
||
|
const showAddTodoDialog = useSignal(false)
|
||
|
|
||
|
const todoItem = (
|
||
|
{ className, description, hideDone }: Pick<Todo, 'description'> & {
|
||
|
className?: string
|
||
|
hideDone?: boolean
|
||
|
},
|
||
|
) => (
|
||
|
<li
|
||
|
style={`border-color: #${color}`}
|
||
|
class={`${className || ''} ${
|
||
|
hideDone ? '' : 'border-l-4'
|
||
|
} p-4 rounded drop-shadow-lg bg-white dark:bg-stone-900 flex flex-col`}
|
||
|
>
|
||
|
<span class='text-xl'>{description}</span>
|
||
|
{hideDone ? '' : <Button class='mt-2'>Done</Button>}
|
||
|
</li>
|
||
|
)
|
||
|
return (
|
||
|
<div class='p-2 w-1/4 min-w-[15rem] relative flex flex-col grow-0'>
|
||
|
<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>
|
||
|
<input
|
||
|
type='radio'
|
||
|
id='assigneeUserId_unassigned'
|
||
|
name='assigneeUserId'
|
||
|
value=''
|
||
|
/>
|
||
|
<Label for='assigneeUserId_unassigned'>
|
||
|
<Avatar
|
||
|
className='mb-2'
|
||
|
src='http://placekitten.com/512/512'
|
||
|
/>
|
||
|
<span
|
||
|
style={`color: #888888;`}
|
||
|
class='font-semibold text-center'
|
||
|
>
|
||
|
Shared
|
||
|
</span>
|
||
|
</Label>
|
||
|
</li>
|
||
|
{Object.entries(users).map(([id, { avatarUrl, color }]) => {
|
||
|
const eid = `assigneeUserId_${id}`
|
||
|
return (
|
||
|
<li>
|
||
|
<input
|
||
|
type='radio'
|
||
|
id={eid}
|
||
|
name='assigneeUserId'
|
||
|
value={id}
|
||
|
/>
|
||
|
<Label for={eid}>
|
||
|
<Avatar className='mb-2' src={avatarUrl} />
|
||
|
<span
|
||
|
style={`color: #${color};`}
|
||
|
class='font-semibold text-center'
|
||
|
>
|
||
|
{name}
|
||
|
</span>
|
||
|
</Label>
|
||
|
</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>
|
||
|
<Avatar
|
||
|
className='mb-2'
|
||
|
src={avatarUrl != null ? avatarUrl : 'https://placehold.co/512x512'}
|
||
|
title={`${name}'s avatar`}
|
||
|
/>
|
||
|
<span style={`color: #${color};`} class='font-semibold text-center'>
|
||
|
{name}
|
||
|
</span>
|
||
|
<button
|
||
|
class='mt-4 mb-4 text-left w-full px-2 py-1 rounded-lg border-[1px] text-stone-500 border-stone-500 opacity-50 hover:opacity-100'
|
||
|
onClick={() => {
|
||
|
showAddTodoDialog.value = true
|
||
|
todoAssignUserId.value = id
|
||
|
}}
|
||
|
>
|
||
|
+ New
|
||
|
</button>
|
||
|
<ul class='flex flex-col gap-y-4'>
|
||
|
{assignedTodos.length < 1
|
||
|
? todoItem({
|
||
|
description: 'All clear! 🎉',
|
||
|
className: 'text-center',
|
||
|
hideDone: true,
|
||
|
})
|
||
|
: assignedTodos.map(todoItem)}
|
||
|
</ul>
|
||
|
</div>
|
||
|
)
|
||
|
}
|