Basis: Certvia dev@a48c5fb als Fundament für Craftvia
Unveränderter Stand von certvia/dev (a48c5fb) plus Craftvia-Spezifikation und Brandbook unter docs/craftvia/. ISMS-Module werden im Folgecommit entfernt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
import Link from "next/link";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import type { Asset, AssetType } from "@prisma/client";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { SegmentedRating } from "@/components/segmented-rating";
|
||||
|
||||
const ASSET_TYPES = ["INFORMATION", "SYSTEM", "APPLICATION", "LOCATION", "SUPPLIER", "IT_SERVICE", "PERSON", "DATA"] as const;
|
||||
const ASSET_STATUS = ["ACTIVE", "PLANNED", "RETIRED"] as const;
|
||||
|
||||
/** Formular für Anlegen/Bearbeiten eines Assets (Server-Action wird übergeben). */
|
||||
export async function AssetForm({
|
||||
action,
|
||||
asset,
|
||||
users,
|
||||
cancelHref,
|
||||
defaultType = "SYSTEM",
|
||||
}: {
|
||||
action: (formData: FormData) => Promise<void>;
|
||||
asset?: Asset;
|
||||
users: { id: string; name: string }[];
|
||||
cancelHref: string;
|
||||
/** Vorbelegter Typ beim Anlegen (z. B. INFORMATION im BIA-Popup Schritt 1). */
|
||||
defaultType?: AssetType;
|
||||
}) {
|
||||
const t = await getTranslations("assets");
|
||||
const tType = await getTranslations("assetType");
|
||||
const tStatus = await getTranslations("assetStatus");
|
||||
const tLevel = await getTranslations("protectionLevel");
|
||||
const tc = await getTranslations("common");
|
||||
|
||||
const selectClass =
|
||||
"h-9 w-full rounded-md border border-input bg-transparent px-3 text-sm";
|
||||
|
||||
return (
|
||||
<form action={action} className="max-w-2xl space-y-4">
|
||||
<div>
|
||||
<Label htmlFor="name">{t("name")}</Label>
|
||||
<Input id="name" name="name" required defaultValue={asset?.name} className="mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="description">{t("description")}</Label>
|
||||
<Textarea
|
||||
id="description"
|
||||
name="description"
|
||||
rows={3}
|
||||
defaultValue={asset?.description ?? ""}
|
||||
className="mt-1"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label htmlFor="type">{t("type")}</Label>
|
||||
<select id="type" name="type" defaultValue={asset?.type ?? defaultType} className={`${selectClass} mt-1`}>
|
||||
{ASSET_TYPES.map((v) => (
|
||||
<option key={v} value={v}>
|
||||
{tType(v)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="status">{t("status")}</Label>
|
||||
<select id="status" name="status" defaultValue={asset?.status ?? "ACTIVE"} className={`${selectClass} mt-1`}>
|
||||
{ASSET_STATUS.map((v) => (
|
||||
<option key={v} value={v}>
|
||||
{tStatus(v)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="ownerId">{t("owner")}</Label>
|
||||
<select id="ownerId" name="ownerId" defaultValue={asset?.ownerId ?? ""} className={`${selectClass} mt-1`}>
|
||||
<option value="">{tc("none")}</option>
|
||||
{users.map((u) => (
|
||||
<option key={u.id} value={u.id}>
|
||||
{u.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="location">{t("location")}</Label>
|
||||
<Input id="location" name="location" defaultValue={asset?.location ?? ""} className="mt-1" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<fieldset>
|
||||
<legend className="text-sm font-medium">{t("protection")} (1–4)</legend>
|
||||
<div className="mt-2 grid grid-cols-3 gap-4">
|
||||
{(
|
||||
[
|
||||
["confidentiality", t("confidentiality"), asset?.confidentiality],
|
||||
["integrity", t("integrity"), asset?.integrity],
|
||||
["availability", t("availability"), asset?.availability],
|
||||
] as const
|
||||
).map(([name, label, value]) => (
|
||||
<div key={name}>
|
||||
<Label>{label}</Label>
|
||||
<div className="mt-1">
|
||||
<SegmentedRating
|
||||
name={name}
|
||||
defaultValue={value ?? 1}
|
||||
low={tLevel("1")}
|
||||
high={tLevel("4")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="tags">{t("tags")}</Label>
|
||||
<Input id="tags" name="tags" defaultValue={asset?.tags.join(", ") ?? ""} className="mt-1" />
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 pt-2">
|
||||
<Button type="submit">{tc("save")}</Button>
|
||||
<Button variant="outline" nativeButton={false} render={<Link href={cancelHref} />}>
|
||||
{tc("cancel")}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user