@@ -0,0 +1,108 @@
|
||||
// A page above any project, on the shell's F10 contract: `/<org>/<workspace>/modules/notes`. It lists the
|
||||
// workspace's projects, each linked to its notes, and `?q=` filters them on the server, so the first byte
|
||||
// already holds the result and a filter is a link anyone can copy.
|
||||
//
|
||||
// The shell answers 403 for `permission` before `load` runs; the rest is a `ProjectPage` with two slugs. This is
|
||||
// the worked example for a catalogue, a list or a setting that belongs to a workspace, not to a project, and for
|
||||
// a page's strings as one record, with a count only the browser knows formatted there (project-picker.tsx).
|
||||
|
||||
import type { Container, TransportCaller } from "@conusai-kit/core"
|
||||
import type { I18n } from "@conusai-kit/i18n"
|
||||
import { projectList } from "@conusai-kit/tenancy"
|
||||
import { type WorkspacePage, workspacePage } from "@conusai-kit/ui"
|
||||
import { FilterBar, Page, PageHeader, PageState, TextField } from "@conusai-kit/ui/patterns"
|
||||
import { Button } from "@conusai-kit/ui/primitives"
|
||||
import { ProjectPicker } from "./project-picker.js"
|
||||
|
||||
/** Every string of the page, as one record. `selected` keeps its placeholder: only the browser knows the count. */
|
||||
export const WORKSPACE_LABEL_KEYS = [
|
||||
"title",
|
||||
"intro",
|
||||
"filter",
|
||||
"apply",
|
||||
"clear",
|
||||
"empty",
|
||||
"noProjects",
|
||||
"selected",
|
||||
] as const
|
||||
|
||||
export interface NotesWorkspaceData {
|
||||
readonly labels: Readonly<Record<(typeof WORKSPACE_LABEL_KEYS)[number], string>>
|
||||
readonly query: string
|
||||
readonly projects: readonly { readonly slug: string; readonly name: string }[]
|
||||
}
|
||||
|
||||
export type NotesWorkspacePage = WorkspacePage<TransportCaller, I18n, Container, NotesWorkspaceData>
|
||||
|
||||
export const notesWorkspacePage: NotesWorkspacePage = workspacePage({
|
||||
permission: "notes.note.read",
|
||||
async load({ slugs, search, caller, i18n }): Promise<NotesWorkspaceData> {
|
||||
const query = search.q ?? ""
|
||||
const wanted = query.trim().toLowerCase()
|
||||
const { projects } = await caller.call(projectList, slugs)
|
||||
return {
|
||||
labels: Object.fromEntries(
|
||||
WORKSPACE_LABEL_KEYS.map((key) => [key, i18n.message(`notes.workspace.${key}`)]),
|
||||
) as NotesWorkspaceData["labels"],
|
||||
query,
|
||||
projects: projects
|
||||
.filter((project) => project.name.toLowerCase().includes(wanted))
|
||||
.map(({ slug, name }) => ({ slug, name })),
|
||||
}
|
||||
},
|
||||
View({ slugs, locale, data }) {
|
||||
const { labels } = data
|
||||
return (
|
||||
<Page>
|
||||
<PageHeader title={labels.title} description={labels.intro} />
|
||||
<FilterBar
|
||||
method="get"
|
||||
actions={
|
||||
<>
|
||||
<Button type="submit" variant="secondary">
|
||||
{labels.apply}
|
||||
</Button>
|
||||
{data.query.trim() && data.projects.length > 0 && (
|
||||
<Button asChild variant="link">
|
||||
<a href={`/${slugs.org}/${slugs.workspace}/modules/notes`}>{labels.clear}</a>
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
>
|
||||
<TextField
|
||||
name="q"
|
||||
label={labels.filter}
|
||||
placeholder={labels.filter}
|
||||
type="search"
|
||||
defaultValue={data.query}
|
||||
/>
|
||||
</FilterBar>
|
||||
{data.projects.length === 0 ? (
|
||||
<PageState
|
||||
title={data.query.trim() ? labels.empty : labels.noProjects}
|
||||
action={
|
||||
data.query.trim() ? (
|
||||
<Button asChild variant="outline">
|
||||
<a href={`/${slugs.org}/${slugs.workspace}/modules/notes`}>{labels.clear}</a>
|
||||
</Button>
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<ProjectPicker
|
||||
label={labels.title}
|
||||
column={labels.filter}
|
||||
empty={labels.noProjects}
|
||||
projects={data.projects.map((project) => ({
|
||||
...project,
|
||||
href: `/${slugs.org}/${slugs.workspace}/${project.slug}/notes`,
|
||||
}))}
|
||||
selected={labels.selected}
|
||||
locale={locale}
|
||||
/>
|
||||
)}
|
||||
</Page>
|
||||
)
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user