L1 Stammdaten: Backoffice-Seiten Kunden, Objekte, Teams und Dokumente
Listen mit Suche/Filter/Paginierung, Popups für Anlage und Bearbeitung, Kundendetail mit Tabs, Dublettenhinweis und Zusammenführen, Objektdetail mit Kartenlink, Dokumenten-Tab und Historie, Teamverwaltung mit Mitgliedern, Dokumentenübersicht. Texte in messages de/en, Audit-Label Ansprechpartner. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { controlClass, Field, primaryButtonClass } from "@/components/customers/form-ui";
|
||||
|
||||
/**
|
||||
* Plain multipart form (works without JavaScript) posting to /documents/upload, which redirects
|
||||
* back to `returnTo` with ?docOk=1 or ?docError=<reason> (see UploadFeedback).
|
||||
*/
|
||||
export async function DocumentUploadForm({
|
||||
returnTo,
|
||||
links = {},
|
||||
lineageId,
|
||||
categories,
|
||||
visibilities,
|
||||
defaultCategory = "other",
|
||||
defaultVisibility = "team",
|
||||
heading,
|
||||
}: {
|
||||
returnTo: string;
|
||||
links?: { customerId?: string; siteId?: string; workOrderId?: string };
|
||||
lineageId?: string;
|
||||
categories: string[];
|
||||
visibilities: string[];
|
||||
defaultCategory?: string;
|
||||
defaultVisibility?: string;
|
||||
heading?: string;
|
||||
}) {
|
||||
const t = await getTranslations("documents");
|
||||
const idp = lineageId ? `v-${lineageId.slice(0, 6)}` : "up";
|
||||
return (
|
||||
<form action="/documents/upload" method="post" encType="multipart/form-data" className="grid gap-3 sm:grid-cols-2">
|
||||
{heading && <p className="font-heading text-sm font-semibold sm:col-span-2">{heading}</p>}
|
||||
<input type="hidden" name="returnTo" value={returnTo} />
|
||||
{links.customerId && <input type="hidden" name="customerId" value={links.customerId} />}
|
||||
{links.siteId && <input type="hidden" name="siteId" value={links.siteId} />}
|
||||
{links.workOrderId && <input type="hidden" name="workOrderId" value={links.workOrderId} />}
|
||||
{lineageId && <input type="hidden" name="lineageId" value={lineageId} />}
|
||||
<Field id={`${idp}-file`} label={t("upload.file")} required hint={t("upload.fileHint")} className="sm:col-span-2">
|
||||
<input
|
||||
id={`${idp}-file`}
|
||||
name="file"
|
||||
type="file"
|
||||
required
|
||||
accept="application/pdf,image/jpeg,image/png,image/webp,image/heic,audio/*"
|
||||
className={`${controlClass} py-2 file:mr-3 file:font-semibold`}
|
||||
/>
|
||||
</Field>
|
||||
<Field id={`${idp}-title`} label={t("upload.titleField")} className="sm:col-span-2">
|
||||
<input id={`${idp}-title`} name="title" className={controlClass} />
|
||||
</Field>
|
||||
<Field id={`${idp}-category`} label={t("upload.category")} required>
|
||||
<select id={`${idp}-category`} name="category" defaultValue={defaultCategory} className={controlClass}>
|
||||
{categories.map((c) => (
|
||||
<option key={c} value={c}>
|
||||
{t(`category.${c}`)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</Field>
|
||||
<Field id={`${idp}-visibility`} label={t("upload.visibility")} required>
|
||||
<select id={`${idp}-visibility`} name="visibility" defaultValue={defaultVisibility} className={controlClass}>
|
||||
{visibilities.map((v) => (
|
||||
<option key={v} value={v}>
|
||||
{t(`visibility.${v}`)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</Field>
|
||||
<div className="sm:col-span-2">
|
||||
<button type="submit" className={primaryButtonClass}>
|
||||
{t("upload.submit")}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
/** Result banner of the upload redirect. */
|
||||
export async function UploadFeedback({ ok, error }: { ok?: string; error?: string }) {
|
||||
if (!ok && !error) return null;
|
||||
const t = await getTranslations("documents");
|
||||
if (ok) {
|
||||
return (
|
||||
<p role="status" className="mb-3 rounded-lg border-l-4 border-[var(--ok)] bg-card px-3 py-2 text-[13px] font-semibold text-[var(--ok)]">
|
||||
{t("upload.ok")}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
const key = t.has(`uploadErrors.${error}`) ? `uploadErrors.${error}` : "uploadErrors.generic";
|
||||
return (
|
||||
<p role="alert" className="mb-3 rounded-lg border-l-4 border-[var(--risk)] bg-card px-3 py-2 text-[13px] font-semibold text-[var(--risk)]">
|
||||
{t(key)}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user