103 lines
4.0 KiB
TypeScript
103 lines
4.0 KiB
TypeScript
// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
|
|
import { businessRefusalNotices, businessServiceNotices } from "@conusai-kit/business/refusals"
|
|
import { type Container, notFound, type TransportCaller } from "@conusai-kit/core"
|
|
import type { I18n } from "@conusai-kit/i18n"
|
|
import { type ProjectPage, projectPage } from "@conusai-kit/ui"
|
|
import { Can } from "@conusai-kit/ui/data"
|
|
import {
|
|
DetailLayout,
|
|
Page,
|
|
PageHeader,
|
|
PageSection,
|
|
type ServiceNotice,
|
|
ServiceStatePage,
|
|
serviceNotice,
|
|
} from "@conusai-kit/ui/patterns"
|
|
import * as contracts from "../contract/index.js"
|
|
import { en } from "../i18n/en.js"
|
|
import { CommentForm, Comments, TaskForm, TaskState, Tasks } from "./controls.js"
|
|
import type { Labels, Messages } from "./labels.js"
|
|
|
|
type PageOf<T> = { rows: T[]; after?: string | undefined }
|
|
type Data = { labels: Labels } & (
|
|
| { kind: "list"; tasks: PageOf<contracts.TaskView>; timeline: PageOf<contracts.TaskView> }
|
|
| { kind: "task"; task: contracts.TaskView; comments: PageOf<contracts.CommentView> }
|
|
| { kind: "state"; notice: ServiceNotice }
|
|
)
|
|
export const activitiesPage: ProjectPage<TransportCaller, I18n, Container, Data> = projectPage({
|
|
async load({ slugs, segments, caller, i18n }) {
|
|
const messages = Object.fromEntries(
|
|
Object.keys(en).map((key) => [key, i18n.message(`activities.${key}`)]),
|
|
) as Messages
|
|
const labels: Labels = {
|
|
...messages,
|
|
refusals: businessRefusalNotices(i18n.locale, {
|
|
conflict: messages.conflict,
|
|
revision_conflict: messages.conflict,
|
|
invalid: messages.invalid,
|
|
}),
|
|
}
|
|
const [taskId, ...rest] = segments
|
|
if (rest.length || (taskId !== undefined && !contracts.activityId.safeParse(taskId).success)) throw notFound()
|
|
try {
|
|
if (taskId === undefined) {
|
|
const [tasks, timeline] = await Promise.all([
|
|
caller.call(contracts.taskList, slugs),
|
|
caller.call(contracts.timeline, slugs),
|
|
])
|
|
return { kind: "list", labels, tasks, timeline }
|
|
}
|
|
const [task, comments] = await Promise.all([
|
|
caller.call(contracts.taskGet, { ...slugs, taskId }),
|
|
caller.call(contracts.commentList, { ...slugs, taskId }),
|
|
])
|
|
return { kind: "task", labels, task, comments }
|
|
} catch (error) {
|
|
const notice = serviceNotice(error, businessServiceNotices(i18n.locale))
|
|
if (!notice) throw error
|
|
return { kind: "state", labels, notice }
|
|
}
|
|
},
|
|
View({ slugs, data }) {
|
|
const labels = data.labels
|
|
const href = `/${slugs.org}/${slugs.workspace}/${slugs.project}/activities`
|
|
if (data.kind === "state") return <ServiceStatePage title={labels.title} notice={data.notice} />
|
|
if (data.kind === "list")
|
|
return (
|
|
<Page>
|
|
<PageHeader title={labels.title} description={labels.intro} />
|
|
<DetailLayout
|
|
aside={
|
|
<Can permission="activities.task.write">
|
|
<TaskForm slugs={slugs} href={href} labels={labels} />
|
|
</Can>
|
|
}
|
|
>
|
|
<PageSection title={labels.open}>
|
|
<Tasks slugs={slugs} seed={data.tasks} href={href} labels={labels} />
|
|
</PageSection>
|
|
<PageSection title={labels.timeline}>
|
|
<Tasks slugs={slugs} seed={data.timeline} href={href} labels={labels} timeline />
|
|
</PageSection>
|
|
</DetailLayout>
|
|
</Page>
|
|
)
|
|
return (
|
|
<Page>
|
|
<PageHeader title={data.task.subject} description={data.task.body} />
|
|
<a href={href}>{labels.back}</a>
|
|
<Can permission="activities.task.write">
|
|
<TaskState slugs={slugs} task={data.task} labels={labels} />
|
|
<TaskForm slugs={slugs} task={data.task} href={href} labels={labels} />
|
|
</Can>
|
|
<PageSection title={labels.comments}>
|
|
<Comments slugs={slugs} task={data.task} seed={data.comments} labels={labels} />
|
|
</PageSection>
|
|
<Can permission="activities.comment.write">
|
|
<CommentForm slugs={slugs} task={data.task} labels={labels} />
|
|
</Can>
|
|
</Page>
|
|
)
|
|
},
|
|
})
|