Crudded up, WIP menu for routine

This commit is contained in:
Daniel Flanagan 2024-01-16 16:45:51 -06:00
parent d2341cdc52
commit bdb444328b
Signed by: lytedev
GPG key ID: 5B2020A0F9921EF4
9 changed files with 124 additions and 69 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",
"update": "deno run -A -r https://fresh.deno.dev/update ."
},
"lint": {
"rules": {
"tags": [
"fresh",
"recommended"
]
}
},
"exclude": [
"**/_fresh/*"
],
"lint": { "rules": { "tags": ["fresh", "recommended"] } },
"exclude": ["**/_fresh/*"],
"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/signals": "https://esm.sh/*@preact/signals@1.2.1",
@ -33,15 +24,8 @@
"$std/": "https://deno.land/std@0.208.0/",
"@homeman/": "./"
},
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "preact"
},
"fmt": {
"useTabs": true,
"semiColons": false,
"singleQuote": true
},
"compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "preact" },
"fmt": { "useTabs": true, "semiColons": false, "singleQuote": true },
"unstable": ["kv"],
"nodeModulesDir": true
}

View file

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

View file

@ -21,12 +21,23 @@ export interface Props {
}
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' })
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> {
onCancelButtonClicked: JSX.MouseEventHandler<HTMLButtonElement>
userData: User | null
@ -123,7 +134,7 @@ export function Admin({ users, todos }: Props) {
<td class=''>
<Button
title='Delete'
className='py-2 mr-2'
className='py-2 mr-2 hover:bg-rose-500'
onClick={() => promptDeleteUser(id, name)}
>
<TrashOutline class='w-6 h-6' />
@ -147,19 +158,35 @@ export function Admin({ users, todos }: Props) {
<tr>
<th>Description</th>
<th>Assignee</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{Object.entries(todos).map((
[_id, { description, assigneeUserId }],
[_index, { id, description, assigneeUserId }],
) => (
<tr>
<td>{description}</td>
<td>
<td
style={`color: #${
assigneeUserId == null
? 'ffffff'
: users[assigneeUserId]?.color
}`}
>
{assigneeUserId == null
? 'Unassigned'
: users[assigneeUserId]?.name}
</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>
))}
</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,6 +2,7 @@ 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 { TrashSolid } from 'preact-heroicons'
export interface Props {
user: UserWithTodos
@ -16,23 +17,25 @@ export function TodoList(
const doneTodos = user.assignedTodos.filter((t) => t.doneAt != null)
const inProgressTodos = user.assignedTodos.filter((t) => t.doneAt == null)
const todoItem = (
{ id, doneAt, className, description, hideDone }:
{ id, doneAt, className, description, virtual }:
& Pick<Todo, 'description' | 'id' | 'doneAt'>
& {
className?: string
hideDone?: boolean
virtual?: boolean
},
) => (
<li
style={`border-color: #${color}`}
title={doneAt != null ? `Completed at ${doneAt}` : ''}
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`}
>
<span class='text-xl'>{description}</span>
{(hideDone || doneAt != null) ? '' : (
<footer class='flex gap-2'>
{(virtual || doneAt != null) ? '' : (
<Button
className='grow'
onClick={async () => {
await fetch('/api/todo/done', {
method: 'PUT',
@ -44,9 +47,10 @@ export function TodoList(
Done
</Button>
)}
{(!hideDone && doneAt != null)
{(!virtual && doneAt != null)
? (
<Button
className='grow'
onClick={async () => {
await fetch('/api/todo/done', {
method: 'DELETE',
@ -59,6 +63,23 @@ export function TodoList(
</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 (
@ -84,7 +105,7 @@ export function TodoList(
doneAt: null,
description: 'All clear! 🎉',
className: 'text-center',
hideDone: true,
virtual: true,
})
: inProgressTodos.map(todoItem)}
</ul>

View file

@ -1,5 +1,5 @@
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) {
return (

View file

@ -4,7 +4,9 @@ import { Admin, Props } from '@homeman/islands/Admin.tsx'
export const handler: Handlers = {
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({})
return ctx.render({ users, todos })
},

View file

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