@m4l/layouts 9.3.17-BE20260109-beta.3 → 9.3.17-BE20260122-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,33 @@
1
1
  import { ModuleDetailTabsProps } from './types';
2
2
  /**
3
- * Componente para mostrar un listado de tabs y contenido en un MasterDetail, basado en las props
3
+ * Componente para mostrar un listado de tabs y contenido en un MasterDetail
4
+ *
5
+ * Este componente renderiza una interfaz de tabs que muestra información detallada
6
+ * del registro seleccionado en el master (lista principal). Cada tab puede mostrar
7
+ * diferentes aspectos o secciones de información del mismo registro.
8
+ *
9
+ * Características principales:
10
+ * - Renderiza tabs dinámicos basados en la configuración proporcionada
11
+ * - Inyecta automáticamente los datos del registro seleccionado a cada tab
12
+ * - Soporta condiciones de visibilidad para mostrar/ocultar tabs dinámicamente
13
+ * - Puede obtener datos adicionales desde un endpoint opcional
14
+ * - Maneja estados de carga, errores y privilegios automáticamente
15
+ * @template T - Tipo de los datos del registro seleccionado en el master
16
+ * @example
17
+ * ```tsx
18
+ * <ModuleDetailTabs
19
+ * defaultTab="main"
20
+ * urlAssetsPrefix="https://api.example.com"
21
+ * tabs={[
22
+ * {
23
+ * value: 'main',
24
+ * tabContent: MainTab,
25
+ * dictionaryId: 'general',
26
+ * icon: '/icons/tab-general.svg',
27
+ * hasBackground: true
28
+ * }
29
+ * ]}
30
+ * />
31
+ * ```
4
32
  */
5
33
  export declare function ModuleDetailTabs<T extends Record<string, any> = Record<string, any>>(props: ModuleDetailTabsProps<T>): import("react/jsx-runtime").JSX.Element;
@@ -3,58 +3,157 @@ import { TabProps } from '@m4l/components';
3
3
  import { NetworkProps } from '@m4l/core';
4
4
  /**
5
5
  * Props obligatorias que deben tener todos los componentes de tab
6
- * Estas props serán inyectadas automáticamente por ModuleDetailTabs
6
+ *
7
+ * Estas props serán inyectadas automáticamente por ModuleDetailTabs a cada
8
+ * componente de tab. No es necesario pasarlas manualmente al definir los tabs.
9
+ * @template T - Tipo de los datos del registro seleccionado en el master
10
+ * @template K - Tipo de los datos adicionales obtenidos desde un endpoint (opcional)
7
11
  */
8
12
  export interface ModuleDetailTabContent<T extends Record<string, any> = Record<string, any>, K extends Record<string, any> = Record<string, any>> {
9
13
  /**
10
- * data - Contiene los datos del registro seleccionado en el master
11
- * Esta prop es OBLIGATORIA para todos los componentes de tab
14
+ * Datos del registro seleccionado en el master (lista principal)
15
+ *
16
+ * Esta prop contiene toda la información del registro que el usuario
17
+ * seleccionó en la lista principal. Es OBLIGATORIA y siempre estará disponible.
18
+ *
19
+ * Ejemplo: Si el usuario selecciona un inventario con id: 1001, name: "Inventario A",
20
+ * entonces data contendrá { id: 1001, name: "Inventario A", ... }
12
21
  */
13
22
  data: T;
14
23
  /**
15
- * endPointData - Contiene los datos del endpoint de detalle, en caso de haberse configurado.
24
+ * Datos adicionales obtenidos desde un endpoint de detalle (opcional)
25
+ *
26
+ * Solo estará disponible si se configuró la prop `getEndPoint` en ModuleDetailTabsProps.
27
+ * Contiene información complementaria que se obtiene mediante una petición HTTP
28
+ * adicional al seleccionar un registro en el master.
29
+ *
30
+ * Ejemplo: Información detallada como descripción, ubicación, historial, etc.
16
31
  */
17
32
  endPointData?: K;
18
33
  /**
19
- * hasPrivilegeDetail - Indica si el usuario tiene privilegio para ver el detalle.
34
+ * Indica si el usuario tiene privilegios para ver el detalle
35
+ *
36
+ * Se establece automáticamente basándose en la respuesta del endpoint.
37
+ * Si el endpoint retorna un error 403 (Forbidden), este valor será false.
20
38
  */
21
39
  hasPrivilegeDetail?: boolean;
22
40
  /**
23
- * refreshDetail - Función para refrescar el detalle.
41
+ * Función para refrescar los datos del detalle
42
+ *
43
+ * Al llamar a esta función, se volverá a ejecutar la petición al endpoint
44
+ * configurado en `getEndPoint` para obtener los datos actualizados.
45
+ *
46
+ * Útil cuando se necesita actualizar la información después de una acción
47
+ * (por ejemplo, después de editar o eliminar un registro).
24
48
  */
25
49
  refreshDetail: () => void;
26
50
  }
