131 lines
3.5 KiB
TypeScript
131 lines
3.5 KiB
TypeScript
import { Todo, UserWithTodos } from '@homeman/models.ts'
|
|
import { JSX } from 'preact'
|
|
import { Button } from '@homeman/components/Button.tsx'
|
|
import { Avatar } from '@homeman/components/Avatar.tsx'
|
|
import {
|
|
CheckBadgeSolid,
|
|
CheckCircleMiniSolid,
|
|
CheckCircleSolid,
|
|
CheckMiniSolid,
|
|
ChevronDownMiniSolid,
|
|
TrashSolid,
|
|
} from 'preact-heroicons'
|
|
|
|
export interface Props {
|
|
user: UserWithTodos
|
|
onNewButtonClicked: JSX.MouseEventHandler<HTMLButtonElement>
|
|
}
|
|
|
|
export function TodoList(
|
|
{ onNewButtonClicked, user: { avatarUrl, name, color, ...user } }: Props,
|
|
) {
|
|
const doneTodos = user.assignedTodos.filter((t) => t.doneAt != null)
|
|
const inProgressTodos = user.assignedTodos.filter((t) => t.doneAt == null)
|
|
const todoItem = (
|
|
{ id, doneAt, className, description, virtual }:
|
|
& Pick<Todo, 'description' | 'id' | 'doneAt'>
|
|
& {
|
|
className?: string
|
|
virtual?: boolean
|
|
},
|
|
) => (
|
|
<li
|
|
style={`border-color: #${color}`}
|
|
title={doneAt != null ? `Completed at ${doneAt}` : ''}
|
|
class={`${className || ''} ${
|
|
virtual ? '' : 'border-l-4'
|
|
} p-4 text-black cursor-auto dark:text-white rounded drop-shadow-lg bg-white dark:bg-stone-900 flex flex-col gap-2`}
|
|
>
|
|
<span class='text-xl'>{description}</span>
|
|
<footer class='flex gap-2'>
|
|
{(virtual || doneAt != null) ? '' : (
|
|
<Button
|
|
className='bg-emerald-500/50 grow'
|
|
onClick={async () => {
|
|
await fetch('/api/todo/done', {
|
|
method: 'PUT',
|
|
body: JSON.stringify({ id }),
|
|
})
|
|
}}
|
|
>
|
|
<CheckMiniSolid class='w-5 h-5' /> Done
|
|
</Button>
|
|
)}
|
|
{(!virtual && doneAt != null)
|
|
? (
|
|
<Button
|
|
className='grow'
|
|
onClick={async () => {
|
|
await fetch('/api/todo/done', {
|
|
method: 'DELETE',
|
|
body: JSON.stringify({ id }),
|
|
})
|
|
}}
|
|
>
|
|
Not Done
|
|
</Button>
|
|
)
|
|
: ''}
|
|
{virtual ? '' : (
|
|
<Button
|
|
className='hover:bg-rose-500'
|
|
onClick={async () => {
|
|
if (confirm(`Are you sure you want to delete '${description}'`)) {
|
|
await fetch('/api/todo', {
|
|
method: 'DELETE',
|
|
headers: { 'content-type': 'application/json' },
|
|
body: JSON.stringify({ id }),
|
|
})
|
|
}
|
|
}}
|
|
>
|
|
<TrashSolid className='w-6 h-6' />
|
|
</Button>
|
|
)}
|
|
</footer>
|
|
</li>
|
|
)
|
|
return (
|
|
<div class='p-2 w-1/4 min-w-[15rem] relative flex flex-col grow-0'>
|
|
<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={onNewButtonClicked}
|
|
>
|
|
+ New
|
|
</button>
|
|
<ul class='flex flex-col gap-y-4'>
|
|
{inProgressTodos.length < 1
|
|
? todoItem({
|
|
id: '',
|
|
doneAt: null,
|
|
description: 'All clear! 🎉',
|
|
className: 'text-center',
|
|
virtual: true,
|
|
})
|
|
: inProgressTodos.map(todoItem)}
|
|
</ul>
|
|
{doneTodos.length > 0
|
|
? (
|
|
<details class='text-gray-500 [&>summary>svg]:open:-rotate-180'>
|
|
<summary class='cursor-pointer marker:content-[""] flex justify-between hover:bg-gray-500/20 p-4 rounded mt-4'>
|
|
<span>+{doneTodos.length} completed todos</span>
|
|
<ChevronDownMiniSolid className='w-6 h-6' />
|
|
</summary>
|
|
<ul class='flex flex-col gap-y-4 mt-2'>
|
|
{doneTodos.map(todoItem)}
|
|
</ul>
|
|
</details>
|
|
)
|
|
: ''}
|
|
</div>
|
|
)
|
|
}
|