homeman-deno/islands/Admin.tsx

206 lines
5.2 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'
2024-01-09 23:06:44 -06:00
import {
PencilSquareOutline,
PlusOutline,
PlusSolid,
TrashOutline,
} from 'preact-heroicons'
import { Table } from '@homeman/components/Table.tsx'
import { SectionHead } from '@homeman/components/SectionHead.tsx'
import { Avatar } from '@homeman/components/Avatar.tsx'
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) {
2024-01-16 16:45:51 -06:00
if (confirm(`Are you sure you want to delete '${name}' (${id})?`)) {
2024-01-07 11:38:25 -06:00
await fetch(`/api/user?id=${id}`, { method: 'DELETE' })
location.reload()
}
}
2024-01-16 16:45:51 -06:00
async function promptDeleteTodo(id: string, description: string) {
if (confirm(`Are you sure you want to delete '${description}' (${id})?`)) {
await fetch(`/api/todo`, {
method: 'DELETE',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ id }),
})
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-12 10:04:50 -06:00
<header class='flex items-center border-b-2 border-stone-500/20 '>
<h1 class='p-5 text-2xl'>
Administration Page
</h1>
</header>
2024-01-09 21:52:47 -06:00
<Dialog
headerTitle='Add user'
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}'`}
show={!!editUser.value}
onClose={() => editUser.value = null}
2024-01-07 16:21:14 -06:00
>
2024-01-09 21:52:47 -06:00
<UserForm
onCancelButtonClicked={() => editUser.value = null}
2024-01-09 23:06:44 -06:00
userData={editUser.value}
2024-01-09 21:52:47 -06:00
/>
</Dialog>
2024-01-17 21:14:33 -06:00
<SectionHead text={`Users (${Object.keys(users).length})`}>
2024-01-09 21:52:47 -06:00
<Button onClick={() => showAddUserDialog.value = true}>
2024-01-09 23:06:44 -06:00
<PlusOutline class='w-6 h-6' />
2024-01-07 02:24:36 -06:00
</Button>
2024-01-09 23:06:44 -06:00
</SectionHead>
<Table>
2024-01-07 02:24:36 -06:00
<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'
2024-01-17 21:14:33 -06:00
: <Avatar className='h-[64px] w-[64px]' src={avatarUrl} />}
2024-01-07 16:21:14 -06:00
</td>
<td style={`color: #${color}`}>
#{color}
</td>
<td class=''>
<Button
title='Delete'
2024-01-16 16:45:51 -06:00
className='py-2 mr-2 hover:bg-rose-500'
2024-01-07 16:21:14 -06:00
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>
2024-01-09 23:06:44 -06:00
</Table>
<SectionHead text={`Todos (${todos.length})`} />
<Table>
2024-01-07 02:24:36 -06:00
<thead>
<tr>
<th>Description</th>
<th>Assignee</th>
2024-01-17 21:14:33 -06:00
<th>Done At</th>
<th>Emoji</th>
2024-01-16 16:45:51 -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(todos).map((
2024-01-17 21:14:33 -06:00
[_index, { id, emoji, doneAt, description, assigneeUserId }],
2024-01-09 21:52:47 -06:00
) => (
2024-01-07 02:24:36 -06:00
<tr>
<td>{description}</td>
2024-01-16 16:45:51 -06:00
<td
style={`color: #${
assigneeUserId == null
? 'ffffff'
: users[assigneeUserId]?.color
}`}
>
2024-01-07 02:24:36 -06:00
{assigneeUserId == null
? 'Unassigned'
2024-01-09 21:52:47 -06:00
: users[assigneeUserId]?.name}
2024-01-07 02:24:36 -06:00
</td>
2024-01-17 21:14:33 -06:00
<td>{doneAt == null ? 'Not Done' : doneAt.toLocaleString()}</td>
<td>{emoji || 'No Emoji'}</td>
2024-01-16 16:45:51 -06:00
<td>
<Button
title='Delete'
className='py-2 mr-2 hover:bg-rose-500'
onClick={() => promptDeleteTodo(id, description)}
>
<TrashOutline class='w-6 h-6' />
</Button>
</td>
2024-01-07 02:24:36 -06:00
</tr>
))}
</tbody>
2024-01-09 23:06:44 -06:00
</Table>
2024-01-07 02:24:36 -06:00
</main>
)
}