51
+ /**
52
+ * Interfaz base para las propiedades comunes de todos los tabs
53
+ *
54
+ * Extiende TabProps de @m4l/components pero omite 'label' ya que
55
+ * se genera automáticamente desde el dictionaryId o se puede personalizar.
56
+ */
27
57
  interface ModuleDetailBaseTab extends Omit<TabProps, 'label'> {
28
58
  /**
29
- * "dictionaryId" Id del diccionario para el label.
59
+ * ID del diccionario para obtener la etiqueta traducida del tab
60
+ *
61
+ * Si se proporciona, el label del tab se obtendrá automáticamente
62
+ * del diccionario usando este ID. Si no se proporciona, el tab no
63
+ * tendrá label visible (aunque puede tener icono).
64
+ *
65
+ * Ejemplo: 'general', 'secondary', 'details', etc.
30
66
  */
31
67
  dictionaryId?: string;
32
68
  /**
33
- * "unmountable" Indica si el tab se desmonta cuando el tab no está seleccionado, default: true
69
+ * Indica si el componente del tab se desmonta cuando no está seleccionado
70
+ *
71
+ * - true (default): El componente se desmonta completamente cuando se cambia de tab.
72
+ * Útil para mejorar el rendimiento y resetear el estado del componente.
73
+ *
74
+ * - false: El componente permanece montado pero oculto. Útil cuando se necesita
75
+ * mantener el estado del componente al cambiar entre tabs.
34
76
  */
35
77
  unmountable?: boolean;
36
78
  }
37
79
  type ComponentWithData<T extends Record<string, any> = Record<string, any>, K extends Record<string, any> = Record<string, any>, C extends React.ElementType = React.ElementType> = React.FunctionComponent<ModuleDetailTabContent<T, K> & React.ComponentProps<C>>;
