Mejora en los cuadros de diálogo

This commit is contained in:
David Arranz 2026-10-09 20:30:41 +02:00
parent 91988a26b9
commit 791fa06ddd
48 changed files with 2681 additions and 2488 deletions

View File

@ -285,3 +285,13 @@ contrae el menú y conservan la selección y las ayudas del botón de navegació
Componentes de @repo/rdx-ui independientes de los DTO, consultas y endpoints de Metis Desk. FieldSections recibe secciones, elementos con sectionId opcional y renderItem; presenta una cuadrícula plana cuando no hay secciones, o bloques desplegables accesibles con descripción y «Sin sección». Admite presentación de valores como listas de descripción y apertura ante errores externos. Componentes de @repo/rdx-ui independientes de los DTO, consultas y endpoints de Metis Desk. FieldSections recibe secciones, elementos con sectionId opcional y renderItem; presenta una cuadrícula plana cuando no hay secciones, o bloques desplegables accesibles con descripción y «Sin sección». Admite presentación de valores como listas de descripción y apertura ante errores externos.
TemplateSectionsEditor recibe secciones, referencias de campos, nombres, textos traducidos, generador de identidad y onChange. Permite ordenar secciones/campos, editar nombre/descripción, asignar campos y mantener su obligatoriedad mediante SwitchField. El módulo consumidor conserva el estado en React Hook Form y genera UUID v7. Los helpers removeTemplateSection y copyTemplateLayout conservan referencias y remapean identidades respectivamente. No se modifican los componentes vendor. TemplateSectionsEditor recibe secciones, referencias de campos, nombres, textos traducidos, generador de identidad y onChange. Permite ordenar secciones/campos, editar nombre/descripción, asignar campos y mantener su obligatoriedad mediante SwitchField. El módulo consumidor conserva el estado en React Hook Form y genera UUID v7. Los helpers removeTemplateSection y copyTemplateLayout conservan referencias y remapean identidades respectivamente. No se modifican los componentes vendor.
## Formularios modales adaptativos
`FormDialog` admite `size="small" | "medium" | "large"`. Los formularios sencillos mantienen el tamaño pequeño; personas y usuarios emplean el intermedio. `wide` sigue siendo un alias del tamaño grande: 90 % del ancho disponible, con máximo de 1.200 px y márgenes en móvil. No se ensanchan automáticamente las altas sencillas.
La cabecera permanece visible. Un formulario modal utiliza una raíz flex con altura mínima cero, `FormBody` para campos, errores y ayuda, y `FormActions` como hermano final fuera del cuerpo desplazable. Los botones permanecen dentro del formulario HTML original: se conservan envío por teclado, validación nativa, estados pendientes y cancelación. La ayuda queda al final del cuerpo, inmediatamente antes de las acciones. Los diálogos de consulta conservan el cuerpo desplazable y su pie externo.
`FormSection` agrupa datos relacionados mediante título accesible y un borde discreto. Activos, recursos y plantillas lo utilizan para sus datos principales. Las características y las secciones del editor se distribuyen en dos bloques por fila cuando el contenedor dispone de al menos 900 px; los controles dentro de cada bloque mantienen una o dos columnas según el espacio. Una plantilla sin secciones puede aprovechar hasta tres columnas de características. Observaciones y ayuda ocupan toda la fila. La disposición responde al espacio del modal, no solo al ancho del navegador.
El desplazamiento vertical se conserva cuando el contenido supera la altura disponible; no se ocultan campos ni se reduce artificialmente su tamaño para eliminarlo. No se introducen pestañas ni páginas nuevas sin una necesidad específica del formulario.

View File

@ -299,5 +299,9 @@
"assign": "Section", "assign": "Section",
"help": "Organize fields without changing their values or requirements. Removing a section moves its fields to Unsectioned.", "help": "Organize fields without changing their values or requirements. Removing a section moves its fields to Unsectioned.",
"empty": "This section has no fields yet." "empty": "This section has no fields yet."
},
"formLayout": {
"identity": "Identification and assignment",
"template": "Template details"
} }
} }

View File

@ -299,5 +299,9 @@
"assign": "Sección", "assign": "Sección",
"help": "Organiza los campos sin cambiar sus valores ni su obligatoriedad. Al eliminar una sección, sus campos pasan a «Sin sección».", "help": "Organiza los campos sin cambiar sus valores ni su obligatoriedad. Al eliminar una sección, sus campos pasan a «Sin sección».",
"empty": "Esta sección aún no tiene campos." "empty": "Esta sección aún no tiene campos."
},
"formLayout": {
"identity": "Identificación y asignación",
"template": "Datos de la plantilla"
} }
} }

View File

