Refactor form dialogs and UI components for improved consistency and usability
- Updated DeleteBrandDialog and DeleteCatalogDefinitionDialog to use FormBody and FormActions for better structure. - Replaced DialogFooter with FormActions in various dialogs to standardize button layouts. - Introduced FormSectionTitle in multiple components for consistent section headings. - Adjusted grid layouts to use responsive design with @min-[560px] for better adaptability. - Created SoftwareResourceCreateDialog and SoftwareInstallationEditorDialog for better separation of concerns in software installation management. - Enhanced FormActions to support cancelDisabled prop for better control over button states. - Refined FormSectionTitle to allow for additional HTML elements like "legend" and "span". - Added EntityTableColumnsDialog for managing table column visibility in a more user-friendly manner.
This commit is contained in:
parent
ccb52fb534
commit
a0f02ced6e
@ -88,9 +88,7 @@ React, TanStack Query, Tailwind y shadcn/ui (Base UI, estilo Vega). Los componen
|
|||||||
Estado remoto en TanStack Query; claves centralizadas e invalidación explícita después de mutaciones.
|
Estado remoto en TanStack Query; claves centralizadas e invalidación explícita después de mutaciones.
|
||||||
No duplicar estado remoto ni usar efectos para derivar valores calculables durante render.
|
No duplicar estado remoto ni usar efectos para derivar valores calculables durante render.
|
||||||
Mantener accesibilidad y estados de carga, error, vacío y acciones deshabilitadas.
|
Mantener accesibilidad y estados de carga, error, vacío y acciones deshabilitadas.
|
||||||
Los títulos de grupos en todos los diálogos siguen el estilo de «Datos de la plantilla»: `text-xs font-medium uppercase tracking-wide text-muted-foreground`. Reutilizar `FormSectionTitle` de @repo/rdx-ui, también dentro de `FormSection`, conservando el nivel semántico del encabezado. La variante del contenedor no cambia este estilo. Los títulos principales del diálogo y los nombres editables de secciones conservan su tratamiento propio.
|
Todos los diálogos nuevos o modificados siguen el [patrón de diseño de cuadros de diálogo](docs/patron-de-dialogos.md), fuente normativa para títulos uniformes, separación entre grupos, distribución responsive, alturas iguales por fila, secciones plegables, filas compactas, ordenación accesible y selección múltiple. Reutilizar los componentes de @repo/rdx-ui indicados en el patrón. En todos los `PickerList`, mantener altura estable, columna `selection` compacta al principio y selección al pulsar zonas no interactivas de la fila. Aplicar cada pauta según el tipo de contenido; conservar los campos y restricciones propios de cada entidad.
|
||||||
Todos los diálogos de selección de tipo `PickerList` utilizan `FormDialog layout="picker"`: altura estable adaptada a la ventana, independiente del número de resultados. La tabla ocupa el espacio disponible con `EntityTable layout="fill"`; búsqueda, filtros, paginación y acciones quedan estables, y los resultados tienen desplazamiento interno con espacio reservado para la barra. En ventanas pequeñas se permite desplazamiento adicional del cuerpo para mantener accesibles todos los controles.
|
|
||||||
En los diálogos de selección de tipo `PickerList`, la columna del checkbox o radio se sitúa siempre al principio, antes de las columnas de datos, con identificador `selection`, cabecera solo visible para lectores de pantalla y anchura ajustada al control, próxima a la columna del nombre, sin ordenación ni posibilidad de ocultarla. No se trata como columna `actions` ni se ancla a la derecha. Pulsar cualquier punto no interactivo de una fila disponible debe activar/desactivar su checkbox, o seleccionar su radio en modo único. Reutilizar `onRowClick` de la tabla común, respetar las filas deshabilitadas y conservar la navegación por teclado del control. Pulsar directamente el checkbox, radio u otro control debe ejecutar solo su interacción propia, sin duplicar el cambio por propagación.
|
|
||||||
Separar componentes por responsabilidades reales, sin fragmentar JSX trivial.
|
Separar componentes por responsabilidades reales, sin fragmentar JSX trivial.
|
||||||
Antes de crear o modificar pantallas, reconocer activamente patrones visuales y de interacción comunes entre ellas, así como componentes repetidos dentro de una misma pantalla. Aplicar soluciones coherentes a todos los casos equivalentes, conservando las necesidades propias de cada entidad.
|
Antes de crear o modificar pantallas, reconocer activamente patrones visuales y de interacción comunes entre ellas, así como componentes repetidos dentro de una misma pantalla. Aplicar soluciones coherentes a todos los casos equivalentes, conservando las necesidades propias de cada entidad.
|
||||||
Reutilizar y extender la biblioteca visual existente antes de duplicar estructura, estilos o comportamiento. Extraer componentes compartidos cuando haya repetición real: cabeceras, listados, formularios modales, grupos de campos, selectores con alta, estados y ayuda contextual.
|
Reutilizar y extender la biblioteca visual existente antes de duplicar estructura, estilos o comportamiento. Extraer componentes compartidos cuando haya repetición real: cabeceras, listados, formularios modales, grupos de campos, selectores con alta, estados y ayuda contextual.
|
||||||
@ -224,6 +222,8 @@ Estas reglas complementan las convenciones y documentos de arquitectura existent
|
|||||||
|
|
||||||
#### Diálogos, modales y ventanas emergentes
|
#### Diálogos, modales y ventanas emergentes
|
||||||
|
|
||||||
|
El diseño visual y de interacción sigue [el patrón de diálogos](docs/patron-de-dialogos.md); las siguientes reglas definen la organización de sus componentes.
|
||||||
|
|
||||||
- Páginas y listados no deben implementar directamente el contenido de ventanas modales funcionales. Los diálogos de alta, edición, confirmación, selección o detalle son componentes independientes.
|
- Páginas y listados no deben implementar directamente el contenido de ventanas modales funcionales. Los diálogos de alta, edición, confirmación, selección o detalle son componentes independientes.
|
||||||
- Toda ventana modal, cuadro de diálogo o formulario modal con contenido y responsabilidad propios debe extraerse a un componente React independiente y vivir en su propio fichero `.tsx`.
|
- Toda ventana modal, cuadro de diálogo o formulario modal con contenido y responsabilidad propios debe extraerse a un componente React independiente y vivir en su propio fichero `.tsx`.
|
||||||
- Esta regla se aplica especialmente a componentes construidos con primitivas de shadcn/ui como `Dialog`, `AlertDialog`, `Sheet`, `Drawer` o componentes propios como `FormDialog`, pero no depende del nombre concreto utilizado.
|
- Esta regla se aplica especialmente a componentes construidos con primitivas de shadcn/ui como `Dialog`, `AlertDialog`, `Sheet`, `Drawer` o componentes propios como `FormDialog`, pero no depende del nombre concreto utilizado.
|
||||||
|
|||||||
@ -9,6 +9,10 @@ consumen mediante `@repo/rdx-ui`. Los componentes que conocen contratos, textos,
|
|||||||
reglas de Metis viven en el módulo o aplicación que los utiliza; nunca en ninguno de los dos
|
reglas de Metis viven en el módulo o aplicación que los utiliza; nunca en ninguno de los dos
|
||||||
paquetes genéricos.
|
paquetes genéricos.
|
||||||
|
|
||||||
|
El [patrón de diseño de cuadros de diálogo](patron-de-dialogos.md) es la fuente normativa
|
||||||
|
para componer grupos, columnas responsive, filas compactas y selectores. Esta guía describe
|
||||||
|
los componentes que lo implementan y sus contratos.
|
||||||
|
|
||||||
## Componentes actuales
|
## Componentes actuales
|
||||||
|
|
||||||
- Button: volver a comprobar la conexión.
|
- Button: volver a comprobar la conexión.
|
||||||
@ -495,7 +499,7 @@ URLs de vista previa se liberan al cambiar de archivo o desmontar el componente.
|
|||||||
|
|
||||||
El alta y la edición de plantillas de activos y recursos agrupan los datos generales con `FormSection` e incorporan campos desde las cabeceras de sus grupos. `TemplateSectionsEditor` permite secciones apiladas o una cuadrícula responsive mediante `sectionLayout="stack" | "responsive"`, con una cabecera numerada sobre fondo diferenciado que contiene el input del nombre, el asa de arrastre y las acciones de orden y eliminación. El cuerpo conserva la descripción y un área de campos separada por un borde. `TemplateSectionEditor` encapsula cada sección con `Collapsible`: todas empiezan abiertas y un botón de cabecera permite plegarlas con ratón o teclado. El nombre, el asa y las acciones siguen visibles al plegar. El contenido permanece montado para conservar los controles; si la validación nativa detecta un control inválido oculto, la sección se abre antes de que el navegador lo enfoque. La apertura se conserva por identidad al reordenar y no se guarda como parte de la plantilla. Los editores de activos y recursos usan la cuadrícula responsive: una columna hasta 1.000 px de ancho disponible y dos a partir de ese umbral, con separación de 16 px y tarjetas de igual altura en cada fila, determinada por la sección más alta. El orden recorre cada fila de izquierda a derecha y se conserva en el DOM, la numeración, el teclado y el arrastre; la altura de cada fila se recalcula al plegar o desplegar sus secciones, manteniendo ambas tarjetas con la misma altura. El bloque «Sin sección» permanece debajo, a ancho completo.
|
El alta y la edición de plantillas de activos y recursos agrupan los datos generales con `FormSection` e incorporan campos desde las cabeceras de sus grupos. `TemplateSectionsEditor` permite secciones apiladas o una cuadrícula responsive mediante `sectionLayout="stack" | "responsive"`, con una cabecera numerada sobre fondo diferenciado que contiene el input del nombre, el asa de arrastre y las acciones de orden y eliminación. El cuerpo conserva la descripción y un área de campos separada por un borde. `TemplateSectionEditor` encapsula cada sección con `Collapsible`: todas empiezan abiertas y un botón de cabecera permite plegarlas con ratón o teclado. El nombre, el asa y las acciones siguen visibles al plegar. El contenido permanece montado para conservar los controles; si la validación nativa detecta un control inválido oculto, la sección se abre antes de que el navegador lo enfoque. La apertura se conserva por identidad al reordenar y no se guarda como parte de la plantilla. Los editores de activos y recursos usan la cuadrícula responsive: una columna hasta 1.000 px de ancho disponible y dos a partir de ese umbral, con separación de 16 px y tarjetas de igual altura en cada fila, determinada por la sección más alta. El orden recorre cada fila de izquierda a derecha y se conserva en el DOM, la numeración, el teclado y el arrastre; la altura de cada fila se recalcula al plegar o desplegar sus secciones, manteniendo ambas tarjetas con la misma altura. El bloque «Sin sección» permanece debajo, a ancho completo.
|
||||||
|
|
||||||
`TemplateSectionFields` muestra un título traducido y el número de campos. Cada campo tiene fondo propio y separa su nombre y acciones de la configuración de obligatoriedad y asignación, cuyo selector conserva una etiqueta visible. Los campos se distribuyen en dos columnas a partir de 700 px de su propio grupo y en una para anchos menores; cada grupo establece su contenedor para evitar columnas demasiado estrechas cuando las secciones se muestran en paralelo. El bloque «Sin sección» se diferencia con borde discontinuo; las secciones vacías conservan un mensaje explícito. Se reutilizan los colores semánticos del tema para mantener el contraste en modo claro y oscuro.
|
`TemplateSectionFields` muestra un título traducido y el número de campos. Cada campo tiene fondo propio. En la presentación `cards`, separa su nombre y acciones de la configuración de obligatoriedad y asignación, cuyo selector conserva una etiqueta visible; los editores de plantillas de activos y recursos utilizan la presentación `compact` descrita más abajo. Los campos se distribuyen en dos columnas a partir de 700 px de su propio grupo y en una para anchos menores; cada grupo establece su contenedor para evitar columnas demasiado estrechas cuando las secciones se muestran en paralelo. El bloque «Sin sección» se diferencia con borde discontinuo; las secciones vacías conservan un mensaje explícito. Se reutilizan los colores semánticos del tema para mantener el contraste en modo claro y oscuro.
|
||||||
|
|
||||||
### Orden mediante arrastre y teclado
|
### Orden mediante arrastre y teclado
|
||||||
|
|
||||||
@ -537,10 +541,28 @@ El alta y la edición de plantillas de activos y recursos reutilizan `TemplateMe
|
|||||||
|
|
||||||
## Títulos de grupos en diálogos
|
## Títulos de grupos en diálogos
|
||||||
|
|
||||||
Todos los diálogos con grupos utilizan el estilo de «Datos de la plantilla»: texto pequeño, peso medio, mayúsculas, espaciado entre letras y color atenuado (`text-xs font-medium uppercase tracking-wide text-muted-foreground`). `FormSectionTitle` centraliza esta presentación y recibe `children`, `id` opcional y `as="h3" | "h4" | "h5"` para conservar la jerarquía semántica. El consumidor aporta el texto traducido; las mayúsculas se aplican mediante CSS.
|
Todos los diálogos con grupos utilizan el estilo de «Datos de la plantilla»: texto pequeño, seminegrita, mayúsculas, espaciado entre letras y color atenuado (`text-xs font-semibold uppercase tracking-wide text-muted-foreground mb-4`). `FormSectionTitle` centraliza esta presentación y recibe `children`, `id` opcional y `as="h3" | "h4" | "h5" | "legend" | "span"` para conservar la semántica del encabezado, leyenda o título dentro de un `summary`. El consumidor aporta el texto traducido; las mayúsculas se aplican mediante CSS. La variante `span` conserva la tipografía y deja el espaciado al `summary` que la contiene, sin margen inferior propio.
|
||||||
|
|
||||||
`FormSection`, `TemplateSectionsEditor` y `TemplateSectionFields` comparten este título, con independencia de sus bordes, acciones o contenido. La regla también se aplica a grupos de consulta, como los puestos del historial de perfiles. Los títulos principales del diálogo y los nombres editables de secciones mantienen su presentación propia.
|
`FormSection`, `TemplateSectionsEditor` y `TemplateSectionFields` comparten este título, con independencia de sus bordes, acciones o contenido. La regla también se aplica a grupos de consulta, como los puestos del historial de perfiles. Los títulos principales del diálogo y los nombres editables de secciones mantienen su presentación propia.
|
||||||
|
|
||||||
### Campos compactos en plantillas de activos y recursos
|
### Campos compactos en plantillas de activos y recursos
|
||||||
|
|
||||||
`TemplateSectionsEditor fieldLayout="compact"` presenta cada campo en una fila: asa de arrastre, switch de obligatoriedad, nombre flexible, flechas arriba/abajo y papelera. El nombre puede ocupar varias líneas en pantallas estrechas. El switch conserva una etiqueta accesible con «Obligatorio» y el nombre del campo; su texto visual y el selector de sección se omiten. Las notas de campos inactivos siguen disponibles para lectores de pantalla. Se mantienen el arrastre, las flechas por teclado, los límites de orden y los estados deshabilitados. Ambos editores utilizan esta presentación compacta.
|
`TemplateSectionsEditor fieldLayout="compact"` presenta cada campo en una fila: asa de arrastre, switch de obligatoriedad, nombre flexible, flechas arriba/abajo y papelera. El nombre puede ocupar varias líneas en pantallas estrechas. El switch conserva una etiqueta accesible con «Obligatorio» y el nombre del campo; su texto visual y el selector de sección se omiten. Las notas de campos inactivos siguen disponibles para lectores de pantalla. Se mantienen el arrastre, las flechas por teclado, los límites de orden y los estados deshabilitados. Ambos editores utilizan esta presentación compacta.
|
||||||
|
|
||||||
|
## Ajustes comunes tras la revisión de diálogos
|
||||||
|
|
||||||
|
`FormBody` establece su propio contenedor de consultas de tamaño: las cuadrículas de formularios
|
||||||
|
y subcontroles utilizan `@min-[560px]` para decidir sus dos columnas según el ancho real del
|
||||||
|
modal. `FieldSections` aplica la misma regla a los campos y estira los grupos de cada fila
|
||||||
|
a la altura de su vecino más alto. Los títulos de consulta, grupos de permisos y secciones
|
||||||
|
desplegables reutilizan `FormSectionTitle`, conservando el estilo vigente del componente.
|
||||||
|
|
||||||
|
`FormActions` admite `cancelDisabled` para conservar el bloqueo de cancelación durante una
|
||||||
|
confirmación pendiente, además de `leadingActions` para acciones opcionales a la izquierda.
|
||||||
|
Las confirmaciones de marcas y definiciones de catálogo usan `FormBody` y `FormActions` para
|
||||||
|
mantener su pie fuera del área desplazable. La selección de compañía del dashboard también
|
||||||
|
sitúa Continuar en ese pie y lo deshabilita mientras falta una selección o una consulta correcta.
|
||||||
|
|
||||||
|
`SoftwareInstallationEditorDialog`, `SoftwareResourceCreateDialog` y `EntityTableColumnsDialog`
|
||||||
|
encapsulan sus interacciones en ficheros independientes. Sus consumidores conservan apertura,
|
||||||
|
selección, datos y callbacks; los formularios y controles mantienen sus contratos anteriores.
|
||||||
|
|||||||
@ -1,177 +1,171 @@
|
|||||||
# Inventario de interacciones modales
|
# Inventario de interacciones modales
|
||||||
|
|
||||||
Revisión de 2026-10-08. Se inspeccionaron las primitivas y wrappers importados, sus consumidores,
|
Revisión de código de 2026-10-15 según el [patrón de diseño de diálogos](patron-de-dialogos.md).
|
||||||
los bloques condicionales y las implementaciones manuales posibles: Dialog, AlertDialog, Sheet,
|
Se buscaron constructores `FormDialog`, `ConfirmDeleteDialog` y `FieldPickerDialog`, además de
|
||||||
Drawer, FormDialog, portales, elementos dialog, aria-modal y roles dialog/alertdialog.
|
primitivas `Dialog`, `AlertDialog`, `Sheet`, `Drawer`, portales y diálogos manuales. Se revisaron
|
||||||
La búsqueda no dependió del nombre de los componentes.
|
los formularios, controles compartidos y contenido de consulta que reciben estos constructores.
|
||||||
|
|
||||||
El código propio contiene **81 componentes que construyen una interacción modal**. Todos
|
El código propio contiene **81 componentes modales en módulos** y **3 constructores
|
||||||
usan FormDialog de @repo/rdx-ui y tienen un componente principal por fichero. Un componente
|
compartidos en @repo/rdx-ui**. El recuento identifica componentes, no cada variante de alta,
|
||||||
puede representar variantes de alta, edición o copia, y catálogos con distintos tipos; el número
|
edición, copia o estado. Las dos variantes de CatalogFieldPickerDialog son adaptadores del
|
||||||
no es un recuento de todos los estados posibles de las ventanas. No se encontraron otras
|
mismo selector genérico. El código vendor de `packages/shadcn-ui` queda fuera de esta revisión
|
||||||
implementaciones propias con AlertDialog, Sheet, Drawer ni diálogos HTML/manuales.
|
y no se modifica.
|
||||||
|
|
||||||
Actualización de 2026-10-09: se añade DeleteBrandDialog para confirmar la eliminación de
|
## Resultado de la revisión
|
||||||
marcas personalizadas; el inventario pasa a 82 componentes. Se ha comprobado esta operación
|
|
||||||
en navegador con API simulada: Cancelar/Escape, errores, bloqueo durante el envío, éxito,
|
|
||||||
español/inglés y anchura móvil. Esta comprobación no amplía la revisión al resto de modales.
|
|
||||||
|
|
||||||
La implementación vendor de packages/shadcn-ui queda fuera de la refactorización según AGENTS.md.
|
- Los diálogos utilizan la estructura común de cabecera, cuerpo desplazable y pie. Las
|
||||||
Sus primitivas y el diálogo de configuración de tablas se consultan y consumen, no se duplican
|
confirmaciones de marcas y catálogo se han adaptado al mismo pie fijo, conservando el
|
||||||
ni reorganizan. Los popovers de selectores y menús de acciones no se contabilizan como formularios
|
bloqueo durante el envío y la presentación de errores.
|
||||||
o ventanas modales funcionales.
|
- Los formularios y sus subcontroles calculan las columnas según su contenedor. Los títulos
|
||||||
|
de grupos de consulta, permisos y secciones comparten el estilo de Datos de la plantilla.
|
||||||
|
- Las secciones de plantillas conservan dos columnas cuando hay espacio, igual altura por
|
||||||
|
fila, plegado, campos compactos, ordenación accesible e incorporación de campos por grupo.
|
||||||
|
- AssetPickerDialog y los dos CatalogFieldPickerDialog mantienen altura estable, selección
|
||||||
|
compacta al principio, selección desde la fila y confirmación provisional. Los diálogos
|
||||||
|
de configuración de columnas son ajustes, no selectores de entidades PickerList.
|
||||||
|
- Se extrajeron los diálogos de edición de instalaciones de software, alta de su recurso y
|
||||||
|
configuración de columnas. Los padres mantienen apertura y datos, sin definir el modal.
|
||||||
|
- Las variantes de selección de compañía del dashboard colocan Continuar en el pie común.
|
||||||
|
Los formularios sencillos, historiales y ayudas mantienen su tamaño según contenido; las
|
||||||
|
fechas de revisión y nombres de registros conservan su tipografía de datos, no de grupos.
|
||||||
|
- Se conservan los pasos de alta de recursos ya existentes: no se ha añadido ningún asistente
|
||||||
|
para resolver únicamente espacio. Tampoco se añaden cuadrículas a formularios sencillos.
|
||||||
|
|
||||||
## Responsabilidades y consumidores
|
## Componentes revisados
|
||||||
|
|
||||||
Las páginas y listados conservan apertura, selección y callbacks. Los componentes enumerados
|
Las rutas siguen la [estructura por entidades y operaciones](estructura-web-por-entidades-y-operaciones.md).
|
||||||
encapsulan título, disposición, anchura, contenido y cierre. Los formularios existentes mantienen
|
La columna Composición indica el constructor utilizado; las variantes heredan sus reglas comunes.
|
||||||
sus schemas, borradores, mutaciones, ayuda y errores. EndRelationshipDialog posee además la
|
|
||||||
mutación de confirmación y sus estados pendientes y de error.
|
|
||||||
|
|
||||||
CompanyEditorDialog, PersonEditorDialog, DepartmentEditorDialog y SiteEditorDialog se reutilizan
|
### assets (23)
|
||||||
el patrón entre catálogo y selector. Los callbacks de selección permanecen en el consumidor que
|
|
||||||
posee el borrador. Las altas anidadas conservan sus restricciones existentes.
|
|
||||||
|
|
||||||
Se integró el contenido exclusivo de los antiguos SupportHistory, ApplicationHistory,
|
| Componente | Composición |
|
||||||
CertificateHistory y PrinterHistory en sus respectivos componentes de diálogo, tras comprobar
|
| --- | --- |
|
||||||
sus referencias. No se eliminaron historiales ni acciones. Los archivos anteriores se pueden
|
| [EndRelationshipDialog](../modules/assets/src/web/asset-relationships/end/ui/end-relationship-dialog.tsx) | `FormDialog` |
|
||||||
recuperar desde Git.
|
| [RelationshipEditorDialog](../modules/assets/src/web/asset-relationships/shared/ui/relationship-editor-dialog.tsx) | `FormDialog` |
|
||||||
|
| [WorkstationAssetCreateDialog](../modules/assets/src/web/assets/create/ui/workstation-asset-create-dialog.tsx) | `FormDialog` |
|
||||||
|
| [AssetDetailDialog](../modules/assets/src/web/assets/detail/ui/asset-detail-dialog.tsx) | `FormDialog` |
|
||||||
|
| [AssetPickerDialog](../modules/assets/src/web/assets/select/ui/asset-picker-dialog.tsx) | `FormDialog` · altura estable |
|
||||||
|
| [AssetEditorDialog](../modules/assets/src/web/assets/shared/ui/asset-editor-dialog.tsx) | `FormDialog` |
|
||||||
|
| [DeleteAssetDialog](../modules/assets/src/web/assets/shared/ui/delete-asset-dialog.tsx) | `ConfirmDeleteDialog` |
|
||||||
|
| [DeleteBrandDialog](../modules/assets/src/web/brands/delete/ui/delete-brand-dialog.tsx) | `FormDialog` |
|
||||||
|
| [BrandEditorDialog](../modules/assets/src/web/brands/shared/ui/brand-editor-dialog.tsx) | `FormDialog` |
|
||||||
|
| [DeleteCatalogDefinitionDialog](../modules/assets/src/web/catalog/delete/ui/delete-catalog-definition-dialog.tsx) | `FormDialog` |
|
||||||
|
| [CatalogDefinitionDialog](../modules/assets/src/web/catalog/detail/ui/catalog-definition-dialog.tsx) | `FormDialog` |
|
||||||
|
| [CatalogEditorDialog](../modules/assets/src/web/catalog/shared/ui/catalog-editor-dialog.tsx) | `FormDialog` |
|
||||||
|
| [CatalogFieldCreateDialog](../modules/assets/src/web/catalog-fields/create/ui/catalog-field-create-dialog.tsx) | `FormDialog` |
|
||||||
|
| [CatalogFieldPickerDialog](../modules/assets/src/web/catalog-fields/select/ui/catalog-field-picker-dialog.tsx) | `FieldPickerDialog` · altura estable |
|
||||||
|
| [CatalogRelationCreateDialog](../modules/assets/src/web/catalog-relations/create/ui/catalog-relation-create-dialog.tsx) | `FormDialog` |
|
||||||
|
| [CatalogTemplateCreateDialog](../modules/assets/src/web/catalog-templates/create/ui/catalog-template-create-dialog.tsx) | `FormDialog` |
|
||||||
|
| [DeleteRecordDialog](../modules/assets/src/web/shared/ui/delete-record-dialog.tsx) | `ConfirmDeleteDialog` |
|
||||||
|
| [SupportHistoryDialog](../modules/assets/src/web/support-connections/history/ui/support-history-dialog.tsx) | `FormDialog` |
|
||||||
|
| [SupportConnectionEditorDialog](../modules/assets/src/web/support-connections/shared/ui/support-connection-editor-dialog.tsx) | `FormDialog` |
|
||||||
|
| [SupportResourceCreateDialog](../modules/assets/src/web/support-connections/shared/ui/support-resource-create-dialog.tsx) | `FormDialog` |
|
||||||
|
| [WorkstationHistoryDialog](../modules/assets/src/web/workstations/history/ui/workstation-history-dialog.tsx) | `FormDialog` |
|
||||||
|
| [WorkstationEditorDialog](../modules/assets/src/web/workstations/shared/ui/workstation-editor-dialog.tsx) | `FormDialog` |
|
||||||
|
| [WorkstationLifecycleDialog](../modules/assets/src/web/workstations/shared/ui/workstation-lifecycle-dialog.tsx) | `ConfirmDeleteDialog` |
|
||||||
|
|
||||||
## Componentes por área
|
### companies (49)
|
||||||
|
|
||||||
Las rutas reflejan la [estructura por entidades y operaciones](estructura-web-por-entidades-y-operaciones.md).
|
| Componente | Composición |
|
||||||
|
| --- | --- |
|
||||||
|
| [AccessQuickEditor](../modules/companies/src/web/access/create/ui/access-quick-editor.tsx) | `FormDialog` |
|
||||||
|
| [QuickCreateDialog](../modules/companies/src/web/access/create/ui/quick-create-dialog.tsx) | `FormDialog` |
|
||||||
|
| [AccessHelpDialog](../modules/companies/src/web/access/help/ui/access-help-dialog.tsx) | `FormDialog` |
|
||||||
|
| [AccessHistory](../modules/companies/src/web/access/history/ui/access-history.tsx) | `FormDialog` |
|
||||||
|
| [AccessEditorDialog](../modules/companies/src/web/access/shared/ui/access-editor-dialog.tsx) | `FormDialog` |
|
||||||
|
| [ApplicationQuickEditor](../modules/companies/src/web/application-configurations/create/ui/application-quick-editor.tsx) | `FormDialog` |
|
||||||
|
| [ApplicationHistoryDialog](../modules/companies/src/web/application-configurations/history/ui/application-history-dialog.tsx) | `FormDialog` |
|
||||||
|
| [ApplicationConfigurationEditorDialog](../modules/companies/src/web/application-configurations/shared/ui/application-configuration-editor-dialog.tsx) | `FormDialog` |
|
||||||
|
| [AssignmentHistoryDialog](../modules/companies/src/web/assignments/history/ui/assignment-history-dialog.tsx) | `FormDialog` |
|
||||||
|
| [AssignmentEditorDialog](../modules/companies/src/web/assignments/shared/ui/assignment-editor-dialog.tsx) | `FormDialog` |
|
||||||
|
| [CertificateQuickEditor](../modules/companies/src/web/certificate-configurations/create/ui/certificate-quick-editor.tsx) | `FormDialog` |
|
||||||
|
| [CertificateHistoryDialog](../modules/companies/src/web/certificate-configurations/history/ui/certificate-history-dialog.tsx) | `FormDialog` |
|
||||||
|
| [CertificateConfigurationEditorDialog](../modules/companies/src/web/certificate-configurations/shared/ui/certificate-configuration-editor-dialog.tsx) | `FormDialog` |
|
||||||
|
| [InventoryQuickCreateDialog](../modules/companies/src/web/companies/detail/ui/inventory-quick-create-dialog.tsx) | `FormDialog` |
|
||||||
|
| [CompanyEditorDialog](../modules/companies/src/web/companies/shared/ui/company-editor-dialog.tsx) | `FormDialog` |
|
||||||
|
| [DashboardQuickCreate](../modules/companies/src/web/dashboard/create/ui/dashboard-quick-create.tsx) | `FormDialog` |
|
||||||
|
| [DashboardDetailsDialog](../modules/companies/src/web/dashboard/detail/ui/dashboard-details-dialog.tsx) | `FormDialog` |
|
||||||
|
| [DepartmentEditorDialog](../modules/companies/src/web/departments/shared/ui/department-editor-dialog.tsx) | `FormDialog` |
|
||||||
|
| [FolderQuickEditor](../modules/companies/src/web/folder-mappings/create/ui/folder-quick-editor.tsx) | `FormDialog` |
|
||||||
|
| [FolderMappingHistoryDialog](../modules/companies/src/web/folder-mappings/history/ui/folder-mapping-history-dialog.tsx) | `FormDialog` |
|
||||||
|
| [FolderMappingEditorDialog](../modules/companies/src/web/folder-mappings/shared/ui/folder-mapping-editor-dialog.tsx) | `FormDialog` |
|
||||||
|
| [MailQuickEditor](../modules/companies/src/web/mail-configurations/create/ui/mail-quick-editor.tsx) | `FormDialog` |
|
||||||
|
| [MailConfigurationHistoryDialog](../modules/companies/src/web/mail-configurations/history/ui/mail-configuration-history-dialog.tsx) | `FormDialog` |
|
||||||
|
| [MailConfigurationEditorDialog](../modules/companies/src/web/mail-configurations/shared/ui/mail-configuration-editor-dialog.tsx) | `FormDialog` |
|
||||||
|
| [MailboxUseEditorDialog](../modules/companies/src/web/mailbox-uses/shared/ui/mailbox-use-editor-dialog.tsx) | `FormDialog` |
|
||||||
|
| [MailboxCreateDialog](../modules/companies/src/web/mailboxes/create/ui/mailbox-create-dialog.tsx) | `FormDialog` |
|
||||||
|
| [PersonEditorDialog](../modules/companies/src/web/people/shared/ui/person-editor-dialog.tsx) | `FormDialog` |
|
||||||
|
| [PrinterProfileCreateDialog](../modules/companies/src/web/printer-configurations/create/ui/printer-profile-create-dialog.tsx) | `FormDialog` |
|
||||||
|
| [PrinterHistoryDialog](../modules/companies/src/web/printer-configurations/history/ui/printer-history-dialog.tsx) | `FormDialog` |
|
||||||
|
| [PrinterConfigurationEditorDialog](../modules/companies/src/web/printer-configurations/shared/ui/printer-configuration-editor-dialog.tsx) | `FormDialog` |
|
||||||
|
| [ProcessTemplateEditorDialog](../modules/companies/src/web/process-templates/shared/ui/process-template-editor-dialog.tsx) | `FormDialog` |
|
||||||
|
| [ProcessQuickCreateDialog](../modules/companies/src/web/processes/create/ui/process-quick-create-dialog.tsx) | `FormDialog` |
|
||||||
|
| [ProcessStartDialog](../modules/companies/src/web/processes/start/ui/process-start-dialog.tsx) | `FormDialog` |
|
||||||
|
| [ProcessEditDialog](../modules/companies/src/web/processes/update/ui/process-edit-dialog.tsx) | `FormDialog` |
|
||||||
|
| [RemoteQuickEditor](../modules/companies/src/web/remote-connections/create/ui/remote-quick-editor.tsx) | `FormDialog` |
|
||||||
|
| [RemoteConnectionHistoryDialog](../modules/companies/src/web/remote-connections/history/ui/remote-connection-history-dialog.tsx) | `FormDialog` |
|
||||||
|
| [RemoteConnectionEditorDialog](../modules/companies/src/web/remote-connections/shared/ui/remote-connection-editor-dialog.tsx) | `FormDialog` |
|
||||||
|
| [ConfigurationQuickEditor](../modules/companies/src/web/shared/ui/configuration-quick-editor.tsx) | `FormDialog` |
|
||||||
|
| [DeleteRecordDialog](../modules/companies/src/web/shared/ui/delete-record-dialog.tsx) | `ConfirmDeleteDialog` |
|
||||||
|
| [SiteEditorDialog](../modules/companies/src/web/sites/shared/ui/site-editor-dialog.tsx) | `FormDialog` |
|
||||||
|
| [SoftwareResourceCreateDialog](../modules/companies/src/web/software-installations/create/ui/software-resource-create-dialog.tsx) | `FormDialog` |
|
||||||
|
| [SoftwareHistoryDialog](../modules/companies/src/web/software-installations/history/ui/software-history-dialog.tsx) | `FormDialog` |
|
||||||
|
| [SoftwareInstallationEditorDialog](../modules/companies/src/web/software-installations/shared/ui/software-installation-editor-dialog.tsx) | `FormDialog` |
|
||||||
|
| [TeleworkAuthorizationHistoryDialog](../modules/companies/src/web/telework-authorizations/history/ui/telework-authorization-history-dialog.tsx) | `FormDialog` |
|
||||||
|
| [TeleworkAuthorizationEditorDialog](../modules/companies/src/web/telework-authorizations/shared/ui/telework-authorization-editor-dialog.tsx) | `FormDialog` |
|
||||||
|
| [WorkProfileHistoryDialog](../modules/companies/src/web/work-profiles/history/ui/work-profile-history-dialog.tsx) | `FormDialog` |
|
||||||
|
| [WorkProfileEditorDialog](../modules/companies/src/web/work-profiles/shared/ui/work-profile-editor-dialog.tsx) | `FormDialog` |
|
||||||
|
| [WorkProfileLifecycleDialog](../modules/companies/src/web/work-profiles/shared/ui/work-profile-lifecycle-dialog.tsx) | `ConfirmDeleteDialog` |
|
||||||
|
| [WorkspaceSettingsDialog](../modules/companies/src/web/workspace/settings/ui/workspace-settings-dialog.tsx) | `FormDialog` |
|
||||||
|
|
||||||
### modules/assets/src/web
|
### identity (2)
|
||||||
|
|
||||||
- [AssetDetailDialog](../modules/assets/src/web/assets/detail/ui/asset-detail-dialog.tsx)
|
| Componente | Composición |
|
||||||
- [AssetEditorDialog](../modules/assets/src/web/assets/shared/ui/asset-editor-dialog.tsx)
|
| --- | --- |
|
||||||
- [BrandEditorDialog](../modules/assets/src/web/brands/shared/ui/brand-editor-dialog.tsx)
|
| [AccountPasswordDialog](../modules/identity/src/web/passwords/change/ui/account-password-dialog.tsx) | `FormDialog` |
|
||||||
- [DeleteBrandDialog](../modules/assets/src/web/brands/delete/ui/delete-brand-dialog.tsx)
|
| [UserEditorDialog](../modules/identity/src/web/users/shared/ui/user-editor-dialog.tsx) | `FormDialog` |
|
||||||
- [CatalogDefinitionDialog](../modules/assets/src/web/catalog/detail/ui/catalog-definition-dialog.tsx)
|
|
||||||
- [CatalogEditorDialog](../modules/assets/src/web/catalog/shared/ui/catalog-editor-dialog.tsx)
|
|
||||||
- [CatalogFieldCreateDialog](../modules/assets/src/web/catalog-fields/create/ui/catalog-field-create-dialog.tsx)
|
|
||||||
- [CatalogFieldPickerDialog](../modules/assets/src/web/catalog-fields/select/ui/catalog-field-picker-dialog.tsx): selección múltiple provisional para añadir campos a una plantilla; reutiliza la tabla común y confirma el lote antes de cambiar el borrador.
|
|
||||||
- [CatalogRelationCreateDialog](../modules/assets/src/web/catalog-relations/create/ui/catalog-relation-create-dialog.tsx)
|
|
||||||
- [CatalogTemplateCreateDialog](../modules/assets/src/web/catalog-templates/create/ui/catalog-template-create-dialog.tsx)
|
|
||||||
- [EndRelationshipDialog](../modules/assets/src/web/asset-relationships/end/ui/end-relationship-dialog.tsx)
|
|
||||||
- [RelationshipEditorDialog](../modules/assets/src/web/asset-relationships/shared/ui/relationship-editor-dialog.tsx)
|
|
||||||
- [SupportConnectionEditorDialog](../modules/assets/src/web/support-connections/shared/ui/support-connection-editor-dialog.tsx)
|
|
||||||
- [SupportHistoryDialog](../modules/assets/src/web/support-connections/history/ui/support-history-dialog.tsx)
|
|
||||||
- [WorkstationAssetCreateDialog](../modules/assets/src/web/assets/create/ui/workstation-asset-create-dialog.tsx)
|
|
||||||
- [WorkstationEditorDialog](../modules/assets/src/web/workstations/shared/ui/workstation-editor-dialog.tsx)
|
|
||||||
- [WorkstationHistoryDialog](../modules/assets/src/web/workstations/history/ui/workstation-history-dialog.tsx)
|
|
||||||
|
|
||||||
### modules/companies/src/web/access
|
### resources (7)
|
||||||
|
|
||||||
- [AccessEditorDialog](../modules/companies/src/web/access/shared/ui/access-editor-dialog.tsx)
|
| Componente | Composición |
|
||||||
- [AccessHelpDialog](../modules/companies/src/web/access/help/ui/access-help-dialog.tsx)
|
| --- | --- |
|
||||||
- [AccessHistory](../modules/companies/src/web/access/history/ui/access-history.tsx)
|
| [CatalogDefinitionDialog](../modules/resources/src/web/catalog/detail/ui/catalog-definition-dialog.tsx) | `FormDialog` |
|
||||||
- [AccessQuickEditor](../modules/companies/src/web/access/create/ui/access-quick-editor.tsx)
|
| [CatalogEditorDialog](../modules/resources/src/web/catalog/shared/ui/catalog-editor-dialog.tsx) | `FormDialog` |
|
||||||
- [QuickCreateDialog](../modules/companies/src/web/access/create/ui/quick-create-dialog.tsx)
|
| [CatalogFieldCreateDialog](../modules/resources/src/web/catalog-fields/create/ui/catalog-field-create-dialog.tsx) | `FormDialog` |
|
||||||
|
| [CatalogFieldPickerDialog](../modules/resources/src/web/catalog-fields/select/ui/catalog-field-picker-dialog.tsx) | `FieldPickerDialog` · altura estable |
|
||||||
|
| [CatalogTemplateCreateDialog](../modules/resources/src/web/catalog-templates/create/ui/catalog-template-create-dialog.tsx) | `FormDialog` |
|
||||||
|
| [ResourceDetailDialog](../modules/resources/src/web/resources/detail/ui/resource-detail-dialog.tsx) | `FormDialog` |
|
||||||
|
| [ResourceEditorDialog](../modules/resources/src/web/resources/shared/ui/resource-editor-dialog.tsx) | `FormDialog` |
|
||||||
|
|
||||||
### modules/companies/src/web/application-configurations
|
### rdx-ui (3)
|
||||||
|
|
||||||
- [ApplicationConfigurationEditorDialog](../modules/companies/src/web/application-configurations/shared/ui/application-configuration-editor-dialog.tsx)
|
| Componente | Composición |
|
||||||
- [ApplicationHistoryDialog](../modules/companies/src/web/application-configurations/history/ui/application-history-dialog.tsx)
|
| --- | --- |
|
||||||
- [ApplicationQuickEditor](../modules/companies/src/web/application-configurations/create/ui/application-quick-editor.tsx)
|
| [ConfirmDeleteDialog](../packages/rdx-ui/src/components/confirm-delete-dialog.tsx) | `FormDialog` |
|
||||||
|
| [EntityTableColumnsDialog](../packages/rdx-ui/src/components/entity-table-columns-dialog.tsx) | `FormDialog` |
|
||||||
|
| [FieldPickerDialog](../packages/rdx-ui/src/components/field-picker-dialog.tsx) | `FormDialog` · altura estable |
|
||||||
|
|
||||||
### modules/companies/src/web/certificate-configurations
|
## Validación y límites
|
||||||
|
|
||||||
- [CertificateConfigurationEditorDialog](../modules/companies/src/web/certificate-configurations/shared/ui/certificate-configuration-editor-dialog.tsx)
|
La revisión cubre código y render estático mediante las pruebas existentes. Las comprobaciones
|
||||||
- [CertificateHistoryDialog](../modules/companies/src/web/certificate-configurations/history/ui/certificate-history-dialog.tsx)
|
interactivas de apertura, Escape, restauración de foco, arrastre, envío, errores y altura visual
|
||||||
- [CertificateQuickEditor](../modules/companies/src/web/certificate-configurations/create/ui/certificate-quick-editor.tsx)
|
en español/inglés y anchos estrecho/amplio quedan pendientes de una sesión de navegador.
|
||||||
|
No se dispone de una herramienta de navegador en esta sesión; los análisis de código, tipos,
|
||||||
|
formato y pruebas no sustituyen esa revisión visual.
|
||||||
|
|
||||||
### Compañías, organización, dashboard y workspace de companies
|
Resultados de los comandos ejecutados:
|
||||||
|
|
||||||
- [AssignmentEditorDialog](../modules/companies/src/web/assignments/shared/ui/assignment-editor-dialog.tsx)
|
| Comprobación | Resultado |
|
||||||
- [AssignmentHistoryDialog](../modules/companies/src/web/assignments/history/ui/assignment-history-dialog.tsx)
|
| --- | --- |
|
||||||
- [CompanyEditorDialog](../modules/companies/src/web/companies/shared/ui/company-editor-dialog.tsx)
|
| Typecheck dirigido a rdx-ui, assets, resources, companies e identity | Correcto |
|
||||||
- [ConfigurationQuickEditor](../modules/companies/src/web/shared/ui/configuration-quick-editor.tsx)
|
| Biome y ESLint dirigidos a los archivos de código modificados | Correctos |
|
||||||
- [DashboardDetailsDialog](../modules/companies/src/web/dashboard/detail/ui/dashboard-details-dialog.tsx)
|
| `pnpm build` | Correcto; conserva el aviso de chunks grandes |
|
||||||
- [DashboardQuickCreate](../modules/companies/src/web/dashboard/create/ui/dashboard-quick-create.tsx)
|
| `pnpm test` | UI, activos, recursos, identidad, core y DDD correctos; empresas mantiene el fallo previo de `companies-page.test.tsx` |
|
||||||
- [DepartmentEditorDialog](../modules/companies/src/web/departments/shared/ui/department-editor-dialog.tsx)
|
| `pnpm typecheck` | Mantiene TS2375 previo en `packages/shadcn-ui/src/components/calendar.tsx:165` |
|
||||||
- [InventoryQuickCreateDialog](../modules/companies/src/web/companies/detail/ui/inventory-quick-create-dialog.tsx)
|
| `pnpm lint` / `pnpm check` | Mantienen respectivamente 14 y 155 errores previos ajenos al cambio |
|
||||||
- [PersonEditorDialog](../modules/companies/src/web/people/shared/ui/person-editor-dialog.tsx)
|
| `git diff --check` y enlaces locales de la documentación | Correctos |
|
||||||
- [SiteEditorDialog](../modules/companies/src/web/sites/shared/ui/site-editor-dialog.tsx)
|
|
||||||
- [WorkspaceSettingsDialog](../modules/companies/src/web/workspace/settings/ui/workspace-settings-dialog.tsx)
|
|
||||||
|
|
||||||
### modules/companies/src/web/folder-mappings
|
El fallo de empresas se reprodujo ejecutando directamente el fichero: una prueba de persona
|
||||||
|
espera el texto «Esta persona no tiene acceso a Metis Desk», ausente en el render actual.
|
||||||
- [FolderMappingEditorDialog](../modules/companies/src/web/folder-mappings/shared/ui/folder-mapping-editor-dialog.tsx)
|
No se ha modificado esa prueba ni el contenido de la ayuda para ocultar el fallo.
|
||||||
- [FolderMappingHistoryDialog](../modules/companies/src/web/folder-mappings/history/ui/folder-mapping-history-dialog.tsx)
|
Los módulos de tickets, intervenciones y sus plantillas permanecen [archivados](funcionalidades-archivadas.md).
|
||||||
- [FolderQuickEditor](../modules/companies/src/web/folder-mappings/create/ui/folder-quick-editor.tsx)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Buzones, usos y configuraciones de correo de companies
|
|
||||||
|
|
||||||
- [MailboxCreateDialog](../modules/companies/src/web/mailboxes/create/ui/mailbox-create-dialog.tsx)
|
|
||||||
- [MailboxUseEditorDialog](../modules/companies/src/web/mailbox-uses/shared/ui/mailbox-use-editor-dialog.tsx)
|
|
||||||
- [MailConfigurationEditorDialog](../modules/companies/src/web/mail-configurations/shared/ui/mail-configuration-editor-dialog.tsx)
|
|
||||||
- [MailConfigurationHistoryDialog](../modules/companies/src/web/mail-configurations/history/ui/mail-configuration-history-dialog.tsx)
|
|
||||||
- [MailQuickEditor](../modules/companies/src/web/mail-configurations/create/ui/mail-quick-editor.tsx)
|
|
||||||
|
|
||||||
### modules/companies/src/web/printer-configurations
|
|
||||||
|
|
||||||
- [PrinterConfigurationEditorDialog](../modules/companies/src/web/printer-configurations/shared/ui/printer-configuration-editor-dialog.tsx)
|
|
||||||
- [PrinterHistoryDialog](../modules/companies/src/web/printer-configurations/history/ui/printer-history-dialog.tsx)
|
|
||||||
- [PrinterProfileCreateDialog](../modules/companies/src/web/printer-configurations/create/ui/printer-profile-create-dialog.tsx)
|
|
||||||
|
|
||||||
### modules/companies/src/web/processes
|
|
||||||
|
|
||||||
- [ProcessEditDialog](../modules/companies/src/web/processes/update/ui/process-edit-dialog.tsx)
|
|
||||||
- [ProcessQuickCreateDialog](../modules/companies/src/web/processes/create/ui/process-quick-create-dialog.tsx)
|
|
||||||
- [ProcessStartDialog](../modules/companies/src/web/processes/start/ui/process-start-dialog.tsx)
|
|
||||||
|
|
||||||
### modules/companies/src/web/process-templates
|
|
||||||
|
|
||||||
- [ProcessTemplateEditorDialog](../modules/companies/src/web/process-templates/shared/ui/process-template-editor-dialog.tsx)
|
|
||||||
|
|
||||||
### modules/companies/src/web/work-profiles
|
|
||||||
|
|
||||||
- [WorkProfileEditorDialog](../modules/companies/src/web/work-profiles/shared/ui/work-profile-editor-dialog.tsx)
|
|
||||||
- [WorkProfileHistoryDialog](../modules/companies/src/web/work-profiles/history/ui/work-profile-history-dialog.tsx)
|
|
||||||
|
|
||||||
### modules/companies/src/web/remote-connections
|
|
||||||
|
|
||||||
- [RemoteConnectionEditorDialog](../modules/companies/src/web/remote-connections/shared/ui/remote-connection-editor-dialog.tsx)
|
|
||||||
- [RemoteConnectionHistoryDialog](../modules/companies/src/web/remote-connections/history/ui/remote-connection-history-dialog.tsx)
|
|
||||||
- [RemoteQuickEditor](../modules/companies/src/web/remote-connections/create/ui/remote-quick-editor.tsx)
|
|
||||||
|
|
||||||
### modules/companies/src/web/telework-authorizations
|
|
||||||
|
|
||||||
- [TeleworkAuthorizationEditorDialog](../modules/companies/src/web/telework-authorizations/shared/ui/telework-authorization-editor-dialog.tsx)
|
|
||||||
- [TeleworkAuthorizationHistoryDialog](../modules/companies/src/web/telework-authorizations/history/ui/telework-authorization-history-dialog.tsx)
|
|
||||||
|
|
||||||
|
|
||||||
### modules/identity/src/web
|
|
||||||
|
|
||||||
- [AccountPasswordDialog](../modules/identity/src/web/passwords/change/ui/account-password-dialog.tsx)
|
|
||||||
- [UserEditorDialog](../modules/identity/src/web/users/shared/ui/user-editor-dialog.tsx)
|
|
||||||
- [UserManagementDialog](../modules/identity/src/web/users/list/ui/user-management-dialog.tsx)
|
|
||||||
|
|
||||||
### modules/resources/src/web
|
|
||||||
|
|
||||||
- [CatalogDefinitionDialog](../modules/resources/src/web/catalog/detail/ui/catalog-definition-dialog.tsx)
|
|
||||||
- [CatalogEditorDialog](../modules/resources/src/web/catalog/shared/ui/catalog-editor-dialog.tsx)
|
|
||||||
- [CatalogFieldCreateDialog](../modules/resources/src/web/catalog-fields/create/ui/catalog-field-create-dialog.tsx)
|
|
||||||
- [CatalogFieldPickerDialog](../modules/resources/src/web/catalog-fields/select/ui/catalog-field-picker-dialog.tsx): adaptador del selector compartido para incorporar campos al destino y ofrecer alta rápida configurable.
|
|
||||||
- [CatalogTemplateCreateDialog](../modules/resources/src/web/catalog-templates/create/ui/catalog-template-create-dialog.tsx)
|
|
||||||
- [ResourceDetailDialog](../modules/resources/src/web/resources/detail/ui/resource-detail-dialog.tsx)
|
|
||||||
- [ResourceEditorDialog](../modules/resources/src/web/resources/shared/ui/resource-editor-dialog.tsx)
|
|
||||||
|
|
||||||
## Comprobaciones y límites
|
|
||||||
|
|
||||||
Typecheck, lint, pruebas de los módulos y validaciones globales correctos. Las pruebas existentes
|
|
||||||
comprueban traducciones es/en, listados sin formularios visibles, ayuda, restricciones de altas
|
|
||||||
anidadas, estados y contratos. Se amplió la prueba del dashboard con datos no vacíos de los
|
|
||||||
paneles extraídos.
|
|
||||||
|
|
||||||
No se ejecutó una sesión interactiva de navegador. Queda por comprobar manualmente apertura,
|
|
||||||
Escape, restauración del foco, cancelación, envío y error en modales simples y anidados, en español,
|
|
||||||
inglés y anchura móvil. El render estático y el lint no sustituyen esa comprobación.
|
|
||||||
|
|
||||||
Véase [revisión de buenas prácticas](revision-de-buenas-practicas.md) para el alcance y los resultados
|
|
||||||
globales, y [componentes de interfaz](componentes-de-interfaz.md) para los patrones compartidos.
|
|
||||||
|
|
||||||
Los modales de tickets, intervenciones y sus plantillas están [archivados](funcionalidades-archivadas.md).
|
|
||||||
|
|||||||
118
docs/patron-de-dialogos.md
Normal file
118
docs/patron-de-dialogos.md
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
# Patrón de diseño de cuadros de diálogo
|
||||||
|
|
||||||
|
Fuente normativa para crear o modificar diálogos de Metis Desk. Recoge las decisiones
|
||||||
|
aplicadas a los editores de plantillas de activos y recursos y a los selectores `PickerList`.
|
||||||
|
La [guía de componentes](componentes-de-interfaz.md) describe sus APIs y consumidores;
|
||||||
|
el [inventario de modales](inventario-de-modales.md) recoge las implementaciones existentes.
|
||||||
|
|
||||||
|
## Composición y reutilización
|
||||||
|
|
||||||
|
- Cada interacción modal con responsabilidad propia vive en un componente y fichero `.tsx`
|
||||||
|
independientes. El padre decide su apertura y aporta datos y callbacks.
|
||||||
|
- Reutilizar `FormDialog`, `FormBody` y `FormActions` de `@repo/rdx-ui`, apoyados en las
|
||||||
|
primitivas de shadcn/ui y Base UI. Consultar los patrones existentes antes de crear otro.
|
||||||
|
- Mantener los bloques visuales genéricos independientes de entidades, DTO, consultas y
|
||||||
|
endpoints. El módulo aporta textos traducidos, controles, datos y acciones.
|
||||||
|
- Aplicar el mismo patrón a alta, edición y copia y a entidades equivalentes. Conservar los
|
||||||
|
campos y restricciones propios de cada entidad, como el uso de las plantillas de recursos.
|
||||||
|
|
||||||
|
## Grupos, títulos y separación
|
||||||
|
|
||||||
|
- Usar `FormSectionTitle` para los títulos de grupo: texto pequeño, seminegrita, mayúsculas,
|
||||||
|
espaciado entre letras y color atenuado (`text-xs font-semibold uppercase tracking-wide
|
||||||
|
text-muted-foreground mb-4`). Conservar el nivel semántico del encabezado; las mayúsculas
|
||||||
|
pertenecen a la presentación CSS y los textos pasan por i18n.
|
||||||
|
- El título principal del diálogo y los nombres editables de secciones tienen su propia
|
||||||
|
función. El nombre editable de una sección va en su cabecera, junto a sus acciones.
|
||||||
|
- Para los datos generales, preferir `FormSection variant="plain"`, sin borde ni relleno de
|
||||||
|
tarjeta. Los grupos repetidos que necesitan identidad visual usan borde y fondo semánticos
|
||||||
|
discretos. Evitar acumular marcos y rellenos en grupos anidados.
|
||||||
|
- Mantener más separación entre grupos principales que entre controles relacionados. En el
|
||||||
|
patrón de plantillas, `TemplateMetadataSection` deja 36 px antes del grupo de secciones
|
||||||
|
(20 px del cuerpo y 16 px adicionales); las columnas y tarjetas se separan 16 px.
|
||||||
|
- Situar las acciones locales en la cabecera del grupo al que afectan. Por ejemplo,
|
||||||
|
«Añadir campo» se ofrece en «Campos» y lleva implícita la sección de destino.
|
||||||
|
|
||||||
|
## Distribución responsive y alturas
|
||||||
|
|
||||||
|
- Decidir las columnas por el ancho disponible del contenedor, mediante container queries.
|
||||||
|
En espacios estrechos, apilar el contenido y permitir que nombres y acciones se adapten.
|
||||||
|
- En formularios con nombre, icono y descripción, reutilizar `TemplateMetadataSection`:
|
||||||
|
nombre e icono debajo a la izquierda, descripción a la derecha, en dos columnas iguales
|
||||||
|
desde 560 px. Ambas columnas tienen igual altura y el área de descripción ocupa el alto
|
||||||
|
restante. En una columna, el recorrido es nombre, icono y descripción.
|
||||||
|
- Para secciones repetidas equivalentes, usar una columna con poco espacio y dos cuando sus
|
||||||
|
controles quepan con comodidad. `TemplateSectionsEditor sectionLayout="responsive"`
|
||||||
|
cambia a dos columnas desde 1.000 px. Las secciones de cada fila tienen la misma altura,
|
||||||
|
determinada por la más alta, y se reajustan al plegar, desplegar o cambiar su contenido.
|
||||||
|
- Mantener el orden de izquierda a derecha y de arriba abajo en el DOM, la numeración,
|
||||||
|
el teclado y el arrastre. No introducir un orden visual diferente mediante CSS.
|
||||||
|
- Cada grupo de campos responde a su propio ancho, también cuando su sección ocupa media
|
||||||
|
fila. El bloque «Sin sección» permanece debajo de las secciones, a ancho completo.
|
||||||
|
- No convertir formularios sencillos en cuadrículas de secciones ni imponerles altura fija.
|
||||||
|
Los umbrales anteriores corresponden a los componentes citados; otros contenidos deben
|
||||||
|
elegir su disposición según el espacio que necesitan sus controles.
|
||||||
|
|
||||||
|
## Secciones plegables y filas compactas
|
||||||
|
|
||||||
|
- Las secciones extensas o repetidas utilizan la primitiva `Collapsible` mediante
|
||||||
|
`TemplateSectionEditor`. Empiezan abiertas y conservan el estado de apertura por identidad
|
||||||
|
al reordenarse. El nombre, el agarre y las acciones permanecen en la cabecera.
|
||||||
|
- Conservar los controles al plegar. Si la validación detecta un control inválido oculto,
|
||||||
|
abrir la sección antes de enfocarlo. Plegar no modifica los datos.
|
||||||
|
- En listas de configuración de campos, utilizar la presentación compacta
|
||||||
|
`TemplateSectionsEditor fieldLayout="compact"`: agarre, switch de obligatoriedad, nombre,
|
||||||
|
flechas de desplazamiento y papelera. Evitar una segunda fila de controles cuando esta
|
||||||
|
configuración sea suficiente. Esta pauta corresponde a referencias de campos dentro de
|
||||||
|
una plantilla; los formularios de edición de sus valores conservan sus controles propios.
|
||||||
|
- Dar nombre accesible a los switches y botones de icono, incluyendo el campo o la sección.
|
||||||
|
La obligatoriedad conserva su etiqueta accesible y ayuda al pasar el ratón.
|
||||||
|
- Reutilizar `IconPicker` como desplegable compacto con icono y texto, incluida la opción
|
||||||
|
sin icono. Evitar una fila permanente con todas las opciones de iconos.
|
||||||
|
|
||||||
|
## Ordenación y selección de campos
|
||||||
|
|
||||||
|
- Reutilizar `useDragReorder`, `ReorderHandle` y el indicador de destino. Mantener flechas
|
||||||
|
explícitas y ordenación por teclado; el arrastre no es la única forma de cambiar el orden.
|
||||||
|
- Las secciones se ordenan entre sí y los campos dentro de su sección. Conservar el foco
|
||||||
|
y bloquear los movimientos imposibles o las acciones durante el guardado. Cancelar un
|
||||||
|
arrastre no cambia los datos y guardar confirma el borrador del formulario.
|
||||||
|
- Cuando se incorporen varias referencias de un catálogo, utilizar un selector múltiple
|
||||||
|
en diálogo en lugar de un desplegable que obligue a añadirlas una a una. En plantillas,
|
||||||
|
reutilizar `CatalogFieldPickerDialog` desde la cabecera de cada grupo de campos, sin un
|
||||||
|
bloque independiente de incorporación tras los datos generales.
|
||||||
|
- Confirmar la selección como lote, conservarla al buscar o paginar y descartar la selección
|
||||||
|
provisional al cancelar. Respetar límites, disponibilidad y ausencia de duplicados.
|
||||||
|
- El alta rápida opcional se ofrece a la izquierda del pie mediante `leadingActions`.
|
||||||
|
Debe poder ocultarse o deshabilitarse desde el consumidor. Mantener el selector y su estado
|
||||||
|
durante el alta anidada; crear el elemento no confirma por sí solo el lote seleccionado.
|
||||||
|
|
||||||
|
## Diálogos de selección PickerList
|
||||||
|
|
||||||
|
- Todos utilizan `FormDialog layout="picker"` y `EntityTable layout="fill"`: altura estable
|
||||||
|
adaptada a la ventana, independiente de búsquedas, carga y número de resultados. La tabla
|
||||||
|
ocupa el espacio restante y mantiene desplazamiento interno con espacio reservado para
|
||||||
|
la barra; búsqueda, filtros, paginación y acciones permanecen estables.
|
||||||
|
- Mantener el área de resultados incluso vacía y las cabeceras visibles durante el scroll.
|
||||||
|
En ventanas pequeñas, permitir el desplazamiento adicional del cuerpo para acceder a
|
||||||
|
todos los controles.
|
||||||
|
- La columna `selection` es la primera, antes del nombre: anchura ajustada al checkbox o
|
||||||
|
radio, poco espacio hasta el nombre y cabecera visualmente oculta con nombre accesible.
|
||||||
|
No se ordena, no se oculta y no se ancla a la derecha como las acciones.
|
||||||
|
- Pulsar una zona no interactiva de una fila disponible alterna su checkbox o selecciona
|
||||||
|
su radio. Reutilizar `onRowClick` de la tabla; los controles internos ejecutan solo su
|
||||||
|
propia acción, sin duplicar cambios. Respetar filas deshabilitadas y navegación por teclado.
|
||||||
|
|
||||||
|
## Cuerpo, pie y accesibilidad
|
||||||
|
|
||||||
|
- Elegir la anchura del diálogo según su contenido y respetar los márgenes de la ventana.
|
||||||
|
Mantener cabecera y pie visibles y el cuerpo desplazable cuando el contenido lo requiera.
|
||||||
|
- Los formularios conservan altura según contenido; la altura estable por resultados es
|
||||||
|
la regla de los `PickerList`. No introducir pestañas ni pasos para resolver solo espacio.
|
||||||
|
- Situar ayuda contextual general al final del cuerpo, inmediatamente antes de `FormActions`.
|
||||||
|
Mantener errores junto al contexto que los origina y las acciones dentro del formulario.
|
||||||
|
- Conservar foco, cancelación, cierre por teclado, estados de carga, error, vacío y envío.
|
||||||
|
No cerrar ni limpiar el formulario antes de confirmar una operación correcta.
|
||||||
|
- Validar los cambios de interacción con pruebas dirigidas y revisar visualmente los anchos
|
||||||
|
estrecho y amplio, nombres largos, secciones de distinta altura, plegado y resultados vacíos
|
||||||
|
cuando correspondan. Informar de las comprobaciones que no se hayan podido ejecutar.
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import { BrowserLink } from "@metis/core/client";
|
import { BrowserLink } from "@metis/core/client";
|
||||||
|
import { FormSectionTitle } from "@repo/rdx-ui/components/form-section-title";
|
||||||
import { Button } from "@repo/shadcn-ui/components/button";
|
import { Button } from "@repo/shadcn-ui/components/button";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@ -65,7 +66,7 @@ export function AssetRelationships(props: AssetRelationshipsProps) {
|
|||||||
return (
|
return (
|
||||||
<section aria-label={t("relations")} className="space-y-3">
|
<section aria-label={t("relations")} className="space-y-3">
|
||||||
<header className="flex flex-wrap items-center justify-between gap-2">
|
<header className="flex flex-wrap items-center justify-between gap-2">
|
||||||
{!compact && <h3 className="font-medium">{t("relations")}</h3>}
|
{!compact && <FormSectionTitle>{t("relations")}</FormSectionTitle>}
|
||||||
<Button onClick={() => setAdding(true)} size="sm" variant="outline">
|
<Button onClick={() => setAdding(true)} size="sm" variant="outline">
|
||||||
{t("linkAsset")}
|
{t("linkAsset")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { BrowserLink } from "@metis/core/client";
|
|||||||
import { DetailField } from "@repo/rdx-ui/components/detail-field";
|
import { DetailField } from "@repo/rdx-ui/components/detail-field";
|
||||||
import { DetailSection } from "@repo/rdx-ui/components/detail-section";
|
import { DetailSection } from "@repo/rdx-ui/components/detail-section";
|
||||||
import { FieldSections } from "@repo/rdx-ui/components/field-sections";
|
import { FieldSections } from "@repo/rdx-ui/components/field-sections";
|
||||||
|
import { FormSectionTitle } from "@repo/rdx-ui/components/form-section-title";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { useAssetDetailController } from "../controllers";
|
import { useAssetDetailController } from "../controllers";
|
||||||
@ -28,7 +29,7 @@ export function AssetDetail(props: AssetDetailProps) {
|
|||||||
: String(value);
|
: String(value);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-2">
|
<div className="@container space-y-5">
|
||||||
{!compact && <p className="text-sm text-muted-foreground">{t("snapshotNotice")}</p>}
|
{!compact && <p className="text-sm text-muted-foreground">{t("snapshotNotice")}</p>}
|
||||||
{!compact && (
|
{!compact && (
|
||||||
<DetailSection title={t("detailSections.identification")}>
|
<DetailSection title={t("detailSections.identification")}>
|
||||||
@ -72,9 +73,7 @@ export function AssetDetail(props: AssetDetailProps) {
|
|||||||
</DetailSection>
|
</DetailSection>
|
||||||
<div>
|
<div>
|
||||||
{!asset.specification.sections?.length && (
|
{!asset.specification.sections?.length && (
|
||||||
<h3 className="py-2 text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
<FormSectionTitle>{t("characteristics")}</FormSectionTitle>
|
||||||
{t("characteristics")}
|
|
||||||
</h3>
|
|
||||||
)}
|
)}
|
||||||
<FieldSections
|
<FieldSections
|
||||||
items={asset.specification.fields}
|
items={asset.specification.fields}
|
||||||
@ -105,9 +104,9 @@ export function AssetDetail(props: AssetDetailProps) {
|
|||||||
{children}
|
{children}
|
||||||
{!compact && (
|
{!compact && (
|
||||||
<>
|
<>
|
||||||
<h3 className="font-medium">
|
<FormSectionTitle>
|
||||||
{t("history")} ({asset.history.length})
|
{t("history")} ({asset.history.length})
|
||||||
</h3>
|
</FormSectionTitle>
|
||||||
<ol className="space-y-4">
|
<ol className="space-y-4">
|
||||||
{[...asset.history].reverse().map((revision) => (
|
{[...asset.history].reverse().map((revision) => (
|
||||||
<li className="space-y-2 rounded-md border p-3 text-sm" key={revision.id}>
|
<li className="space-y-2 rounded-md border p-3 text-sm" key={revision.id}>
|
||||||
@ -125,7 +124,9 @@ export function AssetDetail(props: AssetDetailProps) {
|
|||||||
{t("site")}: {nameOf(revision.values.siteId, lookups.sites)}
|
{t("site")}: {nameOf(revision.values.siteId, lookups.sites)}
|
||||||
</p>
|
</p>
|
||||||
<details>
|
<details>
|
||||||
<summary className="cursor-pointer">{t("characteristics")}</summary>
|
<summary className="cursor-pointer focus-visible:outline-2 focus-visible:outline-ring">
|
||||||
|
<FormSectionTitle as="span">{t("characteristics")}</FormSectionTitle>
|
||||||
|
</summary>
|
||||||
<FieldSections
|
<FieldSections
|
||||||
items={asset.specification.fields}
|
items={asset.specification.fields}
|
||||||
layout="rows"
|
layout="rows"
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { 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 { DialogFooter } from "@repo/shadcn-ui/components/dialog";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { BrandApiError } from "../../shared/api";
|
import { BrandApiError } from "../../shared/api";
|
||||||
@ -26,17 +25,16 @@ export function DeleteBrandDialog(props: DeleteBrandDialogProps) {
|
|||||||
open
|
open
|
||||||
title={t("brands.delete")}
|
title={t("brands.delete")}
|
||||||
>
|
>
|
||||||
<p>{t("brands.deleteConfirm", { name: brand.name })}</p>
|
<FormBody>
|
||||||
<p className="text-sm text-muted-foreground">{t("brands.deleteGuidance")}</p>
|
<p>{t("brands.deleteConfirm", { name: brand.name })}</p>
|
||||||
{mutation.isError && (
|
<p className="text-sm text-muted-foreground">{t("brands.deleteGuidance")}</p>
|
||||||
<p role="alert">
|
{mutation.isError && (
|
||||||
{t(`brands.errors.${errorCode}`, { defaultValue: t("brands.deleteError") })}
|
<p role="alert">
|
||||||
</p>
|
{t(`brands.errors.${errorCode}`, { defaultValue: t("brands.deleteError") })}
|
||||||
)}
|
</p>
|
||||||
<DialogFooter className="border-t pt-4">
|
)}
|
||||||
<Button disabled={mutation.isPending} onClick={onClose} type="button" variant="outline">
|
</FormBody>
|
||||||
{t("cancel")}
|
<FormActions cancelDisabled={mutation.isPending}>
|
||||||
</Button>
|
|
||||||
<Button
|
<Button
|
||||||
disabled={mutation.isPending || brand.builtin}
|
disabled={mutation.isPending || brand.builtin}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -49,7 +47,7 @@ export function DeleteBrandDialog(props: DeleteBrandDialogProps) {
|
|||||||
>
|
>
|
||||||
{t(mutation.isPending ? "brands.deleting" : "brands.delete")}
|
{t(mutation.isPending ? "brands.deleting" : "brands.delete")}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</FormActions>
|
||||||
</FormDialog>
|
</FormDialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { 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 { DialogFooter } from "@repo/shadcn-ui/components/dialog";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import type { CatalogItemDTO } from "../../../../common";
|
import type { CatalogItemDTO } from "../../../../common";
|
||||||
@ -34,17 +33,16 @@ export function DeleteCatalogDefinitionDialog({
|
|||||||
open
|
open
|
||||||
title={t("delete")}
|
title={t("delete")}
|
||||||
>
|
>
|
||||||
<p>{t("deleteDefinitionConfirm", { name: item.values.name })}</p>
|
<FormBody>
|
||||||
<p className="text-sm text-muted-foreground">{t("deleteDefinitionGuidance")}</p>
|
<p>{t("deleteDefinitionConfirm", { name: item.values.name })}</p>
|
||||||
{error && <p role="alert">{t(`errors.${code}`, { defaultValue: t("deleteError") })}</p>}
|
<p className="text-sm text-muted-foreground">{t("deleteDefinitionGuidance")}</p>
|
||||||
<DialogFooter className="border-t pt-4">
|
{error && <p role="alert">{t(`errors.${code}`, { defaultValue: t("deleteError") })}</p>}
|
||||||
<Button disabled={pending} onClick={onClose} type="button" variant="outline">
|
</FormBody>
|
||||||
{t("cancel")}
|
<FormActions cancelDisabled={pending}>
|
||||||
</Button>
|
|
||||||
<Button disabled={pending} onClick={onDelete} type="button" variant="destructive">
|
<Button disabled={pending} onClick={onDelete} type="button" variant="destructive">
|
||||||
{t(pending ? "deleting" : "delete")}
|
{t(pending ? "deleting" : "delete")}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</FormActions>
|
||||||
</FormDialog>
|
</FormDialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { FormSectionTitle } from "@repo/rdx-ui/components/form-section-title";
|
||||||
import { Button } from "@repo/shadcn-ui/components/button";
|
import { Button } from "@repo/shadcn-ui/components/button";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@ -30,7 +31,7 @@ export function AssetSupport(props: AssetSupportProps) {
|
|||||||
return (
|
return (
|
||||||
<section className={compact ? "space-y-3" : "space-y-3 rounded-md border p-4"}>
|
<section className={compact ? "space-y-3" : "space-y-3 rounded-md border p-4"}>
|
||||||
<header className="flex flex-wrap items-center justify-between gap-3">
|
<header className="flex flex-wrap items-center justify-between gap-3">
|
||||||
{!compact && <h3 className="font-semibold">{t("support.title")}</h3>}
|
{!compact && <FormSectionTitle>{t("support.title")}</FormSectionTitle>}
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
<Button
|
<Button
|
||||||
disabled={!query.data || retired}
|
disabled={!query.data || retired}
|
||||||
|
|||||||
@ -27,7 +27,7 @@ export function WorkstationAssociationFields({
|
|||||||
const { t } = useTranslation("assets");
|
const { t } = useTranslation("assets");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid gap-4 sm:grid-cols-2">
|
<div className="grid gap-4 @min-[560px]:grid-cols-2">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor={`${prefix}-site`}>{t("site")}</Label>
|
<Label htmlFor={`${prefix}-site`}>{t("site")}</Label>
|
||||||
<SelectWithAction
|
<SelectWithAction
|
||||||
|
|||||||
@ -40,10 +40,12 @@ export function AccessFormFields(
|
|||||||
<>
|
<>
|
||||||
<form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
|
<form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
|
||||||
<FormBody>
|
<FormBody>
|
||||||
<div className="grid gap-4 sm:grid-cols-2">
|
<div className="grid gap-4 @min-[560px]:grid-cols-2">
|
||||||
{editableFields.map((field) => (
|
{editableFields.map((field) => (
|
||||||
<div
|
<div
|
||||||
className={["note", "permissions"].includes(field.type) ? "sm:col-span-2" : ""}
|
className={
|
||||||
|
["note", "permissions"].includes(field.type) ? "@min-[560px]:col-span-2" : ""
|
||||||
|
}
|
||||||
key={field.key}
|
key={field.key}
|
||||||
>
|
>
|
||||||
<AccessFieldControl
|
<AccessFieldControl
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { FormSectionTitle } from "@repo/rdx-ui/components/form-section-title";
|
||||||
import { Checkbox } from "@repo/shadcn-ui/components/checkbox";
|
import { Checkbox } from "@repo/shadcn-ui/components/checkbox";
|
||||||
import { Label } from "@repo/shadcn-ui/components/label";
|
import { Label } from "@repo/shadcn-ui/components/label";
|
||||||
import type { UseFormReturn } from "react-hook-form";
|
import type { UseFormReturn } from "react-hook-form";
|
||||||
@ -24,7 +25,7 @@ export function PermissionsControl({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<fieldset className="space-y-2">
|
<fieldset className="space-y-2">
|
||||||
<legend className="text-sm font-medium">{t(fieldKey)}</legend>
|
<FormSectionTitle as="legend">{t(fieldKey)}</FormSectionTitle>
|
||||||
{options.map((option) => (
|
{options.map((option) => (
|
||||||
<div className="flex items-center gap-2" key={option.value}>
|
<div className="flex items-center gap-2" key={option.value}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
|||||||
@ -49,7 +49,7 @@ export function ApplicationConfigurationForm(props: ApplicationConfigurationForm
|
|||||||
<form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
|
<form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
|
||||||
<FormBody>
|
<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 @min-[560px]:grid-cols-2">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor={`${prefix}-profile`}>{t("mail.profile")}</Label>
|
<Label htmlFor={`${prefix}-profile`}>{t("mail.profile")}</Label>
|
||||||
<SelectWithAction
|
<SelectWithAction
|
||||||
|
|||||||
@ -57,7 +57,7 @@ export function AssignmentForm(props: AssignmentFormProps) {
|
|||||||
<Label htmlFor={`${id}-position`}>{t("departmentPosition")}</Label>
|
<Label htmlFor={`${id}-position`}>{t("departmentPosition")}</Label>
|
||||||
<Input id={`${id}-position`} maxLength={100} {...form.register("position")} />
|
<Input id={`${id}-position`} maxLength={100} {...form.register("position")} />
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-4 sm:grid-cols-2">
|
<div className="grid gap-4 @min-[560px]:grid-cols-2">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor={`${id}-start`}>{t("startDate")}</Label>
|
<Label htmlFor={`${id}-start`}>{t("startDate")}</Label>
|
||||||
<Input id={`${id}-start`} required type="date" {...form.register("startDate")} />
|
<Input id={`${id}-start`} required type="date" {...form.register("startDate")} />
|
||||||
|
|||||||
@ -48,7 +48,7 @@ export function CertificateConfigurationForm(props: CertificateConfigurationForm
|
|||||||
<form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
|
<form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
|
||||||
<FormBody>
|
<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 @min-[560px]:grid-cols-2">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor={`${prefix}-profile`}>{t("mail.profile")}</Label>
|
<Label htmlFor={`${prefix}-profile`}>{t("mail.profile")}</Label>
|
||||||
<SelectWithAction
|
<SelectWithAction
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { FormDialog } from "@repo/rdx-ui/components/form-dialog";
|
import { FormActions, FormBody, FormDialog } 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 { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@ -19,7 +19,7 @@ export function DashboardQuickCreate(props: DashboardQuickCreateProps) {
|
|||||||
<>
|
<>
|
||||||
<FormDialog
|
<FormDialog
|
||||||
cancelLabel={t("cancel")}
|
cancelLabel={t("cancel")}
|
||||||
formActions={kind !== "asset" && kind !== "resource"}
|
formActions
|
||||||
onOpenChange={(open) => {
|
onOpenChange={(open) => {
|
||||||
if (!open) {
|
if (!open) {
|
||||||
onClose();
|
onClose();
|
||||||
@ -30,35 +30,41 @@ export function DashboardQuickCreate(props: DashboardQuickCreateProps) {
|
|||||||
>
|
>
|
||||||
{kind === "company" && <CompanyForm onSaved={saved} showTitle={false} />}
|
{kind === "company" && <CompanyForm onSaved={saved} showTitle={false} />}
|
||||||
{(kind === "asset" || kind === "resource") && (
|
{(kind === "asset" || kind === "resource") && (
|
||||||
<BlockState empty={false} emptyText="" query={companies}>
|
<>
|
||||||
<SelectWithAction
|
<FormBody>
|
||||||
action={{ label: t("newCompany"), onClick: () => setAddingCompany(true) }}
|
<BlockState empty={false} emptyText="" query={companies}>
|
||||||
id="dashboard-create-company"
|
<SelectWithAction
|
||||||
label={t("companyLabel")}
|
action={{ label: t("newCompany"), onClick: () => setAddingCompany(true) }}
|
||||||
onValueChange={setSelectedCompany}
|
id="dashboard-create-company"
|
||||||
options={
|
label={t("companyLabel")}
|
||||||
companies.data?.companies
|
onValueChange={setSelectedCompany}
|
||||||
.filter((item) => item.active)
|
options={
|
||||||
.map((item) => ({ value: item.id, label: item.name })) ?? []
|
companies.data?.companies
|
||||||
}
|
.filter((item) => item.active)
|
||||||
placeholder={t("dashboard.chooseCompany")}
|
.map((item) => ({ value: item.id, label: item.name })) ?? []
|
||||||
value={selectedCompany}
|
}
|
||||||
/>
|
placeholder={t("dashboard.chooseCompany")}
|
||||||
<p className="text-sm text-muted-foreground">{t("dashboard.chooseCompany")}</p>
|
value={selectedCompany}
|
||||||
<Button
|
/>
|
||||||
disabled={!selectedCompany}
|
<p className="text-sm text-muted-foreground">{t("dashboard.chooseCompany")}</p>
|
||||||
onClick={() =>
|
</BlockState>
|
||||||
onNavigate({
|
</FormBody>
|
||||||
area: "company",
|
<FormActions>
|
||||||
id: selectedCompany,
|
<Button
|
||||||
section: kind === "asset" ? "inventory" : "resources",
|
disabled={!(selectedCompany && companies.isSuccess)}
|
||||||
create: true,
|
onClick={() =>
|
||||||
})
|
onNavigate({
|
||||||
}
|
area: "company",
|
||||||
>
|
id: selectedCompany,
|
||||||
{t("dashboard.continue")}
|
section: kind === "asset" ? "inventory" : "resources",
|
||||||
</Button>
|
create: true,
|
||||||
</BlockState>
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t("dashboard.continue")}
|
||||||
|
</Button>
|
||||||
|
</FormActions>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</FormDialog>
|
</FormDialog>
|
||||||
{addingCompany && (
|
{addingCompany && (
|
||||||
|
|||||||
@ -76,7 +76,7 @@ export function FolderMappingForm(props: FolderMappingFormProps) {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="grid gap-4 sm:grid-cols-2">
|
<div className="grid gap-4 @min-[560px]:grid-cols-2">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor={`${prefix}-letter`}>{t("folders.letter")}</Label>
|
<Label htmlFor={`${prefix}-letter`}>{t("folders.letter")}</Label>
|
||||||
<Input
|
<Input
|
||||||
|
|||||||
@ -75,7 +75,7 @@ export function MailboxUseForm(props: MailboxUseFormProps) {
|
|||||||
value={values.personId}
|
value={values.personId}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-4 sm:grid-cols-2">
|
<div className="grid gap-4 @min-[560px]:grid-cols-2">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor={`${prefix}-start`}>{t("mailbox.start")}</Label>
|
<Label htmlFor={`${prefix}-start`}>{t("mailbox.start")}</Label>
|
||||||
<Input
|
<Input
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { FormSectionTitle } from "@repo/rdx-ui/components/form-section-title";
|
||||||
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";
|
||||||
|
|
||||||
@ -14,7 +15,7 @@ export function MailboxRelations(props: MailboxRelationsProps) {
|
|||||||
return (
|
return (
|
||||||
<section className="space-y-3 border-t pt-4">
|
<section className="space-y-3 border-t pt-4">
|
||||||
<div className="flex items-center justify-between gap-3">
|
<div className="flex items-center justify-between gap-3">
|
||||||
<h3 className="font-medium">{t("people")}</h3>
|
<FormSectionTitle>{t("people")}</FormSectionTitle>
|
||||||
<Button onClick={() => setEditor("new")} size="sm" variant="outline">
|
<Button onClick={() => setEditor("new")} size="sm" variant="outline">
|
||||||
{t("mailbox.linkPerson")}
|
{t("mailbox.linkPerson")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@ -53,7 +53,7 @@ export function PersonForm(props: PersonFormProps) {
|
|||||||
onChange={setPhotoFile}
|
onChange={setPhotoFile}
|
||||||
person={person}
|
person={person}
|
||||||
/>
|
/>
|
||||||
<div className="grid gap-4 sm:grid-cols-2">
|
<div className="grid gap-4 @min-[560px]: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>
|
||||||
<Input id={`${id}-name`} maxLength={100} required {...form.register("name")} />
|
<Input id={`${id}-name`} maxLength={100} required {...form.register("name")} />
|
||||||
@ -65,7 +65,7 @@ export function PersonForm(props: PersonFormProps) {
|
|||||||
</div>
|
</div>
|
||||||
</FormSection>
|
</FormSection>
|
||||||
<FormSection title={t("personForm.contact")} variant="plain">
|
<FormSection title={t("personForm.contact")} variant="plain">
|
||||||
<div className="grid gap-4 sm:grid-cols-2">
|
<div className="grid gap-4 @min-[560px]:grid-cols-2">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor={`${id}-email`}>{t("emailOptional")}</Label>
|
<Label htmlFor={`${id}-email`}>{t("emailOptional")}</Label>
|
||||||
<Input id={`${id}-email`} maxLength={254} type="email" {...form.register("email")} />
|
<Input id={`${id}-email`} maxLength={254} type="email" {...form.register("email")} />
|
||||||
|
|||||||
@ -52,7 +52,7 @@ export function PrinterConfigurationForm(props: PrinterConfigurationFormProps) {
|
|||||||
<form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
|
<form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
|
||||||
<FormBody>
|
<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 @min-[560px]:grid-cols-2">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor={`${prefix}-profile`}>{t("mail.profile")}</Label>
|
<Label htmlFor={`${prefix}-profile`}>{t("mail.profile")}</Label>
|
||||||
<SelectWithAction
|
<SelectWithAction
|
||||||
|
|||||||
@ -62,7 +62,7 @@ export function RemoteConnectionForm(props: RemoteConnectionFormProps) {
|
|||||||
value={v.personId}
|
value={v.personId}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-4 sm:grid-cols-2">
|
<div className="grid gap-4 @min-[560px]:grid-cols-2">
|
||||||
<RemoteEndpointFields
|
<RemoteEndpointFields
|
||||||
assetOptions={assetOptions}
|
assetOptions={assetOptions}
|
||||||
company={company}
|
company={company}
|
||||||
|
|||||||
@ -0,0 +1,39 @@
|
|||||||
|
import { ResourceEditor } from "@metis/resources/client";
|
||||||
|
import { FormDialog } from "@repo/rdx-ui/components/form-dialog";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
export function SoftwareResourceCreateDialog({
|
||||||
|
companyId,
|
||||||
|
kind,
|
||||||
|
onClose,
|
||||||
|
onSaved,
|
||||||
|
}: {
|
||||||
|
companyId: string;
|
||||||
|
kind: "os" | "application";
|
||||||
|
onClose: () => void;
|
||||||
|
onSaved: (id: string) => void;
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation("companies");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormDialog
|
||||||
|
cancelLabel={t("cancel")}
|
||||||
|
formActions
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
if (!open) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
open
|
||||||
|
title={t(kind === "os" ? "software.newOs" : "applications.newResource")}
|
||||||
|
wide
|
||||||
|
>
|
||||||
|
<ResourceEditor
|
||||||
|
allowQuickCreate={false}
|
||||||
|
companyId={companyId}
|
||||||
|
initialKind={kind}
|
||||||
|
onSaved={onSaved}
|
||||||
|
/>
|
||||||
|
</FormDialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -12,7 +12,6 @@ import {
|
|||||||
EntityStatus,
|
EntityStatus,
|
||||||
EntityTable,
|
EntityTable,
|
||||||
} from "@repo/rdx-ui/components/entity-list";
|
} from "@repo/rdx-ui/components/entity-list";
|
||||||
import { 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 { Checkbox } from "@repo/shadcn-ui/components/checkbox";
|
import { Checkbox } from "@repo/shadcn-ui/components/checkbox";
|
||||||
import { useId, useState } from "react";
|
import { useId, useState } from "react";
|
||||||
@ -21,7 +20,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { DeleteRecordDialog } from "../../../shared/ui/delete-record-dialog";
|
import { DeleteRecordDialog } from "../../../shared/ui/delete-record-dialog";
|
||||||
import { SoftwareHistoryDialog } from "../../history/ui/software-history-dialog";
|
import { SoftwareHistoryDialog } from "../../history/ui/software-history-dialog";
|
||||||
import { softwareApi, softwareKeys } from "../../shared/api/software-api";
|
import { softwareApi, softwareKeys } from "../../shared/api/software-api";
|
||||||
import { SoftwareInstallationForm } from "../../shared/ui/software-installation-form";
|
import { SoftwareInstallationEditorDialog } from "../../shared/ui/software-installation-editor-dialog";
|
||||||
|
|
||||||
export function SoftwareInstallationsPanel({
|
export function SoftwareInstallationsPanel({
|
||||||
companyId,
|
companyId,
|
||||||
@ -255,26 +254,15 @@ export function SoftwareInstallationsPanel({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{editor && assetId && (
|
{editor && assetId && (
|
||||||
<FormDialog
|
<SoftwareInstallationEditorDialog
|
||||||
cancelLabel={t("cancel")}
|
assetId={assetId}
|
||||||
formActions
|
companyId={companyId}
|
||||||
onOpenChange={(open) => {
|
installation={edited}
|
||||||
if (!open) {
|
licenseOptions={licenseOptions}
|
||||||
setEditor(undefined);
|
onClose={() => setEditor(undefined)}
|
||||||
}
|
onSaved={() => setEditor(undefined)}
|
||||||
}}
|
options={options}
|
||||||
open
|
/>
|
||||||
title={t(edited ? "software.edit" : "software.new")}
|
|
||||||
>
|
|
||||||
<SoftwareInstallationForm
|
|
||||||
assetId={assetId}
|
|
||||||
companyId={companyId}
|
|
||||||
installation={edited}
|
|
||||||
licenseOptions={licenseOptions}
|
|
||||||
onSaved={() => setEditor(undefined)}
|
|
||||||
options={options}
|
|
||||||
/>
|
|
||||||
</FormDialog>
|
|
||||||
)}
|
)}
|
||||||
{viewed && (
|
{viewed && (
|
||||||
<SoftwareHistoryDialog
|
<SoftwareHistoryDialog
|
||||||
|
|||||||
@ -0,0 +1,28 @@
|
|||||||
|
import { FormDialog } from "@repo/rdx-ui/components/form-dialog";
|
||||||
|
import type { ComponentProps } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
import { SoftwareInstallationForm } from "./software-installation-form";
|
||||||
|
|
||||||
|
export function SoftwareInstallationEditorDialog({
|
||||||
|
onClose,
|
||||||
|
...formProps
|
||||||
|
}: ComponentProps<typeof SoftwareInstallationForm> & { onClose: () => void }) {
|
||||||
|
const { t } = useTranslation("companies");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormDialog
|
||||||
|
cancelLabel={t("cancel")}
|
||||||
|
formActions
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
if (!open) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
open
|
||||||
|
title={t(formProps.installation ? "software.edit" : "software.new")}
|
||||||
|
>
|
||||||
|
<SoftwareInstallationForm {...formProps} />
|
||||||
|
</FormDialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,7 +1,6 @@
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { ResourceEditor } from "@metis/resources/client";
|
|
||||||
import { ContextHelp } from "@repo/rdx-ui/components/context-help";
|
import { ContextHelp } from "@repo/rdx-ui/components/context-help";
|
||||||
import { FormActions, FormBody, FormDialog } 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";
|
||||||
@ -14,6 +13,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
|
|
||||||
import type { SoftwareInstallationDTO, SoftwareInstallationInput } from "../../../../common";
|
import type { SoftwareInstallationDTO, SoftwareInstallationInput } from "../../../../common";
|
||||||
import { SOFTWARE_STATE_TRANSITIONS, SoftwareInstallationInputSchema } from "../../../../common";
|
import { SOFTWARE_STATE_TRANSITIONS, SoftwareInstallationInputSchema } from "../../../../common";
|
||||||
|
import { SoftwareResourceCreateDialog } from "../../create/ui/software-resource-create-dialog";
|
||||||
import { SoftwareApiError, softwareApi, softwareKeys } from "../api/software-api";
|
import { SoftwareApiError, softwareApi, softwareKeys } from "../api/software-api";
|
||||||
|
|
||||||
export function SoftwareInstallationForm({
|
export function SoftwareInstallationForm({
|
||||||
@ -90,7 +90,7 @@ export function SoftwareInstallationForm({
|
|||||||
{t("applications.newResource")}
|
{t("applications.newResource")}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<div className="grid gap-4 sm:grid-cols-2">
|
<div className="grid gap-4 @min-[560px]:grid-cols-2">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor={`${prefix}-version`}>{t("applications.version")}</Label>
|
<Label htmlFor={`${prefix}-version`}>{t("applications.version")}</Label>
|
||||||
<Input id={`${prefix}-version`} maxLength={100} {...form.register("version")} />
|
<Input id={`${prefix}-version`} maxLength={100} {...form.register("version")} />
|
||||||
@ -160,28 +160,15 @@ export function SoftwareInstallationForm({
|
|||||||
</FormActions>
|
</FormActions>
|
||||||
</form>
|
</form>
|
||||||
{quick && (
|
{quick && (
|
||||||
<FormDialog
|
<SoftwareResourceCreateDialog
|
||||||
cancelLabel={t("cancel")}
|
companyId={companyId}
|
||||||
formActions
|
kind={quick}
|
||||||
onOpenChange={(open) => {
|
onClose={() => setQuick(undefined)}
|
||||||
if (!open) {
|
onSaved={(id) => {
|
||||||
setQuick(undefined);
|
form.setValue("resourceId", id);
|
||||||
}
|
setQuick(undefined);
|
||||||
}}
|
}}
|
||||||
open
|
/>
|
||||||
title={t(quick === "os" ? "software.newOs" : "applications.newResource")}
|
|
||||||
wide
|
|
||||||
>
|
|
||||||
<ResourceEditor
|
|
||||||
allowQuickCreate={false}
|
|
||||||
companyId={companyId}
|
|
||||||
initialKind={quick}
|
|
||||||
onSaved={(id) => {
|
|
||||||
form.setValue("resourceId", id);
|
|
||||||
setQuick(undefined);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</FormDialog>
|
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -54,7 +54,7 @@ export function TeleworkAuthorizationForm(props: TeleworkAuthorizationFormProps)
|
|||||||
label={t("telework.active")}
|
label={t("telework.active")}
|
||||||
onCheckedChange={(active) => form.setValue("active", active)}
|
onCheckedChange={(active) => form.setValue("active", active)}
|
||||||
/>
|
/>
|
||||||
<div className="grid gap-4 sm:grid-cols-2">
|
<div className="grid gap-4 @min-[560px]:grid-cols-2">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor={`${prefix}-start`}>{t("telework.startsOn")}</Label>
|
<Label htmlFor={`${prefix}-start`}>{t("telework.startsOn")}</Label>
|
||||||
<Input
|
<Input
|
||||||
|
|||||||
@ -73,7 +73,7 @@ export function WorkProfileForm(props: WorkProfileFormProps) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="grid gap-4 sm:grid-cols-2">
|
<div className="grid gap-4 @min-[560px]:grid-cols-2">
|
||||||
{selectors.map((selector) => {
|
{selectors.map((selector) => {
|
||||||
const fixed = !!profile && selector.field !== "accountId";
|
const fixed = !!profile && selector.field !== "accountId";
|
||||||
|
|
||||||
|
|||||||
@ -30,7 +30,7 @@ export function IntegrationSummary({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<dl className="grid gap-2 sm:grid-cols-2">
|
<dl className="grid gap-2 @min-[560px]:grid-cols-2">
|
||||||
{entries.map((entry) => (
|
{entries.map((entry) => (
|
||||||
<div key={entry.label}>
|
<div key={entry.label}>
|
||||||
<dt className="text-sm text-muted-foreground">{entry.label}</dt>
|
<dt className="text-sm text-muted-foreground">{entry.label}</dt>
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { DetailField } from "@repo/rdx-ui/components/detail-field";
|
import { DetailField } from "@repo/rdx-ui/components/detail-field";
|
||||||
import { FieldSections } from "@repo/rdx-ui/components/field-sections";
|
import { FieldSections } from "@repo/rdx-ui/components/field-sections";
|
||||||
|
import { FormSectionTitle } from "@repo/rdx-ui/components/form-section-title";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import type { ResourceDTO, ResourceReferenceDTO } from "../../../../common/inventory";
|
import type { ResourceDTO, ResourceReferenceDTO } from "../../../../common/inventory";
|
||||||
@ -25,10 +26,10 @@ export function ResourceDetail({
|
|||||||
: String(value);
|
: String(value);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-5">
|
<div className="@container space-y-5">
|
||||||
{!compact && <p className="text-sm text-muted-foreground">{t("snapshotNotice")}</p>}
|
{!compact && <p className="text-sm text-muted-foreground">{t("snapshotNotice")}</p>}
|
||||||
{!compact && (
|
{!compact && (
|
||||||
<dl className="grid gap-4 sm:grid-cols-2">
|
<dl className="grid gap-4 @min-[560px]:grid-cols-2">
|
||||||
<div>
|
<div>
|
||||||
<dt className="text-sm text-muted-foreground">{t("template")}</dt>
|
<dt className="text-sm text-muted-foreground">{t("template")}</dt>
|
||||||
<dd>{resource.specification.templateName}</dd>
|
<dd>{resource.specification.templateName}</dd>
|
||||||
@ -65,9 +66,9 @@ export function ResourceDetail({
|
|||||||
)}
|
)}
|
||||||
{!compact && (
|
{!compact && (
|
||||||
<>
|
<>
|
||||||
<h3 className="font-medium">
|
<FormSectionTitle>
|
||||||
{t("history")} ({resource.history.length})
|
{t("history")} ({resource.history.length})
|
||||||
</h3>
|
</FormSectionTitle>
|
||||||
<ol className="space-y-4">
|
<ol className="space-y-4">
|
||||||
{[...resource.history].reverse().map((revision) => (
|
{[...resource.history].reverse().map((revision) => (
|
||||||
<li className="space-y-2 rounded-md border p-3 text-sm" key={revision.id}>
|
<li className="space-y-2 rounded-md border p-3 text-sm" key={revision.id}>
|
||||||
|
|||||||
@ -32,7 +32,7 @@ export function ResourceIntegrationFields({
|
|||||||
|
|
||||||
if (mailbox) {
|
if (mailbox) {
|
||||||
return (
|
return (
|
||||||
<div className="grid gap-4 sm:grid-cols-2">
|
<div className="grid gap-4 @min-[560px]:grid-cols-2">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor={`${prefix}-kind`}>{t("mailboxKind")}</Label>
|
<Label htmlFor={`${prefix}-kind`}>{t("mailboxKind")}</Label>
|
||||||
<SelectWithAction
|
<SelectWithAction
|
||||||
@ -104,7 +104,7 @@ export function ResourceIntegrationFields({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid gap-4 sm:grid-cols-2">
|
<div className="grid gap-4 @min-[560px]:grid-cols-2">
|
||||||
{(["resourceTypeId", "systemId", "environmentId"] as const).map((key) => {
|
{(["resourceTypeId", "systemId", "environmentId"] as const).map((key) => {
|
||||||
const kind =
|
const kind =
|
||||||
key === "resourceTypeId"
|
key === "resourceTypeId"
|
||||||
|
|||||||
@ -48,7 +48,7 @@ export function ConfirmDeleteDialog({
|
|||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</FormBody>
|
</FormBody>
|
||||||
<FormActions>
|
<FormActions cancelDisabled={pending}>
|
||||||
<Button
|
<Button
|
||||||
disabled={pending}
|
disabled={pending}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
|
|||||||
@ -2,16 +2,18 @@ import type { ReactNode } from "react";
|
|||||||
|
|
||||||
import { ChevronRightIcon } from "../lib/icons";
|
import { ChevronRightIcon } from "../lib/icons";
|
||||||
|
|
||||||
|
import { FormSectionTitle } from "./form-section-title";
|
||||||
|
|
||||||
/** Grupo de consulta compartido por fichas y plantillas; abierto inicialmente. */
|
/** Grupo de consulta compartido por fichas y plantillas; abierto inicialmente. */
|
||||||
export function DetailSection({ title, children }: { title: string; children: ReactNode }) {
|
export function DetailSection({ title, children }: { title: string; children: ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<details className="@container group/detail-section" open>
|
<details className="@container group/detail-section" open>
|
||||||
<summary className="flex cursor-pointer list-none items-center gap-2 py-2 text-xs font-medium uppercase tracking-wide text-muted-foreground focus-visible:outline-2 focus-visible:outline-ring [&::-webkit-details-marker]:hidden">
|
<summary className="flex cursor-pointer list-none items-center gap-2 py-2 focus-visible:outline-2 focus-visible:outline-ring [&::-webkit-details-marker]:hidden">
|
||||||
<ChevronRightIcon
|
<ChevronRightIcon
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className="size-3 shrink-0 transition-transform group-open/detail-section:rotate-90 motion-reduce:transition-none"
|
className="size-3 shrink-0 text-muted-foreground transition-transform group-open/detail-section:rotate-90 motion-reduce:transition-none"
|
||||||
/>
|
/>
|
||||||
{title}
|
<FormSectionTitle as="span">{title}</FormSectionTitle>
|
||||||
</summary>
|
</summary>
|
||||||
<div className="space-y-3 border-t border-border/60 px-5 pb-2 [&>dl>div:first-child]:pt-2.5 [&>dl>div:last-child]:pb-2.5">
|
<div className="space-y-3 border-t border-border/60 px-5 pb-2 [&>dl>div:first-child]:pt-2.5 [&>dl>div:last-child]:pb-2.5">
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@ -0,0 +1,49 @@
|
|||||||
|
import { Checkbox } from "@repo/shadcn-ui/components/checkbox";
|
||||||
|
import type { Table as TableModel } from "@tanstack/react-table";
|
||||||
|
import { useId } from "react";
|
||||||
|
|
||||||
|
import { entityListLabels } from "../lib/entity-list";
|
||||||
|
|
||||||
|
import { FormDialog } from "./form-dialog";
|
||||||
|
|
||||||
|
export function EntityTableColumnsDialog<T>({
|
||||||
|
table,
|
||||||
|
locale,
|
||||||
|
open,
|
||||||
|
onOpenChange,
|
||||||
|
}: {
|
||||||
|
table: TableModel<T>;
|
||||||
|
locale: string;
|
||||||
|
open: boolean;
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
}) {
|
||||||
|
const id = useId();
|
||||||
|
const labels = entityListLabels(locale);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormDialog
|
||||||
|
cancelLabel={locale.startsWith("en") ? "Close" : "Cerrar"}
|
||||||
|
onOpenChange={onOpenChange}
|
||||||
|
open={open}
|
||||||
|
title={labels.columns}
|
||||||
|
>
|
||||||
|
<div className="space-y-3">
|
||||||
|
{table.getAllLeafColumns().map((column) => (
|
||||||
|
<label
|
||||||
|
className="flex items-center gap-2"
|
||||||
|
htmlFor={`${id}-column-${column.id}`}
|
||||||
|
key={column.id}
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
checked={column.getIsVisible()}
|
||||||
|
disabled={!column.getCanHide()}
|
||||||
|
id={`${id}-column-${column.id}`}
|
||||||
|
onCheckedChange={(checked) => column.toggleVisibility(checked === true)}
|
||||||
|
/>
|
||||||
|
{String(column.columnDef.header)}
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</FormDialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,11 +1,10 @@
|
|||||||
import { Button } from "@repo/shadcn-ui/components/button";
|
import { Button } from "@repo/shadcn-ui/components/button";
|
||||||
import { Checkbox } from "@repo/shadcn-ui/components/checkbox";
|
|
||||||
import type { Table as TableModel } from "@tanstack/react-table";
|
import type { Table as TableModel } from "@tanstack/react-table";
|
||||||
import { useId, useState } from "react";
|
import { useId, useState } from "react";
|
||||||
|
|
||||||
import { entityListLabels } from "../lib/entity-list";
|
import { entityListLabels } from "../lib/entity-list";
|
||||||
|
|
||||||
import { FormDialog } from "./form-dialog";
|
import { EntityTableColumnsDialog } from "./entity-table-columns-dialog";
|
||||||
import { SelectWithAction } from "./select-with-action";
|
import { SelectWithAction } from "./select-with-action";
|
||||||
|
|
||||||
export function EntityTableConfiguration<T>({
|
export function EntityTableConfiguration<T>({
|
||||||
@ -60,30 +59,12 @@ export function EntityTableConfiguration<T>({
|
|||||||
value={sorting[0] ? `${sorting[0].id}:${sorting[0].desc ? "desc" : "asc"}` : "initial"}
|
value={sorting[0] ? `${sorting[0].id}:${sorting[0].desc ? "desc" : "asc"}` : "initial"}
|
||||||
/>
|
/>
|
||||||
</div>{" "}
|
</div>{" "}
|
||||||
<FormDialog
|
<EntityTableColumnsDialog
|
||||||
cancelLabel={locale.startsWith("en") ? "Close" : "Cerrar"}
|
locale={locale}
|
||||||
onOpenChange={setColumnsOpen}
|
onOpenChange={setColumnsOpen}
|
||||||
open={columnsOpen}
|
open={columnsOpen}
|
||||||
title={labels.columns}
|
table={table}
|
||||||
>
|
/>
|
||||||
<div className="space-y-3">
|
|
||||||
{table.getAllLeafColumns().map((column) => (
|
|
||||||
<label
|
|
||||||
className="flex items-center gap-2"
|
|
||||||
htmlFor={`${id}-column-${column.id}`}
|
|
||||||
key={column.id}
|
|
||||||
>
|
|
||||||
<Checkbox
|
|
||||||
checked={column.getIsVisible()}
|
|
||||||
disabled={!column.getCanHide()}
|
|
||||||
id={`${id}-column-${column.id}`}
|
|
||||||
onCheckedChange={(checked) => column.toggleVisibility(checked === true)}
|
|
||||||
/>
|
|
||||||
{String(column.columnDef.header)}
|
|
||||||
</label>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</FormDialog>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { useEffect, useRef } from "react";
|
|||||||
import { ChevronRightIcon } from "../lib/icons";
|
import { ChevronRightIcon } from "../lib/icons";
|
||||||
|
|
||||||
import { DetailSection } from "./detail-section";
|
import { DetailSection } from "./detail-section";
|
||||||
|
import { FormSectionTitle } from "./form-section-title";
|
||||||
|
|
||||||
export type FieldSection = { id: string; name: string; description: string };
|
export type FieldSection = { id: string; name: string; description: string };
|
||||||
|
|
||||||
@ -99,10 +100,10 @@ export function FieldSections<T extends { sectionId?: string | undefined }>({
|
|||||||
{styles.compact && (
|
{styles.compact && (
|
||||||
<ChevronRightIcon
|
<ChevronRightIcon
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className="size-3 shrink-0 transition-transform group-open/field-section:rotate-90 motion-reduce:transition-none"
|
className="size-3 shrink-0 text-muted-foreground transition-transform group-open/field-section:rotate-90 motion-reduce:transition-none"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{section.name}
|
<FormSectionTitle as="span">{section.name}</FormSectionTitle>
|
||||||
</summary>
|
</summary>
|
||||||
<div className={styles.body}>
|
<div className={styles.body}>
|
||||||
{section.description && <p className={styles.description}>{section.description}</p>}
|
{section.description && <p className={styles.description}>{section.description}</p>}
|
||||||
@ -113,7 +114,9 @@ export function FieldSections<T extends { sectionId?: string | undefined }>({
|
|||||||
})}
|
})}
|
||||||
{ungrouped.length > 0 && (
|
{ungrouped.length > 0 && (
|
||||||
<section className="col-span-full space-y-3">
|
<section className="col-span-full space-y-3">
|
||||||
<h4 className={styles.ungrouped}>{ungroupedLabel}</h4>
|
<div className={styles.compact ? "pl-5" : undefined}>
|
||||||
|
<FormSectionTitle as="h4">{ungroupedLabel}</FormSectionTitle>
|
||||||
|
</div>
|
||||||
{content(ungrouped)}
|
{content(ungrouped)}
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
@ -129,8 +132,8 @@ function fieldSectionStyles(layout: FieldSectionLayout, values: boolean) {
|
|||||||
? "@container"
|
? "@container"
|
||||||
: "@container rounded-md border";
|
: "@container rounded-md border";
|
||||||
const summary = compact
|
const summary = compact
|
||||||
? "flex cursor-pointer list-none items-center gap-2 py-2 text-xs font-medium uppercase tracking-wide text-muted-foreground focus-visible:outline-2 focus-visible:outline-ring [&::-webkit-details-marker]:hidden"
|
? "flex cursor-pointer list-none items-center gap-2 py-2 focus-visible:outline-2 focus-visible:outline-ring [&::-webkit-details-marker]:hidden"
|
||||||
: `cursor-pointer text-sm font-medium focus-visible:outline-2 focus-visible:outline-ring ${values ? "py-2" : "rounded-md px-4 py-3"}`;
|
: `cursor-pointer focus-visible:outline-2 focus-visible:outline-ring ${values ? "py-2" : "rounded-md px-4 py-3"}`;
|
||||||
const body =
|
const body =
|
||||||
layout === "form"
|
layout === "form"
|
||||||
? "space-y-3 border-t border-border/60 pt-3"
|
? "space-y-3 border-t border-border/60 pt-3"
|
||||||
@ -150,20 +153,17 @@ function fieldSectionStyles(layout: FieldSectionLayout, values: boolean) {
|
|||||||
body,
|
body,
|
||||||
description,
|
description,
|
||||||
...FIELD_SECTION_LAYOUTS[layout],
|
...FIELD_SECTION_LAYOUTS[layout],
|
||||||
ungrouped: compact
|
|
||||||
? "pl-5 text-xs font-medium uppercase tracking-wide text-muted-foreground"
|
|
||||||
: "text-sm font-medium",
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const FIELD_SECTION_LAYOUTS = {
|
const FIELD_SECTION_LAYOUTS = {
|
||||||
fields: {
|
fields: {
|
||||||
content: "grid gap-4 sm:grid-cols-2 @min-[900px]:grid-cols-3",
|
content: "grid gap-4 @min-[560px]:grid-cols-2 @min-[900px]:grid-cols-3",
|
||||||
wrapper: "grid items-start gap-4 @min-[900px]:grid-cols-2",
|
wrapper: "grid items-stretch gap-4 @min-[900px]:grid-cols-2",
|
||||||
},
|
},
|
||||||
rows: {
|
rows: {
|
||||||
content: "divide-y divide-border/50",
|
content: "divide-y divide-border/50",
|
||||||
wrapper: "grid items-start gap-x-8 gap-y-3 @min-[900px]:grid-cols-2",
|
wrapper: "grid items-stretch gap-x-8 gap-y-3 @min-[900px]:grid-cols-2",
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
content:
|
content:
|
||||||
|
|||||||
@ -75,9 +75,11 @@ export function FormDialog({
|
|||||||
export function FormActions({
|
export function FormActions({
|
||||||
children,
|
children,
|
||||||
leadingActions,
|
leadingActions,
|
||||||
|
cancelDisabled = false,
|
||||||
}: {
|
}: {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
leadingActions?: ReactNode;
|
leadingActions?: ReactNode;
|
||||||
|
cancelDisabled?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const cancelLabel = useContext(FormCancelContext);
|
const cancelLabel = useContext(FormCancelContext);
|
||||||
|
|
||||||
@ -85,7 +87,9 @@ export function FormActions({
|
|||||||
<DialogFooter className="shrink-0 border-t bg-popover py-4">
|
<DialogFooter className="shrink-0 border-t bg-popover py-4">
|
||||||
{leadingActions && <div className="flex sm:mr-auto">{leadingActions}</div>}
|
{leadingActions && <div className="flex sm:mr-auto">{leadingActions}</div>}
|
||||||
{cancelLabel && (
|
{cancelLabel && (
|
||||||
<DialogClose render={<Button type="button" variant="outline" />}>{cancelLabel}</DialogClose>
|
<DialogClose render={<Button disabled={cancelDisabled} type="button" variant="outline" />}>
|
||||||
|
{cancelLabel}
|
||||||
|
</DialogClose>
|
||||||
)}
|
)}
|
||||||
{children}
|
{children}
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
@ -100,8 +104,8 @@ export function FormBody({ children }: { children: ReactNode }) {
|
|||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
layout === "picker"
|
layout === "picker"
|
||||||
? "flex min-h-0 flex-1 flex-col gap-3 overflow-y-auto px-1 pt-1 pb-5 [scrollbar-gutter:stable] [&>*]:shrink-0"
|
? "@container flex min-h-0 flex-1 flex-col gap-3 overflow-y-auto px-1 pt-1 pb-5 [scrollbar-gutter:stable] [&>*]:shrink-0"
|
||||||
: "min-h-0 flex-1 space-y-5 overflow-y-auto px-1 pt-1 pb-5"
|
: "@container min-h-0 flex-1 space-y-5 overflow-y-auto px-1 pt-1 pb-5"
|
||||||
}
|
}
|
||||||
data-slot="form-body"
|
data-slot="form-body"
|
||||||
>
|
>
|
||||||
|
|||||||
@ -8,11 +8,11 @@ export function FormSectionTitle({
|
|||||||
}: {
|
}: {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
id?: string;
|
id?: string;
|
||||||
as?: "h3" | "h4" | "h5";
|
as?: "h3" | "h4" | "h5" | "legend" | "span";
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Heading
|
<Heading
|
||||||
className="text-xs font-semibold uppercase tracking-wide text-muted-foreground mb-4"
|
className={`text-xs font-semibold uppercase tracking-wide text-muted-foreground ${Heading === "span" ? "" : "mb-4"}`}
|
||||||
id={id}
|
id={id}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user