Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
cc6cda7236
|
@ -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>
|
||||
)
|
||||
}
|
26
deno.json
26
deno.json
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
37
islands/Nav.tsx
Normal 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>
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -2,8 +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 { ChevronDoubleDownOutline, ChevronDownOutline } from 'preact-heroicons'
|
||||
import { ChevronDownMiniSolid } from 'preact-heroicons'
|
||||
import { ChevronDownMiniSolid, TrashSolid } from 'preact-heroicons'
|
||||
|
||||
export interface Props {
|
||||
user: UserWithTodos
|
||||
|
@ -18,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',
|
||||
|
@ -46,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',
|
||||
|
@ -61,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 (
|
||||
|
@ -86,7 +105,7 @@ export function TodoList(
|
|||
doneAt: null,
|
||||
description: 'All clear! 🎉',
|
||||
className: 'text-center',
|
||||
hideDone: true,
|
||||
virtual: true,
|
||||
})
|
||||
: inProgressTodos.map(todoItem)}
|
||||
</ul>
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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 })
|
||||
},
|
||||
|
|
|
@ -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))
|
||||
|
|
Loading…
Reference in a new issue