L5 Berichte & Unterschrift: Backoffice- und Mobil-Oberflächen
/reports mit Filtern (zur Prüfung zuerst), /reports/[id] mit Aktionen, Versionen und PDF-Link; mobile Komponenten für Bericht, Prüfung und Unterschrift inkl. Signature-Pad; Texte de/en. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getFormatter, getTranslations } from "next-intl/server";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
import { REPORT_EDITABLE, type ReportStatus } from "@/lib/reports/content";
|
||||
import { can, ServiceError } from "@/server/services/context";
|
||||
import { getMobileReportState } from "@/server/services/reports/queries";
|
||||
import { readCtx } from "@/server/services/reports/read-ctx";
|
||||
import { ReportStatusBadge } from "../status-badge";
|
||||
import { StepIndicator } from "../step-indicator";
|
||||
import { SignFlow } from "./sign-flow";
|
||||
|
||||
/** /m/orders/[id]/sign — signature or documented reason, then submit the completion report. */
|
||||
export async function SignScreen({ workOrderId }: { workOrderId: string }) {
|
||||
const t = await getTranslations("reports");
|
||||
const format = await getFormatter();
|
||||
const ctx = await readCtx();
|
||||
let state: Awaited<ReturnType<typeof getMobileReportState>>;
|
||||
try {
|
||||
state = await getMobileReportState(ctx, workOrderId, "completion");
|
||||
} catch (err) {
|
||||
if (err instanceof ServiceError && err.code === "not_found") notFound();
|
||||
throw err;
|
||||
}
|
||||
const { workOrder, report, content, timeZone } = state;
|
||||
const base = `/m/orders/${workOrderId}`;
|
||||
const steps = [t("mobile.stepReview"), t("mobile.stepEdit"), t("mobile.stepSign"), t("mobile.stepSubmit")];
|
||||
|
||||
return (
|
||||
<main className="mx-auto w-full max-w-2xl flex-1 space-y-4 p-4">
|
||||
<Link href={`${base}/report?type=completion`} className="inline-flex min-h-11 items-center gap-1.5 text-[13px] font-semibold text-muted-foreground hover:text-foreground">
|
||||
<ArrowLeft className="size-4" aria-hidden />
|
||||
{t("mobile.review")}
|
||||
</Link>
|
||||
<header>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{workOrder.number} · {workOrder.title}
|
||||
</p>
|
||||
<h1 className="text-[22px]">{t("sign.title")}</h1>
|
||||
</header>
|
||||
|
||||
{!report || !content ? (
|
||||
<p className="shadow-card rounded-xl border bg-card p-4 text-[14px]">
|
||||
{t("sign.noReport")}{" "}
|
||||
<Link href={`${base}/report?type=completion`} className="font-semibold underline">
|
||||
{t("mobile.createCompletion")}
|
||||
</Link>
|
||||
</p>
|
||||
) : (
|
||||
<>
|
||||
<StepIndicator steps={steps} current={3} label={t("mobile.stepOf", { current: 3, total: steps.length })} />
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<ReportStatusBadge status={report.status as ReportStatus} label={t(`status.${report.status}`)} />
|
||||
<span className="text-[13px] text-muted-foreground">
|
||||
{content.reportNumber} · {t("field.version")} {report.version}
|
||||
</span>
|
||||
</div>
|
||||
<SignFlow
|
||||
reportId={report.id}
|
||||
reportNumber={content.reportNumber}
|
||||
orderNumber={workOrder.number}
|
||||
dateLabel={format.dateTime(new Date(), { dateStyle: "medium", timeZone })}
|
||||
canNotRequired={!workOrder.signatureRequired || can(ctx, "report:approve")}
|
||||
existing={content.signature ? { outcome: content.signature.outcome, signerName: content.signature.signerName, reason: content.signature.reason } : null}
|
||||
editable={REPORT_EDITABLE.includes(report.status as ReportStatus)}
|
||||
doneHref={`${base}/report?type=completion`}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user