194 lines
5.7 KiB
TypeScript
194 lines
5.7 KiB
TypeScript
"use client"
|
|
import type { ProjectSlugs } from "@conusai-kit/ui"
|
|
import { useDataClient, useWrite } from "@conusai-kit/ui/data"
|
|
// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
|
|
import {
|
|
FormLayout,
|
|
type FormValues,
|
|
PageSection,
|
|
ResourceList,
|
|
TextField,
|
|
usePageNavigation,
|
|
useResourceList,
|
|
} from "@conusai-kit/ui/patterns"
|
|
import { useState } from "react"
|
|
import type { CommentReceipt, CommentView, TaskReceipt, TaskView } from "../contract/index.js"
|
|
import { describeFailure, type Labels } from "./labels.js"
|
|
|
|
type Page<T> = { rows: T[]; after?: string | undefined }
|
|
export function Tasks({
|
|
slugs,
|
|
seed,
|
|
href,
|
|
labels,
|
|
timeline = false,
|
|
}: {
|
|
slugs: ProjectSlugs
|
|
seed: Page<TaskView>
|
|
href: string
|
|
labels: Labels
|
|
timeline?: boolean
|
|
}) {
|
|
const client = useDataClient()
|
|
const { list } = useResourceList<TaskView>({
|
|
seed,
|
|
rowId: (task) => task.taskId,
|
|
read: async ({ after }) =>
|
|
(
|
|
await client.call<Page<TaskView>>(timeline ? "activities.timeline.list" : "activities.task.list", {
|
|
...slugs,
|
|
...(after ? { after } : {}),
|
|
})
|
|
).value,
|
|
})
|
|
return (
|
|
<ResourceList
|
|
{...list}
|
|
label={timeline ? labels.timeline : labels.open}
|
|
columns={[
|
|
{ id: "subject", header: labels.subject, wrap: true },
|
|
{
|
|
id: "status",
|
|
header: labels.status,
|
|
cell: (task) =>
|
|
task.status === "open"
|
|
? labels.stateOpen
|
|
: task.status === "done"
|
|
? labels.stateDone
|
|
: labels.stateCancelled,
|
|
},
|
|
]}
|
|
rowHref={(task) => `${href}/${task.taskId}`}
|
|
rowLabel={(task) => task.subject}
|
|
empty={{ title: labels.empty }}
|
|
/>
|
|
)
|
|
}
|
|
export function TaskForm({
|
|
slugs,
|
|
task,
|
|
href,
|
|
labels,
|
|
}: {
|
|
slugs: ProjectSlugs
|
|
task?: TaskView
|
|
href: string
|
|
labels: Labels
|
|
}) {
|
|
const navigation = usePageNavigation()
|
|
const [requestId] = useState(() => crypto.randomUUID())
|
|
const mutation = useWrite<Record<string, unknown>, TaskReceipt>(
|
|
task ? "activities.task.update" : "activities.task.create",
|
|
)
|
|
const submit = async ({ subject, body = "" }: FormValues) => {
|
|
const result = await mutation.mutate({
|
|
...slugs,
|
|
subject,
|
|
body,
|
|
requestId,
|
|
...(task ? { taskId: task.taskId, expectedVersion: task.revision } : {}),
|
|
})
|
|
navigation.navigate(`${href}/${result.taskId}`)
|
|
}
|
|
return (
|
|
<PageSection title={task ? labels.update : labels.create} variant="card">
|
|
<FormLayout
|
|
key={requestId}
|
|
describeError={(error) => describeFailure(error, labels)}
|
|
submitLabel={task ? labels.update : labels.create}
|
|
busyLabel={labels.busy}
|
|
onSubmit={submit}
|
|
>
|
|
<TextField name="subject" label={labels.subject} required maxLength={200} defaultValue={task?.subject} />
|
|
<TextField name="body" label={labels.body} maxLength={10000} defaultValue={task?.body} />
|
|
</FormLayout>
|
|
</PageSection>
|
|
)
|
|
}
|
|
export function TaskState({ slugs, task, labels }: { slugs: ProjectSlugs; task: TaskView; labels: Labels }) {
|
|
const navigation = usePageNavigation()
|
|
const [requestId, setRequestId] = useState(() => crypto.randomUUID())
|
|
const change = useWrite<Record<string, unknown>, TaskReceipt>(
|
|
task.status === "done" ? "activities.task.reopen" : "activities.task.complete",
|
|
)
|
|
if (task.status === "cancelled") return null
|
|
const submit = async () => {
|
|
await change.mutate({ ...slugs, taskId: task.taskId, expectedVersion: task.revision, requestId })
|
|
await navigation.refresh()
|
|
setRequestId(crypto.randomUUID())
|
|
}
|
|
return (
|
|
<FormLayout
|
|
key={requestId}
|
|
describeError={(error) => describeFailure(error, labels)}
|
|
submitLabel={task.status === "done" ? labels.reopen : labels.complete}
|
|
busyLabel={labels.busy}
|
|
onSubmit={submit}
|
|
/>
|
|
)
|
|
}
|
|
export function Comments({
|
|
slugs,
|
|
task,
|
|
seed,
|
|
labels,
|
|
}: {
|
|
slugs: ProjectSlugs
|
|
task: TaskView
|
|
seed: Page<CommentView>
|
|
labels: Labels
|
|
}) {
|
|
const client = useDataClient()
|
|
const { list } = useResourceList<CommentView>({
|
|
seed,
|
|
rowId: (comment) => comment.commentId,
|
|
read: async ({ after }) =>
|
|
(
|
|
await client.call<Page<CommentView>>("activities.comment.list", {
|
|
...slugs,
|
|
taskId: task.taskId,
|
|
...(after ? { after } : {}),
|
|
})
|
|
).value,
|
|
})
|
|
return (
|
|
<ResourceList
|
|
{...list}
|
|
label={labels.comments}
|
|
columns={[
|
|
{
|
|
id: "body",
|
|
header: labels.comment,
|
|
wrap: true,
|
|
cell: (comment) => (comment.erased ? labels.erased : comment.body),
|
|
},
|
|
]}
|
|
rowLabel={(comment) => (comment.erased ? labels.erased : comment.body)}
|
|
empty={{ title: labels.emptyComments }}
|
|
/>
|
|
)
|
|
}
|
|
export function CommentForm({ slugs, task, labels }: { slugs: ProjectSlugs; task: TaskView; labels: Labels }) {
|
|
const navigation = usePageNavigation()
|
|
const [requestId, setRequestId] = useState(() => crypto.randomUUID())
|
|
const add = useWrite<Record<string, unknown>, CommentReceipt>("activities.comment.add")
|
|
const submit = async ({ body = "" }: FormValues) => {
|
|
await add.mutate({ ...slugs, taskId: task.taskId, expectedVersion: task.revision, requestId, body })
|
|
await navigation.refresh()
|
|
setRequestId(crypto.randomUUID())
|
|
}
|
|
return (
|
|
<PageSection title={labels.addComment}>
|
|
<FormLayout
|
|
key={requestId}
|
|
describeError={(error) => describeFailure(error, labels)}
|
|
submitLabel={labels.addComment}
|
|
busyLabel={labels.busy}
|
|
onSubmit={submit}
|
|
>
|
|
<TextField name="body" label={labels.comment} required maxLength={10000} />
|
|
</FormLayout>
|
|
</PageSection>
|
|
)
|
|
}
|