homeman-deno/islands/Admin.tsx

171 lines
4.4 KiB
TypeScript
Raw Normal View History

2024-01-09 21:52:47 -06:00
import { JSX } from 'preact'
2024-01-07 16:21:14 -06:00
import { type Signal, useSignal } from '@preact/signals'
2024-01-07 02:24:36 -06:00
import { Todo, User } from '@homeman/models.ts'
import { Button } from '@homeman/components/Button.tsx'
2024-01-07 10:55:18 -06:00
import { Input } from '@homeman/components/Input.tsx'
import { Label } from '@homeman/components/Label.tsx'
2024-01-09 21:52:47 -06:00
import { Dialog } from '@homeman/components/Dialog.tsx'
import { PencilSquareOutline, TrashOutline } from 'preact-heroicons'
2024-01-07 02:24:36 -06:00
export interface Props {
2024-01-09 21:52:47 -06:00
users: Record<string, User>
todos: Record<string, Todo>
2024-01-07 02:24:36 -06:00
}
2024-01-07 11:38:25 -06:00
async function promptDeleteUser(id: string, name: string) {
if (confirm(`Are you sure you want to delete ${name} (${id})?`)) {
await fetch(`/api/user?id=${id}`, { method: 'DELETE' })
location.reload()
}
}
2024-01-09 21:52:47 -06:00
interface UserFormProps extends JSX.HTMLAttributes<HTMLFormElement> {
onCancelButtonClicked: JSX.MouseEventHandler<HTMLButtonElement>
userData: User | null
}
function UserForm(
{ onCancelButtonClicked, userData, ...props }: UserFormProps,
) {
return (
<form
{...props}
class='p-4 gap-4 flex flex-col'
action='/api/user'
method='post'
encType='multipart/form-data'
>
{userData ? <Input type='hidden' name='id' value={userData.id} /> : <></>}
<Label for='name'>
Name
<Input autofocus name='name' value={userData?.name} />
</Label>
<Label for='avatar'>
Avatar
<Input type='file' name='avatar' />
</Label>
<Label for='color'>
Color
<Input type='color' name='color' value={`#${userData?.color}`} />
</Label>
<footer class='flex justify-end gap-2'>
<Button type='button' onClick={onCancelButtonClicked}>
Cancel
</Button>
<Input type='submit' value='Save' />
</footer>
</form>
)
}
2024-01-07 02:24:36 -06:00
export function Admin({ users, todos }: Props) {
2024-01-09 21:52:47 -06:00
const showAddUserDialog = useSignal(false)
2024-01-07 16:21:14 -06:00
const editUser: Signal<User | null> = useSignal(null)
2024-01-09 21:52:47 -06:00
2024-01-07 02:24:36 -06:00
return (
<main class='flex flex-col'>
2024-01-09 21:52:47 -06:00
<Dialog
headerTitle='Add user'
2024-01-07 10:55:18 -06:00
class='rounded drop-shadow-lg backdrop:bg-stone-500/90'
2024-01-09 21:52:47 -06:00
show={showAddUserDialog.value}
onClose={() => showAddUserDialog.value = false}
2024-01-07 10:55:18 -06:00
>
2024-01-09 21:52:47 -06:00
<UserForm
onCancelButtonClicked={() => showAddUserDialog.value = false}
userData={null}
/>
</Dialog>
<Dialog
headerTitle={`Edit '${editUser.value?.name}'`}
2024-01-07 16:21:14 -06:00
class='rounded drop-shadow-lg backdrop:bg-stone-500/90'
2024-01-09 21:52:47 -06:00
show={!!editUser.value}
onClose={() => editUser.value = null}
2024-01-07 16:21:14 -06:00
>
2024-01-09 21:52:47 -06:00
<UserForm
userData={editUser.value}
onCancelButtonClicked={() => editUser.value = null}
/>
</Dialog>
2024-01-07 02:24:36 -06:00
<header class='flex items-center border-b-2 border-stone-500/20 '>
<h1 class='p-5 text-2xl'>
Users ({users.length})
</h1>
2024-01-09 21:52:47 -06:00
<Button onClick={() => showAddUserDialog.value = true}>
2024-01-07 02:24:36 -06:00
Add User
</Button>
</header>
<table class='border-separate [border-spacing:1.25rem] text-left'>
<thead>
<tr>
<th>Name</th>
<th>Avatar</th>
<th>Color</th>
2024-01-07 11:38:25 -06:00
<th>Actions</th>
2024-01-07 02:24:36 -06:00
</tr>
</thead>
<tbody>
2024-01-09 21:52:47 -06:00
{Object.entries(users).map(([id, user]) => {
const { name, avatarUrl, color } = user
2024-01-07 16:21:14 -06:00
return (
<tr>
<td>{name}</td>
<td>
{avatarUrl == null
? 'None'
: <img class='h-16 w-16 object-cover' src={avatarUrl} />}
</td>
<td style={`color: #${color}`}>
#{color}
</td>
<td class=''>
<Button
title='Delete'
className='py-2 mr-2'
onClick={() => promptDeleteUser(id, name)}
2024-01-07 11:38:25 -06:00
>
2024-01-09 21:52:47 -06:00
<TrashOutline class='w-6 h-6' />
2024-01-07 16:21:14 -06:00
</Button>
<Button
title='Edit'
className='py-2'
2024-01-09 21:52:47 -06:00
onClick={() => editUser.value = user}
2024-01-07 16:21:14 -06:00
>
2024-01-09 21:52:47 -06:00
<PencilSquareOutline class='w-6 h-6' />
2024-01-07 16:21:14 -06:00
</Button>
</td>
</tr>
)
})}
2024-01-07 02:24:36 -06:00
</tbody>
</table>
<header class='flex items-center border-b-2 border-stone-500/20 '>
<h1 class='p-5 text-2xl'>
2024-01-07 11:27:50 -06:00
Todos ({todos.length})
2024-01-07 02:24:36 -06:00
</h1>
</header>
<table class='border-separate [border-spacing:1.25rem] text-left'>
<thead>
<tr>
<th>Description</th>
<th>Assignee</th>
</tr>
</thead>
<tbody>
2024-01-09 21:52:47 -06:00
{Object.entries(todos).map((
[_id, { description, assigneeUserId }],
) => (
2024-01-07 02:24:36 -06:00
<tr>
<td>{description}</td>
<td>
{assigneeUserId == null
? 'Unassigned'
2024-01-09 21:52:47 -06:00
: users[assigneeUserId]?.name}
2024-01-07 02:24:36 -06:00
</td>
</tr>
))}
</tbody>
</table>
</main>
)
}