80
+ /**
81
+ * Interfaz para definir un tab en ModuleDetailTabs
82
+ *
83
+ * Cada tab representa una sección de información que se muestra cuando
84
+ * el usuario selecciona un registro en el master (lista principal).
85
+ * @template T - Tipo de los datos del registro seleccionado en el master
86
+ * @template K - Tipo de los datos adicionales obtenidos desde un endpoint
87
+ * @template C - Tipo del componente React que renderiza el contenido del tab
88
+ */
38
89
  export interface ModuleDatailTab<T extends Record<string, any> = Record<string, any>, K extends Record<string, any> = Record<string, any>, C extends React.ElementType = React.ElementType> extends ModuleDetailBaseTab {
39
90
  /**
40
- * "value" Valor o key del tab
91
+ * Identificador único del tab (valor o key)
92
+ *
93
+ * Debe ser único entre todos los tabs. Se usa para identificar
94
+ * qué tab está activo y para cambiar entre tabs.
95
+ *
96
+ * Ejemplo: 'main', 'secondary', 'details', 'logs', etc.
41
97
  */
42
98
  value: string;
43
99
  /**
44
- * "tabContent" Contenido del tab, debe ser un componente que acepte como mínimo
45
- * la prop "data" (DetailComponentProps) que será inyectada automáticamente
100
+ * Componente React que renderiza el contenido del tab
101
+ *
102
+ * IMPORTANTE: Debe ser el componente sin instanciar (la función/clase),
103
+ * NO una instancia del componente (<Component />).
104
+ *
105
+ * El componente debe aceptar las props definidas en ModuleDetailTabContent,
106
+ * que serán inyectadas automáticamente:
107
+ * - data: Datos del registro seleccionado
108
+ * - endPointData: Datos adicionales del endpoint (opcional)
109
+ * - hasPrivilegeDetail: Si tiene privilegios
110
+ * - refreshDetail: Función para refrescar
111
+ *
112
+ * Ejemplo: tabContent: MainTab (no <MainTab />)
46
113
  */
47
- tabContent: ComponentWithData<T, K>; /**Elemento no instanciado de react, solamente la funcion */
114
+ tabContent: ComponentWithData<T, K>;
48
115
  /**
49
- * "componentProps" Propiedades adicionales del componente del tab.
50
- * IMPORTANTE: No se puede sobrescribir la prop "data" aquí, ya que será inyectada
51
- * automáticamente por ModuleDetailTabs
116
+ * Propiedades adicionales que se pasarán al componente del tab
117
+ *
118
+ * Estas props se combinarán con las props automáticas (data, endPointData, etc.)
119
+ * que inyecta ModuleDetailTabs.
120
+ *
121
+ * IMPORTANTE: No se puede sobrescribir ninguna prop de ModuleDetailTabContent
122
+ * (data, endPointData, hasPrivilegeDetail, refreshDetail) ya que estas son
123
+ * inyectadas automáticamente y tienen prioridad.
124
+ *
125
+ * Ejemplo: { customProp: 'value', onCustomAction: () => {} }
52
126
  */
53
127
  componentProps?: Omit<React.ComponentProps<C>, keyof ModuleDetailTabContent<T, K>>;
54
128
  /**
55
- * Indica si el contenido del tab debe tener un fondo, default: false
129
+ * Indica si el contenido del tab debe tener un fondo visual
130
+ *
131
+ * - true: El contenido tendrá un fondo (generalmente blanco o gris claro)
132
+ * - false (default): El contenido no tendrá fondo, será transparente
133
+ *
134
+ * Útil para diferenciar visualmente el contenido del tab del fondo de la aplicación.
56
135
  */
57
136
  hasBackground?: boolean;
137
+ /**
138
+ * Función opcional que determina si el tab debe mostrarse u ocultarse
139
+ *
140
+ * Esta función se evalúa cada vez que cambian los datos del detalle o del endpoint.
141
+ * Permite mostrar u ocultar tabs dinámicamente según las condiciones del negocio.
142
+ * @param detailData - Datos del registro seleccionado en el master
143
+ * @param endPointData - Datos adicionales del endpoint (puede ser undefined)
144
+ * @returns true si el tab debe mostrarse, false si debe ocultarse
145
+ *
146
+ * Ejemplo:
147
+ * ```typescript
148
+ * visibilityCondition: (detailData, endPointData) => {
149
+ * // Solo mostrar si el inventario está activo
150
+ * return detailData?.status === 'activo';
151
+ * }
152
+ * ```
153
+ *
154
+ * Si no se proporciona, el tab siempre se mostrará (siempre que haya una selección en el master).
155
+ */
156
+ visibilityCondition?: (detailData: T, endPointData?: K) => boolean;
58
157
  }
