Done works

This commit is contained in:
Daniel Flanagan 2024-01-11 18:57:29 -06:00
parent 2ba678c4a3
commit 78bd64f100
Signed by: lytedev
GPG key ID: 5B2020A0F9921EF4
6 changed files with 69 additions and 21 deletions

View file

@ -7,6 +7,7 @@ import * as $_app from './routes/_app.tsx'
import * as $admin from './routes/admin.tsx' import * as $admin from './routes/admin.tsx'
import * as $api_joke from './routes/api/joke.ts' import * as $api_joke from './routes/api/joke.ts'
import * as $api_todo from './routes/api/todo.ts' import * as $api_todo from './routes/api/todo.ts'
import * as $api_todo_done from './routes/api/todo/done.ts'
import * as $api_user from './routes/api/user.ts' import * as $api_user from './routes/api/user.ts'
import * as $greet_name_ from './routes/greet/[name].tsx' import * as $greet_name_ from './routes/greet/[name].tsx'
import * as $index from './routes/index.tsx' import * as $index from './routes/index.tsx'
@ -24,6 +25,7 @@ const manifest = {
'./routes/admin.tsx': $admin, './routes/admin.tsx': $admin,
'./routes/api/joke.ts': $api_joke, './routes/api/joke.ts': $api_joke,
'./routes/api/todo.ts': $api_todo, './routes/api/todo.ts': $api_todo,
'./routes/api/todo/done.ts': $api_todo_done,
'./routes/api/user.ts': $api_user, './routes/api/user.ts': $api_user,
'./routes/greet/[name].tsx': $greet_name_, './routes/greet/[name].tsx': $greet_name_,
'./routes/index.tsx': $index, './routes/index.tsx': $index,

View file

@ -21,12 +21,13 @@ const unassignedUserPlaceholder: User = {
color: '888888', color: '888888',
} }
interface UserSelectButtonProps extends JSX.HTMLAttributes<HTMLLabelElement> { interface UserSelectButtonProps extends JSX.HTMLAttributes<HTMLInputElement> {
user: User user: User
} }
function UserSelectButton( function UserSelectButton(
{ user: { id, avatarUrl, color }, ...props }: UserSelectButtonProps, { user: { id, name, avatarUrl, color }, tabindex, ...props }:
UserSelectButtonProps,
) { ) {
const eid = `assigneeUserId_${id}` const eid = `assigneeUserId_${id}`
return ( return (
@ -34,17 +35,18 @@ function UserSelectButton(
<input <input
aria-hidden='true' aria-hidden='true'
type='radio' type='radio'
class='hidden left-[99999px]' class='peer sr-only'
id={eid} id={eid}
name='assigneeUserId' name='assigneeUserId'
value={id} value={id}
{...props}
/> />
<Label <Label
{...props}
for={eid} for={eid}
className='cursor-pointer hover:bg-gray-500/20 rounded p-2' 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'
role='button' role='button'
aria-pressed='false'
> >
<Avatar <Avatar
className='mb-2' className='mb-2'
@ -54,7 +56,7 @@ function UserSelectButton(
style={`color: #${color};`} style={`color: #${color};`}
class='font-semibold text-center' class='font-semibold text-center'
> >
Shared {name}
</span> </span>
</Label> </Label>
</> </>
@ -91,13 +93,20 @@ export default function Dashboard({ users, unassignedTodos }: Props) {
Assignee Assignee
<ul class='flex gap-2'> <ul class='flex gap-2'>
<li> <li>
<UserSelectButton tabindex={0} user={unassignedUser} /> <UserSelectButton
checked={todoAssignUserId.value == null}
tabindex={0}
user={unassignedUser}
/>
</li> </li>
{Object.values(users).map( {Object.values(users).map(
(user, i) => { (user) => {
return ( return (
<li> <li>
<UserSelectButton tabindex={i} user={user} /> <UserSelectButton
checked={todoAssignUserId.value == user.id}
user={user}
/>
</li> </li>
) )
}, },

View file

@ -13,7 +13,9 @@ export function TodoList(
Props, Props,
) { ) {
const todoItem = ( const todoItem = (
{ className, description, hideDone }: Pick<Todo, 'description'> & { { id, doneAt, className, description, hideDone }:
& Pick<Todo, 'description' | 'id' | 'doneAt'>
& {
className?: string className?: string
hideDone?: boolean hideDone?: boolean
}, },
@ -22,10 +24,24 @@ export function TodoList(
style={`border-color: #${color}`} style={`border-color: #${color}`}
class={`${className || ''} ${ class={`${className || ''} ${
hideDone ? '' : 'border-l-4' hideDone ? '' : 'border-l-4'
} p-4 rounded drop-shadow-lg bg-white dark:bg-stone-900 flex flex-col`} } p-4 rounded drop-shadow-lg bg-white dark:bg-stone-900 flex flex-col gap-2`}
> >
{JSON.stringify(doneAt)}
<span class='text-xl'>{description}</span> <span class='text-xl'>{description}</span>
{hideDone ? '' : <Button class='mt-2'>Done</Button>} {(hideDone || doneAt != null) ? '' : (
<Button
onClick={async () => {
await fetch('/api/todo/done', {
method: 'POST',
body: JSON.stringify({ id }),
})
// TODO: remove the todoitem?
// TODO: confetti
}}
>
Done
</Button>
)}
</li> </li>
) )
return ( return (

View file

@ -8,7 +8,11 @@ type TodoPayload = z.infer<typeof TodoPayload>
async function createOrUpdate(todo: TodoPayload) { async function createOrUpdate(todo: TodoPayload) {
if (!todo.id) { if (!todo.id) {
const newTodo: Todo = { ...todo, id: ulid(), createdAt: new Date() } const newTodo: Todo = {
...todo,
id: ulid(),
createdAt: new Date(),
}
return await db.todos.create({ data: newTodo }) return await db.todos.create({ data: newTodo })
} else { } else {
return await db.todos.update({ where: { id: todo.id }, data: todo }) return await db.todos.update({ where: { id: todo.id }, data: todo })
@ -18,7 +22,9 @@ async function createOrUpdate(todo: TodoPayload) {
export const handler: Handlers<Todo | null> = { export const handler: Handlers<Todo | null> = {
async POST(req, _ctx) { async POST(req, _ctx) {
if (req.headers.get('content-type')?.includes('json')) { if (req.headers.get('content-type')?.includes('json')) {
const result = await createOrUpdate(TodoPayload.parse(await req.json())) const result = await createOrUpdate(
TodoPayload.parse(await req.json()),
)
return new Response(JSON.stringify(result)) return new Response(JSON.stringify(result))
} else { } else {
const form = await req.formData() const form = await req.formData()
@ -29,7 +35,8 @@ export const handler: Handlers<Todo | null> = {
const todo = TodoPayload.parse({ const todo = TodoPayload.parse({
id: id, id: id,
emoji: form.get('emoji')?.toString(), emoji: form.get('emoji')?.toString() || null,
doneAt: form.get('doneAt')?.toString() || null,
description: form.get('description')?.toString(), description: form.get('description')?.toString(),
assigneeUserId: form.get('assigneeUserId')?.toString(), assigneeUserId: form.get('assigneeUserId')?.toString(),
}) })
@ -41,7 +48,7 @@ export const handler: Handlers<Todo | null> = {
await createOrUpdate(todo) await createOrUpdate(todo)
const url = new URL(req.url) const url = new URL(req.url)
url.pathname = '/admin' url.pathname = '/'
return Response.redirect(url, 303) return Response.redirect(url, 303)
} }
}, },

14
routes/api/todo/done.ts Normal file
View file

@ -0,0 +1,14 @@
import { Handlers } from '$fresh/server.ts'
import { db, TodoModel } from '@homeman/models.ts'
const Model = TodoModel.pick({ id: true })
export const handler: Handlers = {
async POST(req, _ctx) {
const { id } = Model.parse(await req.json())
const todo = await db.todos.findFirst({ where: { id } })
todo.doneAt = new Date()
const newTodo = await db.todos.update({ where: { id }, data: todo })
return new Response(JSON.stringify(newTodo))
},
}

View file

@ -1,5 +1,5 @@
import { Handlers, PageProps } from '$fresh/server.ts' import { Handlers, PageProps } from '$fresh/server.ts'
import { db, Todo, User, UserWithTodos } from '@homeman/models.ts' import { db, Todo, UserWithTodos } from '@homeman/models.ts'
import Dashboard from '@homeman/islands/Dashboard.tsx' import Dashboard from '@homeman/islands/Dashboard.tsx'
interface Data { interface Data {