feat(housekeeping): complete content events workflow

This commit is contained in:
Simo committed 2026-09-01 21:08:26 +02:00
1 parent f2e67ebace
commit d6235c37a1
42 files changed
+2868 -307

No files matched your search

@@ -1,6 +1,6 @@
"use client";
import { useActionState } from "react";
import { useActionState, useId } from "react";
import type { HousekeepingResult } from "../../../foundation/contracts";
export interface ContentCommandField {
@@ -12,6 +12,7 @@ export interface ContentCommandField {
| "text"
| "textarea"
| "number"
| "datetime-local"
| "checkbox"
| "select"
| "file";
@@ -21,6 +22,7 @@ export interface ContentCommandField {
readonly maxLength?: number;
readonly defaultValue?: string | number | boolean;
readonly allowUnchanged?: boolean;
readonly emptyValue?: null | "";
readonly options?: readonly Readonly<{
value: string | number;
label: string;
@@ -39,6 +41,20 @@ interface ContentCommandFormProps extends ContentCommandSubmission {
}
const OMIT_FIELD = Symbol("omit optional Content command field");
function optionalEmptyValue(field: ContentCommandField): unknown {
return field.emptyValue === null || field.emptyValue === ""
? field.emptyValue
: OMIT_FIELD;
}
function normalizeUtcDateTimeLocal(value: string): string {
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/u.test(value)) return value;
const iso = `${value}:00.000Z`;
const parsed = new Date(iso);
return Number.isFinite(parsed.getTime()) && parsed.toISOString() === iso
? iso
: value;
}
function parseField(field: ContentCommandField, formData: FormData): unknown {
const rawValue = formData.get(field.name);
@@ -51,12 +67,12 @@ function parseField(field: ContentCommandField, formData: FormData): unknown {
}
return rawValue === "on";
}
if (rawValue === null && !field.required) return OMIT_FIELD;
if (rawValue === null && !field.required) return optionalEmptyValue(field);
if (field.type === "file") return rawValue instanceof File ? rawValue : null;
const raw = String(rawValue ?? "")
.normalize("NFC")
.trim();
if (!raw && !field.required) return OMIT_FIELD;
if (!raw && !field.required) return optionalEmptyValue(field);
if (field.type === "number") {
if (!raw) return raw;
const value = Number(raw);
@@ -69,6 +85,7 @@ function parseField(field: ContentCommandField, formData: FormData): unknown {
)?.value;
return selected ?? raw.slice(0, 500);
}
if (field.type === "datetime-local") return normalizeUtcDateTimeLocal(raw);
if (field.type === "json") {
try {
return JSON.parse(raw.slice(0, field.maxLength ?? 20_000));
@@ -118,6 +135,7 @@ export function ContentCommandForm({
fields = [],
requiresReason = false,
}: ContentCommandFormProps) {
const formId = useId();
const [result, submit, pending] = useActionState(
submitContentCommandForm.bind(null, {
commandId,
@@ -136,14 +154,14 @@ export function ContentCommandForm({
{fields.map((field) => (
<label
key={field.name}
htmlFor={`${commandId}-${field.name}`}
htmlFor={`${formId}-${field.name}`}
className="block text-sm"
>
{field.type === "checkbox" && field.allowUnchanged ? (
<>
{field.label}
<select
id={`${commandId}-${field.name}`}
id={`${formId}-${field.name}`}
name={field.name}
defaultValue=""
className="mt-1 block w-full"
@@ -156,7 +174,7 @@ export function ContentCommandForm({
) : field.type === "checkbox" ? (
<>
<input
id={`${commandId}-${field.name}`}
id={`${formId}-${field.name}`}
name={field.name}
type="checkbox"
defaultChecked={field.defaultValue === true}
@@ -167,7 +185,7 @@ export function ContentCommandForm({
<>
{field.label}
<select
id={`${commandId}-${field.name}`}
id={`${formId}-${field.name}`}
name={field.name}
defaultValue={
typeof field.defaultValue === "boolean"
@@ -189,7 +207,7 @@ export function ContentCommandForm({
<>
{field.label}
<textarea
id={`${commandId}-${field.name}`}
id={`${formId}-${field.name}`}
name={field.name}
defaultValue={
typeof field.defaultValue === "boolean"
@@ -205,14 +223,16 @@ export function ContentCommandForm({
<>
{field.label}
<input
id={`${commandId}-${field.name}`}
id={`${formId}-${field.name}`}
name={field.name}
type={
field.type === "number"
? "number"
: field.type === "file"
? "file"
: "text"
: field.type === "datetime-local"
? "datetime-local"
: field.type === "file"
? "file"
: "text"
}
defaultValue={
field.type === "file" ||
@@ -231,10 +251,10 @@ export function ContentCommandForm({
</label>
))}
{requiresReason ? (
<label htmlFor={`${commandId}-reason`} className="block text-sm">
<label htmlFor={`${formId}-reason`} className="block text-sm">
Reason
<textarea
id={`${commandId}-reason`}
id={`${formId}-reason`}
name="reason"
required
maxLength={1000}