59
158
  /**
60
159
  * Interfaz para las props del tab de logs
@@ -91,27 +190,82 @@ export interface ModuleDetailOtherObjectLogs<T> extends ModuleDetailBaseObjectLo
91
190
  getObjectSerialId: (detailData: T) => string;
92
191
  }
93
192
  export type ModuleDetailObjectLogs<T extends Record<string, any>> = ModuleDetailM4LObjectLogs<T> | ModuleDetailOtherObjectLogs<T>;
193
+ /**
194
+ * Props principales del componente ModuleDetailTabs
195
+ *
196
+ * Define la configuración completa de los tabs que se mostrarán cuando
197
+ * el usuario seleccione un registro en el master (lista principal).
198
+ * @template T - Tipo de los datos del registro seleccionado en el master
199
+ * @template K - Tipo de los datos adicionales obtenidos desde un endpoint
200
+ */
94
201
  export interface ModuleDetailTabsProps<T extends Record<string, any> = Record<string, any>, K extends Record<string, any> = Record<string, any>> {
95
202
  /**
96
- * "defaultTab" Valor o key del tab por defecto, default: el primer tab
203
+ * Identificador del tab que se mostrará por defecto al seleccionar un registro
204
+ *
205
+ * Debe coincidir con el 'value' de uno de los tabs definidos en la lista 'tabs'.
206
+ * Si no se proporciona o el tab especificado no está disponible, se mostrará
207
+ * el primer tab de la lista.
208
+ *
209
+ * Ejemplo: 'main', 'general', 'details'
97
210
  */
98
211
  defaultTab: string;
99
212
  /**
100
- * "urlAssetsPrefix" Prefijo de la url para la ruta de los iconos, en algunos casos es:
101
- * - host_api_remote
102
- * - host_static_asset/environment_assets
213
+ * Prefijo de la URL base para cargar los iconos de los tabs
214
+ *
215
+ * Se concatena con la ruta del icono definida en cada tab para formar
216
+ * la URL completa del icono.
217
+ *
218
+ * Ejemplos comunes:
219
+ * - host_api_remote
220
+ * - host_static_asset/environment_assets
221
+ * - https://cdn.example.com/assets
222
+ *
223
+ * Ejemplo de uso:
224
+ * Si urlAssetsPrefix = 'https://api.example.com' y el icono es '/icons/tab-icon.svg',
225
+ * la URL final será: 'https://api.example.com/icons/tab-icon.svg'
103
226
  */
104
227
  urlAssetsPrefix: string;
105
228
  /**
106
- * "tabs" Lista de tabs con los siguientes campos:
229
+ * Lista de tabs que se mostrarán en el componente
230
+ *
231
+ * Cada tab define su contenido, icono, etiqueta, condiciones de visibilidad, etc.
232
+ * Los tabs se mostrarán en el orden en que aparecen en este array.
233
+ *
234
+ * Ver ModuleDatailTab para más detalles sobre la estructura de cada tab.
107
235
  */
108
236
  tabs: ModuleDatailTab<T, K>[];
109
237
  /**
110
- * ObjectLogs props
238
+ * Configuración opcional para agregar un tab de logs automáticamente
239
+ *
240
+ * Si se proporciona, se agregará un tab adicional al final de la lista
241
+ * que muestra los logs/auditoría del objeto seleccionado.
242
+ *
243
+ * Soporta dos tipos:
244
+ * - 'm4l': Para objetos del sistema M4L (requiere resourceId y getObjectId)
245
+ * - 'other': Para objetos externos (requiere resourceTypeId y getObjectSerialId)
246
+ *
247
+ * Ver ModuleDetailObjectLogs para más detalles.
111
248
  */
112
249
  objectLogsProps?: ModuleDetailObjectLogs<T>;
113
250
  /**
114
- * "endPoint" Endpoint para obtener los datos del detalle., en caso de que se requiera.
251
+ * Función opcional para obtener los datos adicionales desde un endpoint
252
+ *
253
+ * Si se proporciona, se ejecutará una petición HTTP al seleccionar un registro
254
+ * en el master. Los datos obtenidos se pasarán a todos los componentes de tab
255
+ * a través de la prop 'endPointData'.
256
+ * @param masterSelection - El registro seleccionado en el master
257
+ * @returns Configuración de la petición HTTP (NetworkProps)
258
+ *
259
+ * Ejemplo:
260
+ * ```typescript
261
+ * getEndPoint: (inventory) => ({
262
+ * method: 'GET',
263
+ * endPoint: `/api/inventories/${inventory.id}/details`
264
+ * })
265
+ * ```
266
+ *
267
+ * Los datos retornados por el endpoint estarán disponibles en todos los tabs
268
+ * a través de la prop 'endPointData' de ModuleDetailTabContent.
115
269
  */
116
270
  getEndPoint?: (masterSelection: T) => NetworkProps;
117
271
  }
