Merge remote-tracking branch 'origin/master'

This commit is contained in:
Daniel Flanagan 2024-01-17 11:53:27 -06:00
commit cc6cda7236
Signed by: lytedev
GPG key ID: 5B2020A0F9921EF4
9 changed files with 124 additions and 71 deletions

View file

@ -1,19 +0,0 @@
// import { JSX } from 'preact'
// import { IS_BROWSER } from '$fresh/runtime.ts'
import { Bars3Outline } from 'preact-heroicons'
import { Clock } from '@homeman/islands/Clock.tsx'
export function Nav(/* props: {} */) {
return (
<nav class='bg-stone-200 dark:bg-stone-800 flex justify-items-start items-center'>
<button class='p-4 hover:bg-stone-500/20'>
<Bars3Outline class='h-6 w-6' />
</button>
<a class='p-4 hover:bg-stone-500/20' href='/'>
Flanagan Family
</a>
<Clock class='ml-auto pr-4 text-2xl' />
</nav>
)
}

View file

@ -9,19 +9,10 @@
"preview": "deno run -A main.ts", "preview": "deno run -A main.ts",
"update": "deno run -A -r https://fresh.deno.dev/update ." "update": "deno run -A -r https://fresh.deno.dev/update ."
}, },
"lint": { "lint": { "rules": { "tags": ["fresh", "recommended"] } },
"rules": { "exclude": ["**/_fresh/*"],
"tags": [
"fresh",
"recommended"
]
}
},
"exclude": [
"**/_fresh/*"
],
"imports": { "imports": {
"$fresh/": "https://deno.land/x/fresh@1.6.1/", "$fresh/": "https://deno.land/x/fresh@1.6.3/",
"preact": "https://esm.sh/preact@10.19.2", "preact": "https://esm.sh/preact@10.19.2",
"preact/": "https://esm.sh/preact@10.19.2/", "preact/": "https://esm.sh/preact@10.19.2/",
"@preact/signals": "https://esm.sh/*@preact/signals@1.2.1", "@preact/signals": "https://esm.sh/*@preact/signals@1.2.1",
@ -33,15 +24,8 @@
"$std/": "https://deno.land/std@0.208.0/", "$std/": "https://deno.land/std@0.208.0/",
"@homeman/": "./" "@homeman/": "./"
}, },
"compilerOptions": { "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "preact" },
"jsx": "react-jsx", "fmt": { "useTabs": true, "semiColons": false, "singleQuote": true },
"jsxImportSource": "preact"
},
"fmt": {
"useTabs": true,
"semiColons": false,
"singleQuote": true
},
"unstable": ["kv"], "unstable": ["kv"],
"nodeModulesDir": true "nodeModulesDir": true
} }

View file

@ -15,6 +15,7 @@ import * as $Admin from './islands/Admin.tsx'
import * as $Clock from './islands/Clock.tsx' import * as $Clock from './islands/Clock.tsx'
import * as $Counter from './islands/Counter.tsx' import * as $Counter from './islands/Counter.tsx'
import * as $Dashboard from './islands/Dashboard.tsx' import * as $Dashboard from './islands/Dashboard.tsx'
import * as $Nav from './islands/Nav.tsx'
import * as $TodoList from './islands/TodoList.tsx' import * as $TodoList from './islands/TodoList.tsx'
import { type Manifest } from '$fresh/server.ts' import { type Manifest } from '$fresh/server.ts'
@ -35,6 +36,7 @@ const manifest = {
'./islands/Clock.tsx': $Clock, './islands/Clock.tsx': $Clock,
'./islands/Counter.tsx': $Counter, './islands/Counter.tsx': $Counter,
'./islands/Dashboard.tsx': $Dashboard, './islands/Dashboard.tsx': $Dashboard,
'./islands/Nav.tsx': $Nav,
'./islands/TodoList.tsx': $TodoList, './islands/TodoList.tsx': $TodoList,
}, },
baseUrl: import.meta.url, baseUrl: import.meta.url,

View file