@ -1,4 +1,4 @@
import { FormActions, FormDialog } from "@repo/rdx-ui/components/form-dialog"; import { FormActions, FormBody, FormDialog } from "@repo/rdx-ui/components/form-dialog";
import { Button } from "@repo/shadcn-ui/components/button"; import { Button } from "@repo/shadcn-ui/components/button";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@ -22,11 +22,13 @@ export function EndRelationshipDialog(props: EndRelationshipDialogProps) {
open open
title={t("endRelationship")} title={t("endRelationship")}
> >
<FormBody>
<p className="text-sm"> <p className="text-sm">
{relationship.values.name} · {sourceName} → {targetName} {relationship.values.name} · {sourceName} → {targetName}
</p> </p>
<p className="text-sm text-muted-foreground">{t("endRelationshipConfirm")}</p> <p className="text-sm text-muted-foreground">{t("endRelationshipConfirm")}</p>
<InventoryFeedback error={mutation.error} /> <InventoryFeedback error={mutation.error} />
</FormBody>
<FormActions> <FormActions>
<Button <Button
disabled={mutation.isPending} disabled={mutation.isPending}

View File

@ -1,5 +1,5 @@
import { ContextHelp } from "@repo/rdx-ui/components/context-help"; import { ContextHelp } from "@repo/rdx-ui/components/context-help";
import { FormActions } from "@repo/rdx-ui/components/form-dialog"; import { FormActions, FormBody } from "@repo/rdx-ui/components/form-dialog";
import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action"; import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action";
import { Button } from "@repo/shadcn-ui/components/button"; import { Button } from "@repo/shadcn-ui/components/button";
import { Label } from "@repo/shadcn-ui/components/label"; import { Label } from "@repo/shadcn-ui/components/label";
@ -34,7 +34,8 @@ export function RelationshipForm(props: RelationshipFormProps) {
const id = useId(); const id = useId();
return ( return (
<> <>
<form className="space-y-4" onSubmit={submit}> <form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
<FormBody>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor={`${id}-type`}>{t("relationshipType")}</Label> <Label htmlFor={`${id}-type`}>{t("relationshipType")}</Label>
<SelectWithAction <SelectWithAction
@ -43,7 +44,10 @@ export function RelationshipForm(props: RelationshipFormProps) {
id={`${id}-type`} id={`${id}-type`}
label={t("relationshipType")} label={t("relationshipType")}
onValueChange={setTypeId} onValueChange={setTypeId}
options={relationshipTypes.map((item) => ({ value: item.id, label: item.values.name }))} options={relationshipTypes.map((item) => ({
value: item.id,
label: item.values.name,
}))}
placeholder={t("chooseRelation")} placeholder={t("chooseRelation")}
required required
value={typeId} value={typeId}
@ -79,6 +83,7 @@ export function RelationshipForm(props: RelationshipFormProps) {
guidance={t("relationshipGuidance")} guidance={t("relationshipGuidance")}
title={t("helpTitle")} title={t("helpTitle")}
/> />
</FormBody>
<FormActions> <FormActions>
<Button disabled={!(typeId && targetId) || mutation.isPending} type="submit"> <Button disabled={!(typeId && targetId) || mutation.isPending} type="submit">
{t(mutation.isPending ? "saving" : "save")} {t(mutation.isPending ? "saving" : "save")}

View File

@ -1,6 +1,7 @@
import { ContextHelp } from "@repo/rdx-ui/components/context-help"; import { ContextHelp } from "@repo/rdx-ui/components/context-help";
import { FieldSections } from "@repo/rdx-ui/components/field-sections"; import { FieldSections } from "@repo/rdx-ui/components/field-sections";
import { FormActions } from "@repo/rdx-ui/components/form-dialog"; import { FormActions, FormBody } from "@repo/rdx-ui/components/form-dialog";
import { FormSection } from "@repo/rdx-ui/components/form-section";
import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action"; import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action";
import { Button } from "@repo/shadcn-ui/components/button"; import { Button } from "@repo/shadcn-ui/components/button";
import { Input } from "@repo/shadcn-ui/components/input"; import { Input } from "@repo/shadcn-ui/components/input";
@ -43,8 +44,10 @@ export function AssetForm(props: AssetFormProps) {
const selectProps = { disabled: mutation.isPending, placeholder: t("noAssignment") }; const selectProps = { disabled: mutation.isPending, placeholder: t("noAssignment") };
return ( return (
<> <>
<form className="space-y-5" onSubmit={submit}> <form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
<FormBody>
<fieldset className="space-y-5" disabled={mutation.isPending}> <fieldset className="space-y-5" disabled={mutation.isPending}>
<FormSection title={t("formLayout.identity")}>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor={`${prefix}-name`}>{t("name")}</Label> <Label htmlFor={`${prefix}-name`}>{t("name")}</Label>
<Input id={`${prefix}-name`} maxLength={100} required {...form.register("name")} /> <Input id={`${prefix}-name`} maxLength={100} required {...form.register("name")} />
@ -64,7 +67,12 @@ export function AssetForm(props: AssetFormProps) {
onValueChange={selectTemplate} onValueChange={selectTemplate}
options={ options={
asset asset
? [{ value: asset.values.templateId, label: asset.specification.templateName }] ? [
{
value: asset.values.templateId,
label: asset.specification.templateName,
},
]
: templates.map((item) => ({ value: item.id, label: item.values.name })) : templates.map((item) => ({ value: item.id, label: item.values.name }))
} }
placeholder={t("chooseValue")} placeholder={t("chooseValue")}
@ -130,6 +138,7 @@ export function AssetForm(props: AssetFormProps) {
/> />
</div> </div>
</div> </div>
</FormSection>
{fields.length > 0 && ( {fields.length > 0 && (
<section className="space-y-3"> <section className="space-y-3">
<h3 className="font-medium">{t("characteristics")}</h3> <h3 className="font-medium">{t("characteristics")}</h3>
@ -197,6 +206,7 @@ export function AssetForm(props: AssetFormProps) {
scopeNote={t("assetScope")} scopeNote={t("assetScope")}
title={t("helpTitle")} title={t("helpTitle")}
/> />
</FormBody>
<FormActions> <FormActions>
<Button disabled={mutation.isPending} type="submit"> <Button disabled={mutation.isPending} type="submit">
{t(mutation.isPending ? "saving" : "save")} {t(mutation.isPending ? "saving" : "save")}

View File

@ -1,5 +1,5 @@
import { ContextHelp } from "@repo/rdx-ui/components/context-help"; import { ContextHelp } from "@repo/rdx-ui/components/context-help";
import { FormActions } from "@repo/rdx-ui/components/form-dialog"; import { FormActions, FormBody } from "@repo/rdx-ui/components/form-dialog";
import { SwitchField } from "@repo/rdx-ui/components/switch-field"; import { SwitchField } from "@repo/rdx-ui/components/switch-field";
import { Button } from "@repo/shadcn-ui/components/button"; import { Button } from "@repo/shadcn-ui/components/button";
import { Input } from "@repo/shadcn-ui/components/input"; import { Input } from "@repo/shadcn-ui/components/input";
@ -29,7 +29,8 @@ export function BrandForm(props: BrandFormProps) {
const { t } = useTranslation("assets"); const { t } = useTranslation("assets");
const id = useId(); const id = useId();
return ( return (
<form className="space-y-5" onSubmit={submit}> <form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
<FormBody>
<fieldset className="space-y-4" disabled={mutation.isPending || isReadingLogo}> <fieldset className="space-y-4" disabled={mutation.isPending || isReadingLogo}>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor={id}>{t("name")}</Label> <Label htmlFor={id}>{t("name")}</Label>
@ -77,6 +78,7 @@ export function BrandForm(props: BrandFormProps) {
scopeNote={t("globalHelp")} scopeNote={t("globalHelp")}
title={t("helpTitle")} title={t("helpTitle")}
/> />
</FormBody>
<FormActions> <FormActions>
<Button disabled={mutation.isPending || isReadingLogo} type="submit"> <Button disabled={mutation.isPending || isReadingLogo} type="submit">
{t(mutation.isPending ? "saving" : "save")} {t(mutation.isPending ? "saving" : "save")}

View File

@ -1,4 +1,4 @@
import { FormActions } from "@repo/rdx-ui/components/form-dialog"; import { FormActions, FormBody } from "@repo/rdx-ui/components/form-dialog";
import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action"; import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action";
import { Button } from "@repo/shadcn-ui/components/button"; import { Button } from "@repo/shadcn-ui/components/button";
import { Input } from "@repo/shadcn-ui/components/input"; import { Input } from "@repo/shadcn-ui/components/input";
@ -19,7 +19,8 @@ export function CatalogFieldForm(props: CatalogFieldFormProps) {
const { t } = useTranslation("assets"); const { t } = useTranslation("assets");
const id = useId(); const id = useId();
return ( return (
<form className="space-y-4" onSubmit={submit}> <form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
<FormBody>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor={`${id}-name`}>{t("name")}</Label> <Label htmlFor={`${id}-name`}>{t("name")}</Label>
<Input id={`${id}-name`} maxLength={100} required {...form.register("name")} /> <Input id={`${id}-name`} maxLength={100} required {...form.register("name")} />
@ -67,6 +68,7 @@ export function CatalogFieldForm(props: CatalogFieldFormProps) {
invalid={Object.keys(form.formState.errors).length > 0} invalid={Object.keys(form.formState.errors).length > 0}
/> />
<CatalogHelp kind="field" scope={scope} /> <CatalogHelp kind="field" scope={scope} />
</FormBody>
<FormActions> <FormActions>
<Button disabled={mutation.isPending} type="submit"> <Button disabled={mutation.isPending} type="submit">
{t(mutation.isPending ? "saving" : "save")} {t(mutation.isPending ? "saving" : "save")}

View File

@ -1,4 +1,4 @@
import { FormActions } from "@repo/rdx-ui/components/form-dialog"; import { FormActions, FormBody } from "@repo/rdx-ui/components/form-dialog";
import { SwitchField } from "@repo/rdx-ui/components/switch-field"; import { SwitchField } from "@repo/rdx-ui/components/switch-field";
import { Button } from "@repo/shadcn-ui/components/button"; import { Button } from "@repo/shadcn-ui/components/button";
import { Input } from "@repo/shadcn-ui/components/input"; import { Input } from "@repo/shadcn-ui/components/input";
@ -18,7 +18,8 @@ export function CatalogRelationForm(props: CatalogRelationFormProps) {
const { t } = useTranslation("assets"); const { t } = useTranslation("assets");
const id = useId(); const id = useId();
return ( return (
<form className="space-y-4" onSubmit={submit}> <form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
<FormBody>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor={id}>{t("name")}</Label> <Label htmlFor={id}>{t("name")}</Label>
<Input <Input
@ -49,7 +50,12 @@ export function CatalogRelationForm(props: CatalogRelationFormProps) {
{!symmetric && ( {!symmetric && (
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor={`${id}-inverse`}>{t("inverseName")}</Label> <Label htmlFor={`${id}-inverse`}>{t("inverseName")}</Label>
<Input id={`${id}-inverse`} maxLength={100} required {...form.register("inverseName")} /> <Input
id={`${id}-inverse`}
maxLength={100}
required
{...form.register("inverseName")}
/>
</div> </div>
)} )}
<div className="space-y-2"> <div className="space-y-2">
@ -61,6 +67,7 @@ export function CatalogRelationForm(props: CatalogRelationFormProps) {
invalid={Object.keys(form.formState.errors).length > 0} invalid={Object.keys(form.formState.errors).length > 0}
/> />
<CatalogHelp kind="relation" scope={scope} /> <CatalogHelp kind="relation" scope={scope} />
</FormBody>
<FormActions> <FormActions>
<Button disabled={mutation.isPending} type="submit"> <Button disabled={mutation.isPending} type="submit">
{t(mutation.isPending ? "saving" : "save")} {t(mutation.isPending ? "saving" : "save")}

View File

@ -1,5 +1,6 @@
import { UniqueID } from "@repo/rdx-ddd"; import { UniqueID } from "@repo/rdx-ddd";
import { FormActions } from "@repo/rdx-ui/components/form-dialog"; import { FormActions, FormBody } from "@repo/rdx-ui/components/form-dialog";
import { FormSection } from "@repo/rdx-ui/components/form-section";
import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action"; import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action";
import { TemplateSectionsEditor } from "@repo/rdx-ui/components/template-sections-editor"; import { TemplateSectionsEditor } from "@repo/rdx-ui/components/template-sections-editor";
import { Button } from "@repo/shadcn-ui/components/button"; import { Button } from "@repo/shadcn-ui/components/button";
@ -23,7 +24,9 @@ export function CatalogTemplateForm(props: CatalogTemplateFormProps) {
const id = useId(); const id = useId();
return ( return (
<> <>
<form className="space-y-4" onSubmit={submit}> <form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
<FormBody>
<FormSection title={t("formLayout.template")}>
<div className="grid gap-4 sm:grid-cols-2"> <div className="grid gap-4 sm:grid-cols-2">
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor={`${id}-name`}>{t("name")}</Label> <Label htmlFor={`${id}-name`}>{t("name")}</Label>
@ -39,6 +42,7 @@ export function CatalogTemplateForm(props: CatalogTemplateFormProps) {
/> />
</div> </div>
</div> </div>
</FormSection>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor={`${id}-field`}>{t("selectField")}</Label> <Label htmlFor={`${id}-field`}>{t("selectField")}</Label>
<SelectWithAction <SelectWithAction
@ -103,6 +107,7 @@ export function CatalogTemplateForm(props: CatalogTemplateFormProps) {
invalid={Object.keys(form.formState.errors).length > 0} invalid={Object.keys(form.formState.errors).length > 0}
/> />
<CatalogHelp kind="template" scope={scope} /> <CatalogHelp kind="template" scope={scope} />
</FormBody>
<FormActions> <FormActions>
<Button disabled={mutation.isPending} type="submit"> <Button disabled={mutation.isPending} type="submit">
{t(mutation.isPending ? "saving" : "save")} {t(mutation.isPending ? "saving" : "save")}

View File

@ -1,4 +1,4 @@
import { FormActions } from "@repo/rdx-ui/components/form-dialog"; import { FormActions, FormBody } from "@repo/rdx-ui/components/form-dialog";
import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action"; import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action";
import { SwitchField } from "@repo/rdx-ui/components/switch-field"; import { SwitchField } from "@repo/rdx-ui/components/switch-field";
import { Button } from "@repo/shadcn-ui/components/button"; import { Button } from "@repo/shadcn-ui/components/button";
@ -21,7 +21,8 @@ export function SupportForm(props: SupportFormProps) {
const prefix = useId(); const prefix = useId();
return ( return (
<> <>
<form className="space-y-5" onSubmit={submit}> <form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
<FormBody>
<fieldset className="space-y-4" disabled={mutation.isPending}> <fieldset className="space-y-4" disabled={mutation.isPending}>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor={prefix}>{t("support.tool")}</Label> <Label htmlFor={prefix}>{t("support.tool")}</Label>
@ -39,7 +40,11 @@ export function SupportForm(props: SupportFormProps) {
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor={`${prefix}-destination`}>{t("support.destination")}</Label> <Label htmlFor={`${prefix}-destination`}>{t("support.destination")}</Label>
<Input id={`${prefix}-destination`} maxLength={300} {...form.register("destination")} /> <Input
id={`${prefix}-destination`}
maxLength={300}
{...form.register("destination")}
/>
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor={`${prefix}-state`}>{t("support.state")}</Label> <Label htmlFor={`${prefix}-state`}>{t("support.state")}</Label>
@ -90,6 +95,7 @@ export function SupportForm(props: SupportFormProps) {
invalid={Object.keys(form.formState.errors).length > 0} invalid={Object.keys(form.formState.errors).length > 0}
/> />
<SupportHelp /> <SupportHelp />
</FormBody>
<FormActions> <FormActions>
<Button disabled={mutation.isPending} type="submit"> <Button disabled={mutation.isPending} type="submit">
{t(mutation.isPending ? "saving" : "save")} {t(mutation.isPending ? "saving" : "save")}

View File

@ -1,4 +1,4 @@
import { FormActions } from "@repo/rdx-ui/components/form-dialog"; import { FormActions, FormBody } from "@repo/rdx-ui/components/form-dialog";
import { SwitchField } from "@repo/rdx-ui/components/switch-field"; import { SwitchField } from "@repo/rdx-ui/components/switch-field";
import { Button } from "@repo/shadcn-ui/components/button"; import { Button } from "@repo/shadcn-ui/components/button";
import { Input } from "@repo/shadcn-ui/components/input"; import { Input } from "@repo/shadcn-ui/components/input";
@ -17,7 +17,8 @@ export function SupportToolForm(props: SupportToolFormProps) {
const { t } = useTranslation("assets"); const { t } = useTranslation("assets");
const id = useId(); const id = useId();
return ( return (
<form className="space-y-5" onSubmit={submit}> <form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
<FormBody>
<fieldset className="space-y-4" disabled={mutation.isPending}> <fieldset className="space-y-4" disabled={mutation.isPending}>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor={id}>{t("support.toolName")}</Label> <Label htmlFor={id}>{t("support.toolName")}</Label>
@ -41,6 +42,7 @@ export function SupportToolForm(props: SupportToolFormProps) {
invalid={Object.keys(form.formState.errors).length > 0} invalid={Object.keys(form.formState.errors).length > 0}
/> />
<SupportHelp tool /> <SupportHelp tool />
</FormBody>
<FormActions> <FormActions>
<Button disabled={mutation.isPending} type="submit"> <Button disabled={mutation.isPending} type="submit">
{t(mutation.isPending ? "saving" : "save")} {t(mutation.isPending ? "saving" : "save")}

View File

@ -1,5 +1,5 @@
import { ContextHelp } from "@repo/rdx-ui/components/context-help"; import { ContextHelp } from "@repo/rdx-ui/components/context-help";
import { FormActions } from "@repo/rdx-ui/components/form-dialog"; import { FormActions, FormBody } from "@repo/rdx-ui/components/form-dialog";
import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action"; import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action";
import { SwitchField } from "@repo/rdx-ui/components/switch-field"; import { SwitchField } from "@repo/rdx-ui/components/switch-field";
import { Button } from "@repo/shadcn-ui/components/button"; import { Button } from "@repo/shadcn-ui/components/button";
@ -36,7 +36,8 @@ export function WorkstationForm(props: WorkstationFormProps) {
const prefix = useId(); const prefix = useId();
return ( return (
<> <>
<form className="space-y-5" onSubmit={submit}> <form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
<FormBody>
<fieldset className="space-y-4" disabled={mutation.isPending}> <fieldset className="space-y-4" disabled={mutation.isPending}>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor={prefix}>{t("name")}</Label> <Label htmlFor={prefix}>{t("name")}</Label>
@ -173,6 +174,7 @@ export function WorkstationForm(props: WorkstationFormProps) {
scopeNote={t("workstationScope")} scopeNote={t("workstationScope")}
title={t("helpTitle")} title={t("helpTitle")}
/> />
</FormBody>
<FormActions> <FormActions>
<Button <Button
disabled={ disabled={

View File

@ -1,4 +1,4 @@
import { FormActions, FormDialog } from "@repo/rdx-ui/components/form-dialog"; import { FormActions, FormBody, FormDialog } from "@repo/rdx-ui/components/form-dialog";
import { Button } from "@repo/shadcn-ui/components/button"; import { Button } from "@repo/shadcn-ui/components/button";
import { Input } from "@repo/shadcn-ui/components/input"; import { Input } from "@repo/shadcn-ui/components/input";
import { Label } from "@repo/shadcn-ui/components/label"; import { Label } from "@repo/shadcn-ui/components/label";
@ -23,7 +23,8 @@ export function QuickCreateDialog(props: QuickCreateDialogProps) {
open open
title={t(target.kind === "system" ? "quickAddSystem" : "quickAddEnvironment")} title={t(target.kind === "system" ? "quickAddSystem" : "quickAddEnvironment")}
> >
<form className="space-y-4" onSubmit={submit}> <form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
<FormBody>
{target.kind === "environment" && ( {target.kind === "environment" && (
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
{t("quickEnvironmentContext", { name: target.systemName })} {t("quickEnvironmentContext", { name: target.systemName })}
@ -65,6 +66,7 @@ export function QuickCreateDialog(props: QuickCreateDialogProps) {
)} )}
<p className="text-sm text-muted-foreground">{t("quickCreateNotice")}</p> <p className="text-sm text-muted-foreground">{t("quickCreateNotice")}</p>
<EntityHelp kind={target.kind} scope={scope} /> <EntityHelp kind={target.kind} scope={scope} />
</FormBody>
<FormActions> <FormActions>
<Button disabled={mutation.isPending} type="submit"> <Button disabled={mutation.isPending} type="submit">
{t(mutation.isPending ? "saving" : "quickSaveSelect")} {t(mutation.isPending ? "saving" : "quickSaveSelect")}

View File

@ -1,4 +1,4 @@
import { FormActions } from "@repo/rdx-ui/components/form-dialog"; import { FormActions, FormBody } from "@repo/rdx-ui/components/form-dialog";
import { Button } from "@repo/shadcn-ui/components/button"; import { Button } from "@repo/shadcn-ui/components/button";
import type { ComponentType } from "react"; import type { ComponentType } from "react";
import { useId } from "react"; import { useId } from "react";
@ -37,7 +37,8 @@ export function AccessFormFields(
const prefix = useId(); const prefix = useId();
return ( return (
<> <>
<form className="space-y-4" onSubmit={submit}> <form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
<FormBody>
<div className="grid gap-4 sm:grid-cols-2"> <div className="grid gap-4 sm:grid-cols-2">
{editableFields.map((field) => ( {editableFields.map((field) => (
<div <div
@ -83,6 +84,7 @@ export function AccessFormFields(
<p className="text-sm text-muted-foreground">{t("configurationHelp")}</p> <p className="text-sm text-muted-foreground">{t("configurationHelp")}</p>
)} )}
<EntityHelp kind={kind} scope={scope} /> <EntityHelp kind={kind} scope={scope} />
</FormBody>
<FormActions> <FormActions>
<Button disabled={mutation.isPending} type="submit"> <Button disabled={mutation.isPending} type="submit">
{t(mutation.isPending ? "saving" : "save")} {t(mutation.isPending ? "saving" : "save")}

View File

@ -1,5 +1,5 @@
import { ContextHelp } from "@repo/rdx-ui/components/context-help"; import { ContextHelp } from "@repo/rdx-ui/components/context-help";
import { FormActions } from "@repo/rdx-ui/components/form-dialog"; import { FormActions, FormBody } from "@repo/rdx-ui/components/form-dialog";
import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action"; import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action";
import { Button } from "@repo/shadcn-ui/components/button"; import { Button } from "@repo/shadcn-ui/components/button";
import { Input } from "@repo/shadcn-ui/components/input"; import { Input } from "@repo/shadcn-ui/components/input";
@ -45,7 +45,8 @@ export function ApplicationConfigurationForm(props: ApplicationConfigurationForm
const prefix = useId(); const prefix = useId();
return ( return (
<> <>
<form className="space-y-5" onSubmit={submit}> <form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
<FormBody>
<fieldset className="space-y-4" disabled={mutation.isPending}> <fieldset className="space-y-4" disabled={mutation.isPending}>
<div className="grid gap-4 sm:grid-cols-2"> <div className="grid gap-4 sm:grid-cols-2">
<div className="space-y-2"> <div className="space-y-2">
@ -160,6 +161,7 @@ export function ApplicationConfigurationForm(props: ApplicationConfigurationForm
scopeNote={t("applications.scope")} scopeNote={t("applications.scope")}
title={t("applications.help")} title={t("applications.help")}
/> />
</FormBody>
<FormActions> <FormActions>
<Button disabled={!ready || mutation.isPending} type="submit"> <Button disabled={!ready || mutation.isPending} type="submit">
{t(mutation.isPending ? "saving" : "save")} {t(mutation.isPending ? "saving" : "save")}

View File

@ -1,4 +1,4 @@
import { FormActions } from "@repo/rdx-ui/components/form-dialog"; import { FormActions, FormBody } from "@repo/rdx-ui/components/form-dialog";
import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action"; import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action";
import { SwitchField } from "@repo/rdx-ui/components/switch-field"; import { SwitchField } from "@repo/rdx-ui/components/switch-field";
import { Button } from "@repo/shadcn-ui/components/button"; import { Button } from "@repo/shadcn-ui/components/button";
@ -21,7 +21,8 @@ export function AssignmentForm(props: AssignmentFormProps) {
const id = useId(); const id = useId();
return ( return (
<> <>
<form className="space-y-4" onSubmit={submit}> <form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
<FormBody>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor={`${id}-department`}>{t("department")}</Label> <Label htmlFor={`${id}-department`}>{t("department")}</Label>
<SelectWithAction <SelectWithAction
@ -75,7 +76,9 @@ export function AssignmentForm(props: AssignmentFormProps) {
disabled={!!form.watch("endDate")} disabled={!!form.watch("endDate")}
id={`${id}-primary`} id={`${id}-primary`}
label={t("primaryDepartment")} label={t("primaryDepartment")}
onCheckedChange={(checked) => form.setValue("isPrimary", checked, { shouldDirty: true })} onCheckedChange={(checked) =>
form.setValue("isPrimary", checked, { shouldDirty: true })
}
/> />
<p className="text-xs text-muted-foreground">{t("assignmentHelp")}</p> <p className="text-xs text-muted-foreground">{t("assignmentHelp")}</p>
<CompanyFeedback <CompanyFeedback
@ -84,6 +87,7 @@ export function AssignmentForm(props: AssignmentFormProps) {
/> />
<EntityHelp kind="assignment" /> <EntityHelp kind="assignment" />
</FormBody>
<FormActions> <FormActions>
<Button disabled={mutation.isPending} type="submit"> <Button disabled={mutation.isPending} type="submit">
{t(mutation.isPending ? "saving" : "save")} {t(mutation.isPending ? "saving" : "save")}

View File

@ -1,5 +1,5 @@
import { ContextHelp } from "@repo/rdx-ui/components/context-help"; import { ContextHelp } from "@repo/rdx-ui/components/context-help";
import { FormActions } from "@repo/rdx-ui/components/form-dialog"; import { FormActions, FormBody } from "@repo/rdx-ui/components/form-dialog";
import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action"; import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action";
import { Button } from "@repo/shadcn-ui/components/button"; import { Button } from "@repo/shadcn-ui/components/button";
import { Input } from "@repo/shadcn-ui/components/input"; import { Input } from "@repo/shadcn-ui/components/input";
@ -44,7 +44,8 @@ export function CertificateConfigurationForm(props: CertificateConfigurationForm
const prefix = useId(); const prefix = useId();
return ( return (
<> <>
<form className="space-y-5" onSubmit={submit}> <form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
<FormBody>
<fieldset className="space-y-4" disabled={mutation.isPending}> <fieldset className="space-y-4" disabled={mutation.isPending}>
<div className="grid gap-4 sm:grid-cols-2"> <div className="grid gap-4 sm:grid-cols-2">
<div className="space-y-2"> <div className="space-y-2">
@ -142,6 +143,7 @@ export function CertificateConfigurationForm(props: CertificateConfigurationForm
scopeNote={t("certificates.scope")} scopeNote={t("certificates.scope")}
title={t("certificates.help")} title={t("certificates.help")}
/> />
</FormBody>
<FormActions> <FormActions>
<Button disabled={!ready || mutation.isPending} type="submit"> <Button disabled={!ready || mutation.isPending} type="submit">
{t(mutation.isPending ? "saving" : "save")} {t(mutation.isPending ? "saving" : "save")}

View File

@ -1,4 +1,4 @@
import { FormActions } from "@repo/rdx-ui/components/form-dialog"; import { FormActions, FormBody } from "@repo/rdx-ui/components/form-dialog";
import { SwitchField } from "@repo/rdx-ui/components/switch-field"; import { SwitchField } from "@repo/rdx-ui/components/switch-field";
import { Button } from "@repo/shadcn-ui/components/button"; import { Button } from "@repo/shadcn-ui/components/button";
import { Input } from "@repo/shadcn-ui/components/input"; import { Input } from "@repo/shadcn-ui/components/input";
@ -20,7 +20,8 @@ export function CompanyForm(props: CompanyFormProps) {
const { t } = useTranslation("companies"); const { t } = useTranslation("companies");
const id = useId(); const id = useId();
return ( return (
<form className="space-y-4" onSubmit={submit}> <form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
<FormBody>
{showTitle && ( {showTitle && (
<h2 className="text-lg font-semibold">{t(company ? "editCompany" : "newCompany")}</h2> <h2 className="text-lg font-semibold">{t(company ? "editCompany" : "newCompany")}</h2>
)} )}
@ -54,6 +55,7 @@ export function CompanyForm(props: CompanyFormProps) {
/> />
<EntityHelp kind="company" /> <EntityHelp kind="company" />
</FormBody>
<FormActions> <FormActions>
<Button disabled={mutation.isPending} type="submit"> <Button disabled={mutation.isPending} type="submit">
{t(mutation.isPending ? "saving" : "save")} {t(mutation.isPending ? "saving" : "save")}

View File

@ -1,4 +1,4 @@
import { FormActions } from "@repo/rdx-ui/components/form-dialog"; import { FormActions, FormBody } from "@repo/rdx-ui/components/form-dialog";
import { SwitchField } from "@repo/rdx-ui/components/switch-field"; import { SwitchField } from "@repo/rdx-ui/components/switch-field";
import { Button } from "@repo/shadcn-ui/components/button"; import { Button } from "@repo/shadcn-ui/components/button";
import { Input } from "@repo/shadcn-ui/components/input"; import { Input } from "@repo/shadcn-ui/components/input";
@ -17,7 +17,8 @@ export function DepartmentForm(props: DepartmentFormProps) {
const { t } = useTranslation("companies"); const { t } = useTranslation("companies");
const id = useId(); const id = useId();
return ( return (
<form className="space-y-4" onSubmit={submit}> <form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
<FormBody>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor={id}>{t("name")}</Label> <Label htmlFor={id}>{t("name")}</Label>
<Input id={id} maxLength={100} required {...form.register("name")} /> <Input id={id} maxLength={100} required {...form.register("name")} />
@ -38,6 +39,7 @@ export function DepartmentForm(props: DepartmentFormProps) {
/> />
<EntityHelp kind="department" /> <EntityHelp kind="department" />
</FormBody>
<FormActions> <FormActions>
<Button disabled={mutation.isPending} type="submit"> <Button disabled={mutation.isPending} type="submit">
{t(mutation.isPending ? "saving" : "save")} {t(mutation.isPending ? "saving" : "save")}

View File

@ -1,5 +1,5 @@
import { ContextHelp } from "@repo/rdx-ui/components/context-help"; import { ContextHelp } from "@repo/rdx-ui/components/context-help";
import { FormActions } from "@repo/rdx-ui/components/form-dialog"; import { FormActions, FormBody } from "@repo/rdx-ui/components/form-dialog";
import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action"; import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action";
import { Button } from "@repo/shadcn-ui/components/button"; import { Button } from "@repo/shadcn-ui/components/button";
import { Input } from "@repo/shadcn-ui/components/input"; import { Input } from "@repo/shadcn-ui/components/input";
@ -40,7 +40,8 @@ export function FolderMappingForm(props: FolderMappingFormProps) {
const prefix = useId(); const prefix = useId();
return ( return (
<> <>
<form className="space-y-5" onSubmit={submit}> <form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
<FormBody>
<fieldset className="space-y-4" disabled={mutation.isPending}> <fieldset className="space-y-4" disabled={mutation.isPending}>
<FolderMappingTargets <FolderMappingTargets
company={company} company={company}
@ -127,6 +128,7 @@ export function FolderMappingForm(props: FolderMappingFormProps) {
scopeNote={t("folders.scope")} scopeNote={t("folders.scope")}
title={t("folders.help")} title={t("folders.help")}
/> />
</FormBody>
<FormActions> <FormActions>
<Button disabled={mutation.isPending || !ready} type="submit"> <Button disabled={mutation.isPending || !ready} type="submit">
{t(mutation.isPending ? "saving" : "save")} {t(mutation.isPending ? "saving" : "save")}

View File

@ -1,4 +1,4 @@
import { FormActions } from "@repo/rdx-ui/components/form-dialog"; import { FormActions, FormBody } from "@repo/rdx-ui/components/form-dialog";
import { SwitchField } from "@repo/rdx-ui/components/switch-field"; import { SwitchField } from "@repo/rdx-ui/components/switch-field";
import { Button } from "@repo/shadcn-ui/components/button"; import { Button } from "@repo/shadcn-ui/components/button";
import { Input } from "@repo/shadcn-ui/components/input"; import { Input } from "@repo/shadcn-ui/components/input";
@ -20,14 +20,17 @@ export function InterventionTemplateForm(props: InterventionTemplateFormProps) {
const { t } = useTranslation("companies"); const { t } = useTranslation("companies");
const prefix = useId(); const prefix = useId();
return ( return (
<form className="space-y-5" onSubmit={submit}> <form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
<FormBody>
<fieldset className="space-y-4" disabled={mutation.isPending}> <fieldset className="space-y-4" disabled={mutation.isPending}>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor={`${prefix}-name`}>{t("name")}</Label> <Label htmlFor={`${prefix}-name`}>{t("name")}</Label>
<Input id={`${prefix}-name`} maxLength={160} required {...form.register("name")} /> <Input id={`${prefix}-name`} maxLength={160} required {...form.register("name")} />
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor={`${prefix}-description`}>{t("interventionTemplates.description")}</Label> <Label htmlFor={`${prefix}-description`}>
{t("interventionTemplates.description")}
</Label>
<Textarea <Textarea
id={`${prefix}-description`} id={`${prefix}-description`}
maxLength={4000} maxLength={4000}
@ -67,6 +70,7 @@ export function InterventionTemplateForm(props: InterventionTemplateFormProps) {
invalid={Object.keys(form.formState.errors).length > 0} invalid={Object.keys(form.formState.errors).length > 0}
/> />
<InterventionTemplateHelp /> <InterventionTemplateHelp />
</FormBody>
<FormActions> <FormActions>
<Button disabled={mutation.isPending} type="submit"> <Button disabled={mutation.isPending} type="submit">
{t(mutation.isPending ? "saving" : "save")} {t(mutation.isPending ? "saving" : "save")}

View File

@ -1,4 +1,4 @@
import { FormActions } from "@repo/rdx-ui/components/form-dialog"; import { FormActions, FormBody } from "@repo/rdx-ui/components/form-dialog";
import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action"; import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action";
import { Button } from "@repo/shadcn-ui/components/button"; import { Button } from "@repo/shadcn-ui/components/button";
import { Input } from "@repo/shadcn-ui/components/input"; import { Input } from "@repo/shadcn-ui/components/input";
@ -52,7 +52,8 @@ export function InterventionForm(props: InterventionFormProps) {
const prefix = useId(); const prefix = useId();
return ( return (
<> <>
<form className="space-y-5" onSubmit={submit}> <form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
<FormBody>
<fieldset className="space-y-4" disabled={mutation.isPending}> <fieldset className="space-y-4" disabled={mutation.isPending}>
<InterventionCompanyField <InterventionCompanyField
companies={data.companies} companies={data.companies}
@ -149,6 +150,7 @@ export function InterventionForm(props: InterventionFormProps) {
invalid={Object.keys(form.formState.errors).length > 0} invalid={Object.keys(form.formState.errors).length > 0}
/> />
<InterventionHelp /> <InterventionHelp />
</FormBody>
<FormActions> <FormActions>
<Button <Button
disabled={mutation.isPending || !companyId || !lookups.data || lookups.isError} disabled={mutation.isPending || !companyId || !lookups.data || lookups.isError}

View File

@ -1,5 +1,5 @@
import { ContextHelp } from "@repo/rdx-ui/components/context-help"; import { ContextHelp } from "@repo/rdx-ui/components/context-help";
import { FormActions } from "@repo/rdx-ui/components/form-dialog"; import { FormActions, FormBody } from "@repo/rdx-ui/components/form-dialog";
import { Button } from "@repo/shadcn-ui/components/button"; import { Button } from "@repo/shadcn-ui/components/button";
import { Label } from "@repo/shadcn-ui/components/label"; import { Label } from "@repo/shadcn-ui/components/label";
import { Textarea } from "@repo/shadcn-ui/components/textarea"; import { Textarea } from "@repo/shadcn-ui/components/textarea";
@ -44,7 +44,8 @@ export function MailConfigurationForm(props: MailConfigurationFormProps) {
const prefix = useId(); const prefix = useId();
return ( return (
<> <>
<form className="space-y-5" onSubmit={submit}> <form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
<FormBody>
<fieldset className="space-y-4" disabled={disabled}> <fieldset className="space-y-4" disabled={disabled}>
{legacy && ( {legacy && (
<p className="rounded-md border p-3 text-sm"> <p className="rounded-md border p-3 text-sm">
@ -109,6 +110,7 @@ export function MailConfigurationForm(props: MailConfigurationFormProps) {
scopeNote={t("mail.scope")} scopeNote={t("mail.scope")}
title={t("mail.help")} title={t("mail.help")}
/> />
</FormBody>
<FormActions> <FormActions>
<Button disabled={disabled || !ready} type="submit"> <Button disabled={disabled || !ready} type="submit">
{t(disabled ? "saving" : "save")} {t(disabled ? "saving" : "save")}

View File

@ -1,5 +1,5 @@
import { ContextHelp } from "@repo/rdx-ui/components/context-help"; import { ContextHelp } from "@repo/rdx-ui/components/context-help";
import { FormActions } from "@repo/rdx-ui/components/form-dialog"; import { FormActions, FormBody } from "@repo/rdx-ui/components/form-dialog";
import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action"; import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action";
import { Button } from "@repo/shadcn-ui/components/button"; import { Button } from "@repo/shadcn-ui/components/button";
import { Input } from "@repo/shadcn-ui/components/input"; import { Input } from "@repo/shadcn-ui/components/input";
@ -31,7 +31,8 @@ export function MailboxUseForm(props: MailboxUseFormProps) {
const prefix = useId(); const prefix = useId();
return ( return (
<> <>
<form className="space-y-5" onSubmit={submit}> <form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
<FormBody>
<fieldset className="space-y-4" disabled={mutation.isPending}> <fieldset className="space-y-4" disabled={mutation.isPending}>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor={`${prefix}-mailbox`}>{t("mailbox.titleSingle")}</Label> <Label htmlFor={`${prefix}-mailbox`}>{t("mailbox.titleSingle")}</Label>
@ -107,6 +108,7 @@ export function MailboxUseForm(props: MailboxUseFormProps) {
guidance={t("mailbox.useGuidance")} guidance={t("mailbox.useGuidance")}
title={t("mailbox.useHelp")} title={t("mailbox.useHelp")}
/> />
</FormBody>
<FormActions> <FormActions>
<Button disabled={mutation.isPending || !query.data} type="submit"> <Button disabled={mutation.isPending || !query.data} type="submit">
{t(mutation.isPending ? "saving" : "save")} {t(mutation.isPending ? "saving" : "save")}

View File

@ -19,8 +19,8 @@ export function PersonEditorDialog({
formActions formActions
onOpenChange={onOpenChange} onOpenChange={onOpenChange}
open={open} open={open}
size="medium"
title={t(formProps.person ? "editPerson" : "newPerson")} title={t(formProps.person ? "editPerson" : "newPerson")}
wide
> >
<PersonForm {...formProps} key={formProps.person?.id ?? "new"} /> <PersonForm {...formProps} key={formProps.person?.id ?? "new"} />
</FormDialog> </FormDialog>

View File

@ -1,4 +1,4 @@
import { FormActions } from "@repo/rdx-ui/components/form-dialog"; import { FormActions, FormBody } from "@repo/rdx-ui/components/form-dialog";
import { SwitchField } from "@repo/rdx-ui/components/switch-field"; import { SwitchField } from "@repo/rdx-ui/components/switch-field";
import { Button } from "@repo/shadcn-ui/components/button"; import { Button } from "@repo/shadcn-ui/components/button";
import { Input } from "@repo/shadcn-ui/components/input"; import { Input } from "@repo/shadcn-ui/components/input";
@ -17,7 +17,8 @@ export function PersonForm(props: PersonFormProps) {
const { t } = useTranslation("companies"); const { t } = useTranslation("companies");
const id = useId(); const id = useId();
return ( return (
<form className="max-w-2xl space-y-6" onSubmit={submit}> <form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
<FormBody>
<div className="grid gap-4 sm:grid-cols-2"> <div className="grid gap-4 sm:grid-cols-2">
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor={`${id}-name`}>{t("name")}</Label> <Label htmlFor={`${id}-name`}>{t("name")}</Label>
@ -60,6 +61,7 @@ export function PersonForm(props: PersonFormProps) {
/> />
<p className="text-sm text-muted-foreground">{t("personHelp")}</p> <p className="text-sm text-muted-foreground">{t("personHelp")}</p>
<EntityHelp kind="person" /> <EntityHelp kind="person" />
</FormBody>
<FormActions> <FormActions>
<Button disabled={mutation.isPending} type="submit"> <Button disabled={mutation.isPending} type="submit">
{t(mutation.isPending ? "saving" : "save")} {t(mutation.isPending ? "saving" : "save")}

View File

@ -1,5 +1,5 @@
import { ContextHelp } from "@repo/rdx-ui/components/context-help"; import { ContextHelp } from "@repo/rdx-ui/components/context-help";
import { FormActions } from "@repo/rdx-ui/components/form-dialog"; import { FormActions, FormBody } from "@repo/rdx-ui/components/form-dialog";
import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action"; import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action";
import { SwitchField } from "@repo/rdx-ui/components/switch-field"; import { SwitchField } from "@repo/rdx-ui/components/switch-field";
import { Button } from "@repo/shadcn-ui/components/button"; import { Button } from "@repo/shadcn-ui/components/button";
@ -47,7 +47,8 @@ export function PrinterConfigurationForm(props: PrinterConfigurationFormProps) {
const prefix = useId(); const prefix = useId();
return ( return (
<> <>
<form className="space-y-5" onSubmit={submit}> <form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
<FormBody>
<fieldset className="space-y-4" disabled={mutation.isPending}> <fieldset className="space-y-4" disabled={mutation.isPending}>
<div className="grid gap-4 sm:grid-cols-2"> <div className="grid gap-4 sm:grid-cols-2">
<div className="space-y-2"> <div className="space-y-2">
@ -99,7 +100,8 @@ export function PrinterConfigurationForm(props: PrinterConfigurationFormProps) {
id={`${prefix}-connection`} id={`${prefix}-connection`}
label={t("printers.connection")} label={t("printers.connection")}
onValueChange={(value) => { onValueChange={(value) => {
const parsed = PrinterConfigurationInputSchema.shape.connection.safeParse(value); const parsed =
PrinterConfigurationInputSchema.shape.connection.safeParse(value);
if (parsed.success) { if (parsed.success) {
form.setValue("connection", parsed.data); form.setValue("connection", parsed.data);
} }
@ -159,6 +161,7 @@ export function PrinterConfigurationForm(props: PrinterConfigurationFormProps) {
scopeNote={t("printers.scope")} scopeNote={t("printers.scope")}
title={t("printers.help")} title={t("printers.help")}
/> />
</FormBody>
<FormActions> <FormActions>
<Button disabled={!ready || mutation.isPending} type="submit"> <Button disabled={!ready || mutation.isPending} type="submit">
{t(mutation.isPending ? "saving" : "save")} {t(mutation.isPending ? "saving" : "save")}

View File

@ -1,4 +1,4 @@
import { FormActions } from "@repo/rdx-ui/components/form-dialog"; import { FormActions, FormBody } from "@repo/rdx-ui/components/form-dialog";
import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action"; import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action";
import { SwitchField } from "@repo/rdx-ui/components/switch-field"; import { SwitchField } from "@repo/rdx-ui/components/switch-field";
import { Button } from "@repo/shadcn-ui/components/button"; import { Button } from "@repo/shadcn-ui/components/button";
@ -18,7 +18,8 @@ export function ProcessTemplateForm(props: ProcessTemplateFormProps) {
const { t } = useTranslation("companies"); const { t } = useTranslation("companies");
const prefix = useId(); const prefix = useId();
return ( return (
<form className="space-y-5" onSubmit={submit}> <form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
<FormBody>
<fieldset className="space-y-4" disabled={mutation.isPending}> <fieldset className="space-y-4" disabled={mutation.isPending}>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor={`${prefix}-name`}>{t("name")}</Label> <Label htmlFor={`${prefix}-name`}>{t("name")}</Label>
@ -62,7 +63,12 @@ export function ProcessTemplateForm(props: ProcessTemplateFormProps) {
/> />
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor={`${prefix}-notes`}>{t("mail.notes")}</Label> <Label htmlFor={`${prefix}-notes`}>{t("mail.notes")}</Label>
<Textarea id={`${prefix}-notes`} maxLength={4000} rows={3} {...form.register("notes")} /> <Textarea
id={`${prefix}-notes`}
maxLength={4000}
rows={3}
{...form.register("notes")}
/>
</div> </div>
</fieldset> </fieldset>
<ProcessFeedback <ProcessFeedback
@ -70,6 +76,7 @@ export function ProcessTemplateForm(props: ProcessTemplateFormProps) {
invalid={Object.keys(form.formState.errors).length > 0} invalid={Object.keys(form.formState.errors).length > 0}
/> />
<ProcessHelp template /> <ProcessHelp template />
</FormBody>
<FormActions> <FormActions>
<Button disabled={mutation.isPending} type="submit"> <Button disabled={mutation.isPending} type="submit">
{t(mutation.isPending ? "saving" : "save")} {t(mutation.isPending ? "saving" : "save")}

View File

@ -1,4 +1,4 @@
import { FormActions } from "@repo/rdx-ui/components/form-dialog"; import { FormActions, FormBody } from "@repo/rdx-ui/components/form-dialog";
import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action"; import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action";
import { Button } from "@repo/shadcn-ui/components/button"; import { Button } from "@repo/shadcn-ui/components/button";
import { Input } from "@repo/shadcn-ui/components/input"; import { Input } from "@repo/shadcn-ui/components/input";
@ -20,7 +20,8 @@ export function ProcessStartForm(props: ProcessStartFormProps) {
const prefix = useId(); const prefix = useId();
return ( return (
<> <>
<form className="space-y-5" onSubmit={submit}> <form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
<FormBody>
<fieldset className="space-y-4" disabled={mutation.isPending}> <fieldset className="space-y-4" disabled={mutation.isPending}>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor={`${prefix}-name`}>{t("name")}</Label> <Label htmlFor={`${prefix}-name`}>{t("name")}</Label>
@ -87,6 +88,7 @@ export function ProcessStartForm(props: ProcessStartFormProps) {
invalid={Object.keys(form.formState.errors).length > 0} invalid={Object.keys(form.formState.errors).length > 0}
/> />
<ProcessHelp /> <ProcessHelp />
</FormBody>
<FormActions> <FormActions>
<Button <Button
disabled={ disabled={

View File

@ -1,4 +1,4 @@
import { FormActions } from "@repo/rdx-ui/components/form-dialog"; import { FormActions, FormBody } from "@repo/rdx-ui/components/form-dialog";
import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action"; import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action";
import { Button } from "@repo/shadcn-ui/components/button"; import { Button } from "@repo/shadcn-ui/components/button";
import { Input } from "@repo/shadcn-ui/components/input"; import { Input } from "@repo/shadcn-ui/components/input";
@ -20,7 +20,8 @@ export function ProcessEditForm(props: ProcessEditFormProps) {
const { t } = useTranslation("companies"); const { t } = useTranslation("companies");
const prefix = useId(); const prefix = useId();
return ( return (
<form className="space-y-5" onSubmit={submit}> <form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
<FormBody>
<fieldset className="space-y-4" disabled={mutation.isPending}> <fieldset className="space-y-4" disabled={mutation.isPending}>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor={`${prefix}-name`}>{t("name")}</Label> <Label htmlFor={`${prefix}-name`}>{t("name")}</Label>
@ -47,7 +48,12 @@ export function ProcessEditForm(props: ProcessEditFormProps) {
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor={`${prefix}-notes`}>{t("mail.notes")}</Label> <Label htmlFor={`${prefix}-notes`}>{t("mail.notes")}</Label>
<Textarea id={`${prefix}-notes`} maxLength={4000} rows={3} {...form.register("notes")} /> <Textarea
id={`${prefix}-notes`}
maxLength={4000}
rows={3}
{...form.register("notes")}
/>
</div> </div>
<p className="text-sm text-muted-foreground">{t("processes.resolutionHelp")}</p> <p className="text-sm text-muted-foreground">{t("processes.resolutionHelp")}</p>
{process.values.tasks.map((task, index) => ( {process.values.tasks.map((task, index) => (
@ -59,6 +65,7 @@ export function ProcessEditForm(props: ProcessEditFormProps) {
invalid={Object.keys(form.formState.errors).length > 0} invalid={Object.keys(form.formState.errors).length > 0}
/> />
<ProcessHelp /> <ProcessHelp />
</FormBody>
<FormActions> <FormActions>
<Button disabled={mutation.isPending} type="submit"> <Button disabled={mutation.isPending} type="submit">
{t(mutation.isPending ? "saving" : "save")} {t(mutation.isPending ? "saving" : "save")}

View File

@ -1,5 +1,5 @@
import { ContextHelp } from "@repo/rdx-ui/components/context-help"; import { ContextHelp } from "@repo/rdx-ui/components/context-help";
import { FormActions } from "@repo/rdx-ui/components/form-dialog"; import { FormActions, FormBody } from "@repo/rdx-ui/components/form-dialog";
import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action"; import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action";
import { SwitchField } from "@repo/rdx-ui/components/switch-field"; import { SwitchField } from "@repo/rdx-ui/components/switch-field";
import { Button } from "@repo/shadcn-ui/components/button"; import { Button } from "@repo/shadcn-ui/components/button";
@ -40,7 +40,8 @@ export function RemoteConnectionForm(props: RemoteConnectionFormProps) {
const prefix = useId(); const prefix = useId();
return ( return (
<> <>
<form className="space-y-5" onSubmit={submit}> <form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
<FormBody>
<fieldset className="space-y-4" disabled={mutation.isPending}> <fieldset className="space-y-4" disabled={mutation.isPending}>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor={`${prefix}-person`}>{t("remote.person")}</Label> <Label htmlFor={`${prefix}-person`}>{t("remote.person")}</Label>
@ -135,6 +136,7 @@ export function RemoteConnectionForm(props: RemoteConnectionFormProps) {
scopeNote={t("remote.scope")} scopeNote={t("remote.scope")}
title={t("remote.help")} title={t("remote.help")}
/> />
</FormBody>
<FormActions> <FormActions>
<Button disabled={!ready || mutation.isPending} type="submit"> <Button disabled={!ready || mutation.isPending} type="submit">
{t(mutation.isPending ? "saving" : "save")} {t(mutation.isPending ? "saving" : "save")}

View File

@ -1,4 +1,4 @@
import { FormActions } from "@repo/rdx-ui/components/form-dialog"; import { FormActions, FormBody } from "@repo/rdx-ui/components/form-dialog";
import { Button } from "@repo/shadcn-ui/components/button"; import { Button } from "@repo/shadcn-ui/components/button";
import { Input } from "@repo/shadcn-ui/components/input"; import { Input } from "@repo/shadcn-ui/components/input";
import { Label } from "@repo/shadcn-ui/components/label"; import { Label } from "@repo/shadcn-ui/components/label";
@ -17,7 +17,8 @@ export function SiteForm(props: SiteFormProps) {
const { t } = useTranslation("companies"); const { t } = useTranslation("companies");
const id = useId(); const id = useId();
return ( return (
<form className="space-y-4" onSubmit={submit}> <form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
<FormBody>
{showTitle && <h3 className="text-lg font-semibold">{t(site ? "editSite" : "newSite")}</h3>} {showTitle && <h3 className="text-lg font-semibold">{t(site ? "editSite" : "newSite")}</h3>}
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor={`${id}-name`}>{t("name")}</Label> <Label htmlFor={`${id}-name`}>{t("name")}</Label>
@ -41,6 +42,7 @@ export function SiteForm(props: SiteFormProps) {
/> />
<EntityHelp kind="site" /> <EntityHelp kind="site" />
</FormBody>
<FormActions> <FormActions>
<Button disabled={mutation.isPending} type="submit"> <Button disabled={mutation.isPending} type="submit">
{t(mutation.isPending ? "saving" : "save")} {t(mutation.isPending ? "saving" : "save")}

View File

@ -1,5 +1,5 @@
import { ContextHelp } from "@repo/rdx-ui/components/context-help"; import { ContextHelp } from "@repo/rdx-ui/components/context-help";
import { FormActions } from "@repo/rdx-ui/components/form-dialog"; import { FormActions, FormBody } from "@repo/rdx-ui/components/form-dialog";
import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action"; import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action";
import { SwitchField } from "@repo/rdx-ui/components/switch-field"; import { SwitchField } from "@repo/rdx-ui/components/switch-field";
import { Button } from "@repo/shadcn-ui/components/button"; import { Button } from "@repo/shadcn-ui/components/button";
@ -23,12 +23,15 @@ export function TeleworkAuthorizationForm(props: TeleworkAuthorizationFormProps)
const prefix = useId(); const prefix = useId();
return ( return (
<> <>
<form className="space-y-5" onSubmit={submit}> <form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
<FormBody>
<fieldset className="space-y-4" disabled={mutation.isPending}> <fieldset className="space-y-4" disabled={mutation.isPending}>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor={`${prefix}-person`}>{t("telework.person")}</Label> <Label htmlFor={`${prefix}-person`}>{t("telework.person")}</Label>
<SelectWithAction <SelectWithAction
action={fixed ? undefined : { label: t("newPerson"), onClick: () => setQuick(true) }} action={
fixed ? undefined : { label: t("newPerson"), onClick: () => setQuick(true) }
}
disabled={fixed} disabled={fixed}
id={`${prefix}-person`} id={`${prefix}-person`}
label={t("telework.person")} label={t("telework.person")}
@ -96,6 +99,7 @@ export function TeleworkAuthorizationForm(props: TeleworkAuthorizationFormProps)
scopeNote={t("telework.scope")} scopeNote={t("telework.scope")}
title={t("telework.help")} title={t("telework.help")}
/> />
</FormBody>
<FormActions> <FormActions>
<Button disabled={mutation.isPending} type="submit"> <Button disabled={mutation.isPending} type="submit">
{t(mutation.isPending ? "saving" : "save")} {t(mutation.isPending ? "saving" : "save")}

View File

@ -1,4 +1,4 @@
import { FormActions } from "@repo/rdx-ui/components/form-dialog"; import { FormActions, FormBody } from "@repo/rdx-ui/components/form-dialog";
import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action"; import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action";
import { Button } from "@repo/shadcn-ui/components/button"; import { Button } from "@repo/shadcn-ui/components/button";
import { Input } from "@repo/shadcn-ui/components/input"; import { Input } from "@repo/shadcn-ui/components/input";
@ -38,7 +38,8 @@ export function TicketForm(props: TicketFormProps) {
const prefix = useId(); const prefix = useId();
return ( return (
<> <>
<form className="space-y-5" onSubmit={submit}> <form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
<FormBody>
<fieldset className="space-y-4" disabled={mutation.isPending}> <fieldset className="space-y-4" disabled={mutation.isPending}>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor={prefix}>{t("tickets.company")}</Label> <Label htmlFor={prefix}>{t("tickets.company")}</Label>
@ -140,6 +141,7 @@ export function TicketForm(props: TicketFormProps) {
invalid={Object.keys(form.formState.errors).length > 0} invalid={Object.keys(form.formState.errors).length > 0}
/> />
<TicketHelp /> <TicketHelp />
</FormBody>
<FormActions> <FormActions>
<Button disabled={mutation.isPending || !companyId || !lookups.data} type="submit"> <Button disabled={mutation.isPending || !companyId || !lookups.data} type="submit">
{t(mutation.isPending ? "saving" : "save")} {t(mutation.isPending ? "saving" : "save")}

View File

@ -1,5 +1,5 @@
import { ContextHelp } from "@repo/rdx-ui/components/context-help"; import { ContextHelp } from "@repo/rdx-ui/components/context-help";
import { FormActions } from "@repo/rdx-ui/components/form-dialog"; import { FormActions, FormBody } from "@repo/rdx-ui/components/form-dialog";
import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action"; import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action";
import { SwitchField } from "@repo/rdx-ui/components/switch-field"; import { SwitchField } from "@repo/rdx-ui/components/switch-field";
import { Button } from "@repo/shadcn-ui/components/button"; import { Button } from "@repo/shadcn-ui/components/button";
@ -35,7 +35,8 @@ export function WorkProfileForm(props: WorkProfileFormProps) {
const prefix = useId(); const prefix = useId();
return ( return (
<> <>
<form className="space-y-5" onSubmit={submit}> <form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
<FormBody>
<fieldset className="space-y-4" disabled={disabled}> <fieldset className="space-y-4" disabled={disabled}>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor={prefix}>{t("profiles.name")}</Label> <Label htmlFor={prefix}>{t("profiles.name")}</Label>
@ -119,6 +120,7 @@ export function WorkProfileForm(props: WorkProfileFormProps) {
scopeNote={t("profiles.scope")} scopeNote={t("profiles.scope")}
title={t("profiles.help")} title={t("profiles.help")}
/> />
</FormBody>
<FormActions> <FormActions>
<Button disabled={disabled || !inventory.data || !access.data} type="submit"> <Button disabled={disabled || !inventory.data || !access.data} type="submit">
{t(disabled ? "saving" : "save")} {t(disabled ? "saving" : "save")}

View File

@ -1,4 +1,4 @@
import { FormActions } from "@repo/rdx-ui/components/form-dialog"; import { FormActions, FormBody } from "@repo/rdx-ui/components/form-dialog";
import { Button } from "@repo/shadcn-ui/components/button"; import { Button } from "@repo/shadcn-ui/components/button";
import { Input } from "@repo/shadcn-ui/components/input"; import { Input } from "@repo/shadcn-ui/components/input";
import { Label } from "@repo/shadcn-ui/components/label"; import { Label } from "@repo/shadcn-ui/components/label";
@ -15,7 +15,8 @@ export function PasswordForm(props: PasswordFormProps) {
const { t } = useTranslation("identity"); const { t } = useTranslation("identity");
const id = useId(); const id = useId();
return ( return (
<form className="space-y-4" onSubmit={submit}> <form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
<FormBody>
{showTitle && ( {showTitle && (
<h3 className="font-semibold"> <h3 className="font-semibold">
{t(target ? "resetPassword" : "changePassword")} {t(target ? "resetPassword" : "changePassword")}
@ -56,6 +57,7 @@ export function PasswordForm(props: PasswordFormProps) {
</p> </p>
)} )}
<FormFeedback error={mutation.error} success={mutation.isSuccess} /> <FormFeedback error={mutation.error} success={mutation.isSuccess} />
</FormBody>
<FormActions> <FormActions>
<Button disabled={mutation.isPending} type="submit"> <Button disabled={mutation.isPending} type="submit">
{t(mutation.isPending ? "saving" : "save")} {t(mutation.isPending ? "saving" : "save")}

View File

@ -24,6 +24,7 @@ export function UserEditorDialog(
} }
}} }}
open={props.open} open={props.open}
size="medium"
title={t( title={t(
props.mode === "create" props.mode === "create"
? "createUser" ? "createUser"

View File

@ -1,4 +1,4 @@
import { FormActions } from "@repo/rdx-ui/components/form-dialog"; import { FormActions, FormBody } from "@repo/rdx-ui/components/form-dialog";
import { SwitchField } from "@repo/rdx-ui/components/switch-field"; import { SwitchField } from "@repo/rdx-ui/components/switch-field";
import { Button } from "@repo/shadcn-ui/components/button"; import { Button } from "@repo/shadcn-ui/components/button";
import { Input } from "@repo/shadcn-ui/components/input"; import { Input } from "@repo/shadcn-ui/components/input";
@ -16,7 +16,8 @@ export function UserForm(props: UserFormProps) {
const { t } = useTranslation("identity"); const { t } = useTranslation("identity");
const id = useId(); const id = useId();
return ( return (
<form className="space-y-4" onSubmit={submit}> <form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
<FormBody>
{showTitle && <h3 className="font-semibold">{t(user ? "editUser" : "createUser")}</h3>} {showTitle && <h3 className="font-semibold">{t(user ? "editUser" : "createUser")}</h3>}
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor={`${id}-name`}>{t("name")}</Label> <Label htmlFor={`${id}-name`}>{t("name")}</Label>
@ -72,6 +73,7 @@ export function UserForm(props: UserFormProps) {
</p> </p>
)} )}
<FormFeedback error={mutation.error} success={mutation.isSuccess} /> <FormFeedback error={mutation.error} success={mutation.isSuccess} />
</FormBody>
<FormActions> <FormActions>
<Button disabled={mutation.isPending} type="submit"> <Button disabled={mutation.isPending} type="submit">
{t(mutation.isPending ? "saving" : "save")} {t(mutation.isPending ? "saving" : "save")}

View File

@ -152,5 +152,9 @@
"assign": "Section", "assign": "Section",
"help": "Organize fields without changing their values or requirements. Removing a section moves its fields to Unsectioned.", "help": "Organize fields without changing their values or requirements. Removing a section moves its fields to Unsectioned.",
"empty": "This section has no fields yet." "empty": "This section has no fields yet."
},
"formLayout": {
"identity": "Resource identification",
"template": "Template details"
} }
} }

View File

@ -152,5 +152,9 @@
"assign": "Sección", "assign": "Sección",
"help": "Organiza los campos sin cambiar sus valores ni su obligatoriedad. Al eliminar una sección, sus campos pasan a «Sin sección».", "help": "Organiza los campos sin cambiar sus valores ni su obligatoriedad. Al eliminar una sección, sus campos pasan a «Sin sección».",
"empty": "Esta sección aún no tiene campos." "empty": "Esta sección aún no tiene campos."
},
"formLayout": {
"identity": "Identificación del recurso",
"template": "Datos de la plantilla"
} }
} }

View File

@ -1,4 +1,4 @@
import { FormActions } from "@repo/rdx-ui/components/form-dialog"; import { FormActions, FormBody } from "@repo/rdx-ui/components/form-dialog";
import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action"; import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action";
import { Button } from "@repo/shadcn-ui/components/button"; import { Button } from "@repo/shadcn-ui/components/button";
import { Input } from "@repo/shadcn-ui/components/input"; import { Input } from "@repo/shadcn-ui/components/input";
@ -19,7 +19,8 @@ export function CatalogFieldForm(props: CatalogFieldFormProps) {
const { t } = useTranslation("resources"); const { t } = useTranslation("resources");
const id = useId(); const id = useId();
return ( return (
<form className="space-y-4" onSubmit={submit}> <form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
<FormBody>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor={`${id}-name`}>{t("name")}</Label> <Label htmlFor={`${id}-name`}>{t("name")}</Label>
<Input id={`${id}-name`} maxLength={100} required {...form.register("name")} /> <Input id={`${id}-name`} maxLength={100} required {...form.register("name")} />
@ -67,6 +68,7 @@ export function CatalogFieldForm(props: CatalogFieldFormProps) {
invalid={Object.keys(form.formState.errors).length > 0} invalid={Object.keys(form.formState.errors).length > 0}
/> />
<CatalogHelp kind="field" scope={scope} /> <CatalogHelp kind="field" scope={scope} />
</FormBody>
<FormActions> <FormActions>
<Button disabled={mutation.isPending} type="submit"> <Button disabled={mutation.isPending} type="submit">
{t(mutation.isPending ? "saving" : "save")} {t(mutation.isPending ? "saving" : "save")}

View File

@ -1,5 +1,6 @@
import { UniqueID } from "@repo/rdx-ddd"; import { UniqueID } from "@repo/rdx-ddd";
import { FormActions } from "@repo/rdx-ui/components/form-dialog"; import { FormActions, FormBody } from "@repo/rdx-ui/components/form-dialog";
import { FormSection } from "@repo/rdx-ui/components/form-section";
import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action"; import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action";
import { TemplateSectionsEditor } from "@repo/rdx-ui/components/template-sections-editor"; import { TemplateSectionsEditor } from "@repo/rdx-ui/components/template-sections-editor";
import { Button } from "@repo/shadcn-ui/components/button"; import { Button } from "@repo/shadcn-ui/components/button";
@ -23,7 +24,9 @@ export function CatalogTemplateForm(props: CatalogTemplateFormProps) {
const id = useId(); const id = useId();
return ( return (
<> <>
<form className="space-y-4" onSubmit={submit}> <form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
<FormBody>
<FormSection title={t("formLayout.template")}>
<div className="grid gap-4 sm:grid-cols-2"> <div className="grid gap-4 sm:grid-cols-2">
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor={`${id}-name`}>{t("name")}</Label> <Label htmlFor={`${id}-name`}>{t("name")}</Label>
@ -61,6 +64,7 @@ export function CatalogTemplateForm(props: CatalogTemplateFormProps) {
/> />
<p className="text-xs text-muted-foreground">{t("templateUsageHelp")}</p> <p className="text-xs text-muted-foreground">{t("templateUsageHelp")}</p>
</div> </div>
</FormSection>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor={`${id}-field`}>{t("selectField")}</Label> <Label htmlFor={`${id}-field`}>{t("selectField")}</Label>
<SelectWithAction <SelectWithAction
@ -125,6 +129,7 @@ export function CatalogTemplateForm(props: CatalogTemplateFormProps) {
invalid={Object.keys(form.formState.errors).length > 0} invalid={Object.keys(form.formState.errors).length > 0}
/> />
<CatalogHelp kind="template" scope={scope} /> <CatalogHelp kind="template" scope={scope} />
</FormBody>
<FormActions> <FormActions>
<Button disabled={mutation.isPending} type="submit"> <Button disabled={mutation.isPending} type="submit">
{t(mutation.isPending ? "saving" : "save")} {t(mutation.isPending ? "saving" : "save")}

View File

@ -1,6 +1,7 @@
import { ContextHelp } from "@repo/rdx-ui/components/context-help"; import { ContextHelp } from "@repo/rdx-ui/components/context-help";
import { FieldSections } from "@repo/rdx-ui/components/field-sections"; import { FieldSections } from "@repo/rdx-ui/components/field-sections";
import { FormActions } from "@repo/rdx-ui/components/form-dialog"; import { FormActions, FormBody } from "@repo/rdx-ui/components/form-dialog";
import { FormSection } from "@repo/rdx-ui/components/form-section";
import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action"; import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action";
import { Button } from "@repo/shadcn-ui/components/button"; import { Button } from "@repo/shadcn-ui/components/button";
import { Input } from "@repo/shadcn-ui/components/input"; import { Input } from "@repo/shadcn-ui/components/input";
@ -42,8 +43,10 @@ export function ResourceForm(props: ResourceFormProps) {
const prefix = useId(); const prefix = useId();
return ( return (
<> <>
<form className="space-y-5" onSubmit={submit}> <form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
<FormBody>
<fieldset className="space-y-5" disabled={mutation.isPending}> <fieldset className="space-y-5" disabled={mutation.isPending}>
<FormSection title={t("formLayout.identity")}>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor={`${prefix}-name`}>{t("name")}</Label> <Label htmlFor={`${prefix}-name`}>{t("name")}</Label>
<Input id={`${prefix}-name`} maxLength={100} required {...form.register("name")} /> <Input id={`${prefix}-name`} maxLength={100} required {...form.register("name")} />
@ -90,6 +93,7 @@ export function ResourceForm(props: ResourceFormProps) {
/> />
</div> </div>
</div> </div>
</FormSection>
<ResourceIntegrationFields <ResourceIntegrationFields
fixed={!!resource} fixed={!!resource}
integration={values.integration} integration={values.integration}
@ -147,6 +151,7 @@ export function ResourceForm(props: ResourceFormProps) {
scopeNote={t(`${helpPrefix}Scope`)} scopeNote={t(`${helpPrefix}Scope`)}
title={t("helpTitle")} title={t("helpTitle")}
/> />
</FormBody>
<FormActions> <FormActions>
<Button disabled={mutation.isPending} type="submit"> <Button disabled={mutation.isPending} type="submit">
{t(mutation.isPending ? "saving" : "save")} {t(mutation.isPending ? "saving" : "save")}

View File

@ -29,9 +29,13 @@ export function FieldSections<T extends { sectionId?: string | undefined }>({
}, [hasErrors]); }, [hasErrors]);
const content = (fields: readonly T[]) => const content = (fields: readonly T[]) =>
values ? ( values ? (
<dl className="grid gap-4 sm:grid-cols-2">{fields.map(renderItem)}</dl> <dl className="grid gap-4 sm:grid-cols-2 @min-[900px]:grid-cols-3">
{fields.map(renderItem)}
</dl>
) : ( ) : (
<div className="grid gap-4 sm:grid-cols-2">{fields.map(renderItem)}</div> <div className="grid gap-4 sm:grid-cols-2 @min-[900px]:grid-cols-3">
{fields.map(renderItem)}
</div>
); );
if (!sections.length) { if (!sections.length) {
return content(items); return content(items);
@ -40,7 +44,7 @@ export function FieldSections<T extends { sectionId?: string | undefined }>({
const ungrouped = items.filter((item) => !(item.sectionId && known.has(item.sectionId))); const ungrouped = items.filter((item) => !(item.sectionId && known.has(item.sectionId)));
return ( return (
<div <div
className="space-y-3" className="grid items-start gap-4 @min-[900px]:grid-cols-2"
onInvalidCapture={(event) => { onInvalidCapture={(event) => {
// Abrir antes de que el navegador intente enfocar un control inválido oculto. // Abrir antes de que el navegador intente enfocar un control inválido oculto.
if (event.target instanceof HTMLElement) { if (event.target instanceof HTMLElement) {
@ -58,7 +62,7 @@ export function FieldSections<T extends { sectionId?: string | undefined }>({
return null; return null;
} }
return ( return (
<details className="rounded-md border" key={section.id} open> <details className="@container rounded-md border" key={section.id} open>
<summary className="cursor-pointer rounded-md px-4 py-3 text-sm font-medium focus-visible:outline-2 focus-visible:outline-ring"> <summary className="cursor-pointer rounded-md px-4 py-3 text-sm font-medium focus-visible:outline-2 focus-visible:outline-ring">
{section.name} {section.name}
</summary> </summary>
@ -72,7 +76,7 @@ export function FieldSections<T extends { sectionId?: string | undefined }>({
); );
})} })}
{ungrouped.length > 0 && ( {ungrouped.length > 0 && (
<section className="space-y-3"> <section className="col-span-full space-y-3">
<h4 className="text-sm font-medium">{ungroupedLabel}</h4> <h4 className="text-sm font-medium">{ungroupedLabel}</h4>
{content(ungrouped)} {content(ungrouped)}
</section> </section>

View File

@ -18,6 +18,7 @@ export function FormDialog({
cancelLabel, cancelLabel,
children, children,
wide = false, wide = false,
size,
formActions = false, formActions = false,
}: { }: {
open: boolean; open: boolean;
@ -26,28 +27,39 @@ export function FormDialog({
cancelLabel: string; cancelLabel: string;
children: ReactNode; children: ReactNode;
wide?: boolean; wide?: boolean;
size?: "small" | "medium" | "large";
formActions?: boolean; formActions?: boolean;
}) { }) {
const width = size ?? (wide ? "large" : "small");
const widths = {
small: "sm:max-w-xl",
medium: "sm:max-w-3xl",
large: "sm:w-[90vw] sm:max-w-[1200px]",
};
return ( return (
<Dialog onOpenChange={onOpenChange} open={open}> <Dialog onOpenChange={onOpenChange} open={open}>
<DialogContent <DialogContent
className={`max-h-[calc(100dvh-2rem)] overflow-y-auto ${wide ? "sm:max-w-3xl" : "sm:max-w-xl"}`} className={`flex max-h-[calc(100dvh-2rem)] flex-col gap-0 overflow-hidden p-0 ${widths[width]}`}
showCloseButton={true} showCloseButton={true}
> >
<DialogHeader> <DialogHeader className="shrink-0 border-b px-6 py-5 pr-14">
<DialogTitle className="text-lg font-bold">{title}</DialogTitle> <DialogTitle className="text-lg font-bold">{title}</DialogTitle>
</DialogHeader> </DialogHeader>
<div
className="@container flex min-h-0 flex-1 flex-col overflow-y-auto px-6 pt-5 pb-5 [&:has([data-slot=form-body])]:overflow-hidden [&:has([data-slot=form-body])]:pb-0"
data-slot="form-dialog-content"
>
<FormCancelContext.Provider value={formActions ? cancelLabel : undefined}> <FormCancelContext.Provider value={formActions ? cancelLabel : undefined}>
{children} {children}
</FormCancelContext.Provider> </FormCancelContext.Provider>
</div>
{!formActions && ( {!formActions && (
<DialogFooter> <DialogFooter className="shrink-0 border-t px-6 py-4">
<DialogClose render={<Button type="button" variant="outline" />}> <DialogClose render={<Button type="button" variant="outline" />}>
{cancelLabel} {cancelLabel}
</DialogClose> </DialogClose>
</DialogFooter> </DialogFooter>
)} )}
</DialogContent> </DialogContent>
</Dialog> </Dialog>
); );
@ -56,7 +68,7 @@ export function FormDialog({
export function FormActions({ children }: { children: ReactNode }) { export function FormActions({ children }: { children: ReactNode }) {
const cancelLabel = useContext(FormCancelContext); const cancelLabel = useContext(FormCancelContext);
return ( return (
<DialogFooter className="border-t pt-4"> <DialogFooter className="shrink-0 border-t bg-popover py-4">
{cancelLabel && ( {cancelLabel && (
<DialogClose render={<Button type="button" variant="outline" />}>{cancelLabel}</DialogClose> <DialogClose render={<Button type="button" variant="outline" />}>{cancelLabel}</DialogClose>
)} )}
@ -64,3 +76,12 @@ export function FormActions({ children }: { children: ReactNode }) {
</DialogFooter> </DialogFooter>
); );
} }
/** El cuerpo se desplaza dentro del formulario; las acciones conservan su submit nativo. */
export function FormBody({ children }: { children: ReactNode }) {
return (
<div className="min-h-0 flex-1 space-y-5 overflow-y-auto pb-5 pr-1" data-slot="form-body">
{children}
</div>
);
}

View File

@ -0,0 +1,15 @@
import type { ReactNode } from "react";
import { useId } from "react";
/** Bloque visual reutilizable con título accesible; no altera validación ni datos. */
export function FormSection({ title, children }: { title: string; children: ReactNode }) {
const id = useId();
return (
<section aria-labelledby={id} className="space-y-4 rounded-lg border p-4">
<h3 className="text-sm font-semibold" id={id}>
{title}
</h3>
{children}
</section>
);
}

View File

@ -87,8 +87,13 @@ export function TemplateSectionsEditor({
</Button> </Button>
</div> </div>
<p className="text-xs text-muted-foreground">{labels.help}</p> <p className="text-xs text-muted-foreground">{labels.help}</p>
<div className="grid items-start gap-4 @min-[900px]:grid-cols-2">
{sections.map((section, index) => ( {sections.map((section, index) => (
<fieldset className="space-y-3 rounded-md border p-3" disabled={disabled} key={section.id}> <fieldset
className="space-y-3 rounded-md border p-3"
disabled={disabled}
key={section.id}
>
<legend className="sr-only">{section.name || labels.name}</legend> <legend className="sr-only">{section.name || labels.name}</legend>
<div className="flex items-end gap-2"> <div className="flex items-end gap-2">
<div className="min-w-0 flex-1 space-y-2"> <div className="min-w-0 flex-1 space-y-2">
@ -157,6 +162,7 @@ export function TemplateSectionsEditor({
{fieldList(section.id)} {fieldList(section.id)}
</fieldset> </fieldset>
))} ))}
</div>
{fieldList()} {fieldList()}
</section> </section>
); );