@@ -16,6 +16,7 @@ export declare function useModuleDetailTabs<T extends Record<string, any> = Reco
16
16
  tabContent: import('react').FunctionComponent<any>;
17
17
  componentProps?: Omit<any, keyof import('./types').ModuleDetailTabContent<T_1, K_1>> | undefined;
18
18
  hasBackground?: boolean;
19
+ visibilityCondition?: ((detailData: T, endPointData?: K | undefined) => boolean) | undefined;
19
20
  dictionaryId?: string;
20
21
  size?: Extract<import('@m4l/styles').Sizes, "small" | "medium"> | undefined;
21
22
  children?: null | undefined;
@@ -46,19 +46,39 @@ function useModuleDetailTabs(props) {
46
46
  icon: "/main/na/icons/i_common_master_detail_layout_detail_tab_logs"
47
47
  });
48
48
  }
49
- return arrTabs.map((tab) => ({
49
+ const processedTabs = arrTabs.map((tab) => ({
50
50
  ...tab,
51
51
  unmountable: tab.unmountable === void 0 ? true : tab.unmountable,
52
52
  icon: typeof tab.icon === "string" ? /* @__PURE__ */ jsx(Icon, { src: `${urlAssetsPrefix}${tab.icon}` }) : tab.icon,
53
53
  label: tab.dictionaryId ? getLabel(tab.dictionaryId) : void 0
54
54
  }));
55
- }, [masterSelection, tabs, objectLogsProps, urlAssetsPrefix, getLabel]);
55
+ return processedTabs.filter((tab) => {
56
+ if (tab.visibilityCondition) {
57
+ return tab.visibilityCondition(detailData, endPointData);
58
+ }
59
+ return true;
60
+ });
61
+ }, [masterSelection, tabs, objectLogsProps, urlAssetsPrefix, getLabel, detailData, endPointData]);
56
62
  useEffect(() => {
57
63
  if (masterSelection === void 0 || masterSelection.length === 0) {
58
64
  return;
59
65
  }
60
66
  setDetailData(masterSelection[0]);
61
67
  }, [masterSelection]);
68
+ useEffect(() => {
69
+ if (finalTabs.length === 0) {
70
+ return;
71
+ }
72
+ const currentTabExists = finalTabs.some((tab) => tab.value === currentTab);
73
+ if (!currentTabExists) {
74
+ const defaultTabExists = finalTabs.some((tab) => tab.value === defaultTab);
75
+ if (defaultTabExists) {
76
+ setCurrentTab(defaultTab);
77
+ } else {
78
+ setCurrentTab(finalTabs[0].value);
79
+ }
80
+ }
81
+ }, [finalTabs, currentTab, defaultTab]);
62
82
  const conditionNoMasterSelection = masterSelection === void 0 || masterSelection.length === 0;