@ -21,12 +21,23 @@ export interface Props {
} }
async function promptDeleteUser(id: string, name: string) { async function promptDeleteUser(id: string, name: string) {
if (confirm(`Are you sure you want to delete ${name} (${id})?`)) { if (confirm(`Are you sure you want to delete '${name}' (${id})?`)) {
await fetch(`/api/user?id=${id}`, { method: 'DELETE' }) await fetch(`/api/user?id=${id}`, { method: 'DELETE' })
location.reload() location.reload()
} }
} }
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()
}
}
interface UserFormProps extends JSX.HTMLAttributes<HTMLFormElement> { interface UserFormProps extends JSX.HTMLAttributes<HTMLFormElement> {
onCancelButtonClicked: JSX.MouseEventHandler<HTMLButtonElement> onCancelButtonClicked: JSX.MouseEventHandler<HTMLButtonElement>
userData: User | null userData: User | null
@ -123,7 +134,7 @@ export function Admin({ users, todos }: Props) {
<td class=''> <td class=''>
<Button <Button
title='Delete' title='Delete'
className='py-2 mr-2' className='py-2 mr-2 hover:bg-rose-500'
onClick={() => promptDeleteUser(id, name)} onClick={() => promptDeleteUser(id, name)}
> >
<TrashOutline class='w-6 h-6' /> <TrashOutline class='w-6 h-6' />
@ -147,19 +158,35 @@ export function Admin({ users, todos }: Props) {
<tr> <tr>
<th>Description</th> <th>Description</th>
<th>Assignee</th> <th>Assignee</th>
<th>Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{Object.entries(todos).map(( {Object.entries(todos).map((
[_id, { description, assigneeUserId }], [_index, { id, description, assigneeUserId }],
) => ( ) => (
<tr> <tr>
<td>{description}</td> <td>{description}</td>
<td> <td
style={`color: #${
assigneeUserId == null
? 'ffffff'
: users[assigneeUserId]?.color
}`}
>
{assigneeUserId == null {assigneeUserId == null
? 'Unassigned' ? 'Unassigned'
: users[assigneeUserId]?.name} : users[assigneeUserId]?.name}
</td> </td>
<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>
</tr> </tr>
))} ))}
</tbody> </tbody>

37
islands/Nav.tsx Normal file
View file

@ -0,0 +1,37 @@
// import { JSX } from 'preact'
// import { IS_BROWSER } from '$fresh/runtime.ts'
import { Bars3Outline } from 'preact-heroicons'
import { Clock } from '@homeman/islands/Clock.tsx'
import { Dialog } from '@homeman/components/Dialog.tsx'
import { useSignal } from '@preact/signals'
export function Nav(/* props: {} */) {
const showMenu = useSignal(false)
return (
<>
<Dialog
headerTitle='Main Menu'
show={showMenu.value}
onClose={() => {
showMenu.value = false
}}
>
</Dialog>
<nav class='bg-stone-200 dark:bg-stone-800 flex justify-items-start items-center'>
<button
class='p-4 hover:bg-stone-500/20'
onClick={() => {
showMenu.value = true
}}
>
<Bars3Outline class='h-6 w-6' />
</button>
<a class='p-4 hover:bg-stone-500/20' href='/'>
Flanagan Family
</a>
<Clock class='ml-auto pr-4 text-2xl' />
</nav>
</>
)
}

View file

@ -2,8 +2,7 @@ import { Todo, UserWithTodos } from '@homeman/models.ts'
import { JSX } from 'preact' import { JSX } from 'preact'
import { Button } from '@homeman/components/Button.tsx' import { Button } from '@homeman/components/Button.tsx'
import { Avatar } from '@homeman/components/Avatar.tsx' import { Avatar } from '@homeman/components/Avatar.tsx'
import { ChevronDoubleDownOutline, ChevronDownOutline } from 'preact-heroicons' import { ChevronDownMiniSolid, TrashSolid } from 'preact-heroicons'
import { ChevronDownMiniSolid } from 'preact-heroicons'
export interface Props { export interface Props {
user: UserWithTodos user: UserWithTodos
@ -18,49 +17,69 @@ export function TodoList(
const doneTodos = user.assignedTodos.filter((t) => t.doneAt != null) const doneTodos = user.assignedTodos.filter((t) => t.doneAt != null)
const inProgressTodos = user.assignedTodos.filter((t) => t.doneAt == null) const inProgressTodos = user.assignedTodos.filter((t) => t.doneAt == null)
const todoItem = ( const todoItem = (
{ id, doneAt, className, description, hideDone }: { id, doneAt, className, description, virtual }:
& Pick<Todo, 'description' | 'id' | 'doneAt'> & Pick<Todo, 'description' | 'id' | 'doneAt'>
& { & {
className?: string className?: string
hideDone?: boolean virtual?: boolean
}, },
) => ( ) => (
<li <li
style={`border-color: #${color}`} style={`border-color: #${color}`}
title={doneAt != null ? `Completed at ${doneAt}` : ''} title={doneAt != null ? `Completed at ${doneAt}` : ''}
class={`${className || ''} ${ class={`${className || ''} ${
hideDone ? '' : 'border-l-4' 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`} } 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> <span class='text-xl'>{description}</span>
{(hideDone || doneAt != null) ? '' : ( <footer class='flex gap-2'>
<Button {(virtual || doneAt != null) ? '' : (
onClick={async () => {
await fetch('/api/todo/done', {
method: 'PUT',
body: JSON.stringify({ id }),
})
await onTodoDone(id)
}}
>
Done
</Button>
)}
{(!hideDone && doneAt != null)
? (
<Button <Button
className='grow'
onClick={async () => { onClick={async () => {
await fetch('/api/todo/done', { await fetch('/api/todo/done', {
method: 'DELETE', method: 'PUT',
body: JSON.stringify({ id }), body: JSON.stringify({ id }),
}) })
await onTodoDone(id) await onTodoDone(id)
}} }}
> >
Not Done Done
</Button> </Button>
) )}
: ''} {(!virtual && doneAt != null)
? (
<Button
className='grow'
onClick={async () => {
await fetch('/api/todo/done', {
method: 'DELETE',
body: JSON.stringify({ id }),
})
await onTodoDone(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> </li>
) )
return ( return (
@ -86,7 +105,7 @@ export function TodoList(
doneAt: null, doneAt: null,
description: 'All clear! 🎉', description: 'All clear! 🎉',
className: 'text-center', className: 'text-center',
hideDone: true, virtual: true,
}) })
: inProgressTodos.map(todoItem)} : inProgressTodos.map(todoItem)}
</ul> </ul>

View file

@ -1,5 +1,5 @@
import { type PageProps } from '$fresh/server.ts' import { type PageProps } from '$fresh/server.ts'
import { Nav } from '@homeman/components/Nav.tsx' import { Nav } from '@homeman/islands/Nav.tsx'
export default function App({ Component }: PageProps) { export default function App({ Component }: PageProps) {
return ( return (

View file

@ -4,7 +4,9 @@ import { Admin, Props } from '@homeman/islands/Admin.tsx'
export const handler: Handlers = { export const handler: Handlers = {
async GET(_req, ctx) { async GET(_req, ctx) {
const users = await db.users.findMany({}) const users = Object.fromEntries((await db.users.findMany({})).map(
(u) => [u.id, u],
))
const todos = await db.todos.findMany({}) const todos = await db.todos.findMany({})
return ctx.render({ users, todos }) return ctx.render({ users, todos })
}, },

View file

@ -60,6 +60,7 @@ export const handler: Handlers<Todo | null> = {
} else { } else {
data = { id: new URL(req.url).searchParams.get('id') } data = { id: new URL(req.url).searchParams.get('id') }
} }
console.log('delete todo data:', data)
const todoData = TodoModel.pick({ id: true }).parse(data) const todoData = TodoModel.pick({ id: true }).parse(data)
const result = await db.todos.delete({ where: todoData }) const result = await db.todos.delete({ where: todoData })
return new Response(JSON.stringify(result)) return new Response(JSON.stringify(result))