Files
Epicnabbo-Catalogus-Updated…/src/features/housekeeping/domains/people/pages/staff.tsx
T

163 lines
4.6 KiB
TypeScript

import { PERMS } from "@/lib/permission-slugs";
import {
createCorrelationId,
fail,
type HousekeepingCapabilityContext,
type HousekeepingResult,
} from "../../../foundation/contracts";
import type { HousekeepingPageInput } from "../../../route-handlers";
import { type PeopleStaffQueryData, peopleStaffQuery } from "../queries/staff";
import { PeoplePageFrame, parsePeopleListInput } from "./page-state";
import { PeopleCommandForm } from "./people-command-form";
interface PeopleStaffPageProps {
readonly context: HousekeepingCapabilityContext;
readonly result?: HousekeepingResult<PeopleStaffQueryData>;
}
export function PeopleStaffPage({ context, result }: PeopleStaffPageProps) {
return (
<PeoplePageFrame
title="Staff"
description="Review staff applications and team definitions."
result={result}
isEmpty={(data) => data.page.items.length === 0}
>
{(data) => (
<div className="space-y-3">
<form method="get" className="flex gap-2">
<input
name="search"
maxLength={100}
aria-label="Search staff workflow"
/>
<button type="submit">Search</button>
</form>
{data.kind === "applications" ? (
<ul className="space-y-2">
{data.page.items.map((application) => (
<li
key={application.id}
className="space-y-3 rounded-lg border border-[var(--admin-border)] bg-[var(--admin-surface)] p-4"
>
<h2>
{application.username ?? `User #${application.userId}`}
</h2>
<p>{application.content}</p>
{context.has(PERMS.USERS_EDIT) ? (
<PeopleCommandForm
commandId="people.application.decide"
buttonLabel="Dismiss application"
input={{
applicationId: application.id,
decision: "dismiss",
}}
requiresReason
/>
) : null}
</li>
))}
</ul>
) : data.kind === "teams" ? (
<div className="space-y-3">
{context.has(PERMS.USERS_EDIT) ? (
<PeopleCommandForm
commandId="people.team.change"
buttonLabel="Create team"
input={{ action: "create" }}
fields={[
{
name: "rankName",
label: "Team name",
type: "text",
maxLength: 255,
required: true,
},
{
name: "badge",
label: "Badge",
type: "text",
maxLength: 255,
},
{
name: "jobDescription",
label: "Job description",
type: "text",
maxLength: 255,
},
{
name: "staffColor",
label: "Staff color",
type: "text",
maxLength: 255,
defaultValue: "#327fa8",
},
{
name: "hiddenRank",
label: "Hidden rank",
type: "checkbox",
},
]}
requiresReason
/>
) : null}
<ul className="space-y-2">
{data.page.items.map((team) => (
<li
key={team.id}
className="space-y-3 rounded-lg border border-[var(--admin-border)] bg-[var(--admin-surface)] p-4"
>
<h2>{team.name}</h2>
<p>{team.jobDescription ?? "No job description"}</p>
{context.has(PERMS.USERS_EDIT) ? (
<PeopleCommandForm
commandId="people.team.change"
buttonLabel="Delete team"
input={{ action: "delete", teamId: team.id }}
requiresReason
/>
) : null}
</li>
))}
</ul>
</div>
) : data.kind === "moderation-team" ? (
<ul className="space-y-2">
{data.page.items.map((member) => (
<li
key={member.id}
className="rounded-lg border border-[var(--admin-border)] bg-[var(--admin-surface)] p-4"
>
<a href={member.href}>{member.username}</a>
<p>
{member.openCfh} CFH · {member.openTickets} tickets ·{" "}
{member.actionCount} actions
</p>
</li>
))}
</ul>
) : null}
</div>
)}
</PeoplePageFrame>
);
}
export async function renderPeopleStaffPage(input: HousekeepingPageInput) {
const routeId = input.match.routeId;
const result =
routeId === "people.staff.applications" ||
routeId === "people.staff.teams" ||
routeId === "people.staff.moderation-team"
? await peopleStaffQuery.run(input.context, {
routeId,
list: parsePeopleListInput(input.searchParams ?? {}),
})
: fail(
"NOT_FOUND",
"errors.housekeeping.notFound",
createCorrelationId(),
);
return <PeopleStaffPage context={input.context} result={result} />;
}