63
83
  useEffect(() => {
64
84
  if (!getEndPoint) {
@@ -1,2 +1,2 @@
1
1
  export * from './useDynamicAccordions';
2
- export type { useDynamicAccordionsProps, ConfigDynamicAccordion as DynamicAccordion, DynamicAccordionValue, GroupConfigDynamicAccordion as DynamicAccordionGroup, GroupConfigDynamicAccordion } from './types';
2
+ export type { useDynamicAccordionsProps, ConfigDynamicAccordion as DynamicAccordion, DynamicAccordionValue, GroupConfigDynamicAccordion as DynamicAccordionGroup } from './types';
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@m4l/layouts",
3
- "version": "9.3.17-BE20260109-beta.3",
3
+ "version": "9.3.17-BE20260122-1+PR22-moduledetailtabs-condicional",
4
4
  "license": "UNLICENSED",
5
5
  "author": "M4L Team",
6
6
  "lint-staged": {
7
7
  "*.{js,ts,tsx}": "eslint --fix --max-warnings 0"
8
8
  },
9
9
  "peerDependencies": {
10
- "@m4l/components": "9.4.6-BE20260109-beta.3",
10
+ "@m4l/components": "^9.0.0",
11
11
  "@m4l/core": "^2.0.0",
12
12
  "@m4l/graphics": "^7.0.0",
13
13
  "@m4l/styles": "^7.0.0"
@@ -0,0 +1,41 @@
1
+ import { Meta, StoryObj } from '@storybook/react';
2
+ import { ModuleDetailTabs } from '../../../src/components/ModuleDetailTabs/ModuleDetailTabs';
3
+ declare const meta: Meta<typeof ModuleDetailTabs>;
4
+ type Story = StoryObj<typeof ModuleDetailTabs>;
5
+ /**
6
+ * Historia por defecto: Muestra todos los tabs disponibles sin condiciones de visibilidad
7
+ *
8
+ * En este ejemplo:
9
+ * - Se muestran 2 tabs: "General" y "Secundario"
10
+ * - El tab "General" está seleccionado por defecto
11
+ * - Ambos tabs muestran información del inventario con ID 1001
12
+ * - No hay condiciones que oculten ningún tab
13
+ */
14
+ export declare const Default: Story;
15
+ /**
16
+ * Historia con condición de visibilidad: Demuestra cómo ocultar tabs dinámicamente
17
+ *
18
+ * En este ejemplo:
19
+ * - El tab "General" siempre se muestra
20
+ * - El tab "Secundario" se oculta cuando el ID del inventario es igual a 1001
21
+ * - Como el mockInventoryData tiene id: 1001, el tab "Secundario" NO será visible
22
+ *
23
+ * Para ver el tab "Secundario", cambiaría el ID del inventario a cualquier otro valor
24
+ * (por ejemplo, 2001) y entonces el tab se mostraría.
25
+ */
26
+ export declare const WithVisibilityCondition: Story;
27
+ /**
28
+ * Historia con control dinámico de visibilidad: Demuestra cómo cambiar la visibilidad
29
+ * de los tabs mediante interacción del usuario
30
+ *
31
+ * En este ejemplo:
32
+ * - Inicialmente ambos tabs ("General" y "Secundario") son visibles
33
+ * - El tab "General" contiene un botón de control interno
34
+ * - Al hacer clic en el botón "Ocultar Tab Secundario", el tab "Secundario" se oculta
35
+ * - Solo el tab "General" permanece visible después de la interacción
36
+ *
37
+ * Este caso de uso es útil cuando se necesita controlar la visibilidad de tabs
38
+ * basándose en acciones del usuario o estados internos de la aplicación.
39
+ */
40
+ export declare const InteractionWithDynamicVisibilityControl: Story;
41
+ export default meta;
@@ -1,4 +1,4 @@
1
- import { Meta, StoryObj } from '@storybook/react/*';
1
+ import { Meta, StoryObj } from '@storybook/react';
2
2
  import { NoAuthModuleLayout } from '../../../src/layouts/NoAuthModuleLayout/index';
3
3
  declare const meta: Meta<typeof NoAuthModuleLayout>;
4
4
  type Story = StoryObj<typeof NoAuthModuleLayout>;