@drax/audit-vue 0.38.0 → 0.39.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.
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.38.0",
6
+ "version": "0.39.1",
7
7
  "type": "module",
8
8
  "main": "./src/index.ts",
9
9
  "module": "./src/index.ts",
@@ -24,8 +24,8 @@
24
24
  "format": "prettier --write src/"
25
25
  },
26
26
  "dependencies": {
27
- "@drax/crud-front": "^0.38.0",
28
- "@drax/crud-share": "^0.38.0"
27
+ "@drax/crud-front": "^0.39.0",
28
+ "@drax/crud-share": "^0.39.0"
29
29
  },
30
30
  "peerDependencies": {
31
31
  "pinia": "^2.2.2",
@@ -62,5 +62,5 @@
62
62
  "vue-tsc": "^2.0.11",
63
63
  "vuetify": "^3.7.1"
64
64
  },
65
- "gitHead": "43c90f3c12165e7527edefbc80dd327a59236dd5"
65
+ "gitHead": "f765e15e64c21dca72b8d4a8d2d6279c7fdb32d5"
66
66
  }
@@ -1,380 +1,203 @@
1
+ <template>
1
2
 
2
- <script setup lang="ts">
3
- import { computed } from 'vue'
4
- import type { IAudit } from '@drax/audit-share'
5
-
6
- interface Props {
7
- audit: IAudit
8
- }
3
+ <!-- BLOQUE DE ATRIBUTOS EN COLUMNA -->
4
+ <div class="text-body-2 mt-3">
5
+
6
+ <div class="mb-1 audit-row">
7
+ <strong class="audit-label">{{ t('audit.field.action') }}:</strong>
8
+ <span>{{ audit.action }}</span>
9
+ </div>
10
+
11
+ <div class="mb-1 audit-row">
12
+ <strong class="audit-label">{{ t('audit.field.entity') }}:</strong>
13
+ <span>{{ audit.entity }}</span>
14
+ </div>
15
+
16
+ <div class="mb-1 audit-row">
17
+ <strong class="audit-label">{{ t('audit.field.resourceId') }}:</strong>
18
+ <span>{{ audit.resourceId }}</span>
19
+ </div>
20
+
21
+
22
+ <div class="mb-1 audit-row">
23
+ <strong class="audit-label">{{ t('audit.field.user') }}:</strong>
24
+ <span>{{ audit.user.username }}</span> <span v-if="audit.user.rolName"><{{ audit.user.rolName }}></span> <span>(ID {{
25
+ audit.user.id
26
+ }})</span>
27
+ </div>
28
+
29
+ <div
30
+ v-if="audit.apiKey?.id"
31
+ class="mb-1 audit-row"
32
+ >
33
+ <strong class="audit-label">{{ t('audit.field.apiKey') }}:</strong>
34
+ <span>{{ audit.apiKey?.name }}</span> <span>(ID {{ audit.apiKey?.id }})</span>
35
+ </div>
36
+
37
+ <div class="mb-1 audit-row">
38
+ <strong class="audit-label">{{ t('audit.field.ip') }}:</strong>
39
+ <span>{{ audit.ip }}</span>
40
+ </div>
41
+
42
+ <div class="mb-1 audit-row">
43
+ <strong class="audit-label">{{ t('audit.field.userAgent') }}:</strong>
44
+ <span class="d-inline-block">
45
+ {{ audit.userAgent }}
46
+ </span>
47
+ </div>
48
+
49
+ <div
50
+ v-if="audit.tenant"
51
+ class="mb-1 audit-row"
52
+ >
53
+ <strong class="audit-label">{{ t('audit.field.tenant') }}:</strong>
54
+ <span>{{ audit.tenant?.name }}</span><span v-if="audit.tenant.id">(ID {{ audit.tenant?.id }})</span>
55
+ </div>
56
+
57
+
58
+ <div
59
+ class="mb-1 audit-row"
60
+ >
61
+ <strong class="audit-label">{{ t('audit.field.sessionId') }}:</strong>
62
+ <span>{{ audit.sessionId }}</span>
63
+ </div>
64
+
65
+ <div
66
+ v-if="audit.requestId"
67
+ class="mb-1 audit-row"
68
+ >
69
+ <strong class="audit-label">{{ t('audit.field.requestId') }}:</strong>
70
+ <span>{{ audit.requestId }}</span>
71
+ </div>
72
+
73
+ <div
74
+ v-if="audit.createdAt"
75
+ class="mb-1 audit-row"
76
+ >
77
+ <strong class="audit-label">{{ t('audit.field.createdAt') }}:</strong>
78
+ <span>{{ formatDateTime(audit.createdAt) }}</span>
79
+ </div>
80
+
81
+
82
+ </div>
83
+
84
+ <!-- DETAIL -->
85
+ <v-row
86
+ v-if="audit.detail"
87
+ class="mt-2"
88
+ >
89
+ <v-col cols="12">
90
+ <span class="font-weight-medium">{{ t('audit.field.detail') }}:</span>
91
+ <v-sheet
92
+ color="grey-lighten-4"
93
+ rounded
94
+ class="pa-3 text-body-2 mt-1"
95
+ >
96
+ {{ audit.detail }}
97
+ </v-sheet>
98
+ </v-col>
99
+ </v-row>
100
+
101
+ <!-- CAMBIOS REGISTRADOS (SIN PAGINACIÓN) -->
102
+ <v-row
103
+ v-if="audit.changes && audit.changes.length"
104
+ class="mt-2"
105
+ >
106
+ <v-col cols="12">
107
+ <span class="font-weight-medium">{{ t('audit.field.changes') }}</span>
108
+
109
+ <v-data-table
110
+ class="mt-2"
111
+ density="compact"
112
+ :headers="changeHeaders"
113
+ :items="audit.changes"
114
+ :items-per-page="-1"
115
+ hide-default-footer
116
+ >
117
+ <template #item.old="{ item }">
118
+ <span
119
+ class="text-red-darken-2 text-body-2"
120
+ style="white-space: pre-wrap; word-break: break-all;"
121
+ >
122
+ {{ item.old ?? '-' }}
123
+ </span>
124
+ </template>
125
+
126
+ <template #item.new="{ item }">
127
+ <span
128
+ class="text-green-darken-2 text-body-2"
129
+ style="white-space: pre-wrap; word-break: break-all;"
130
+ >
131
+ {{ item.new ?? '-' }}
132
+ </span>
133
+ </template>
134
+ </v-data-table>
135
+ </v-col>
136
+ </v-row>
9
137
 
10
- const props = defineProps<Props>()
138
+ </template>
11
139
 
12
- const actionColor = computed(() => {
13
- const actionMap: Record<string, string> = {
14
- 'CREATE': 'success',
15
- 'UPDATE': 'warning',
16
- 'DELETE': 'error',
17
- 'READ': 'info',
18
- 'EXPORT': 'primary'
140
+ <script setup lang="ts">
141
+ import {useI18n} from 'vue-i18n'
142
+ import {formatDateTime} from '@drax/common-front'
143
+
144
+ interface IAudit {
145
+ _id: string
146
+ entity: string
147
+ resourceId?: string
148
+ user: {
149
+ id: string
150
+ username: string
151
+ rolName: string
19
152
  }
20
- return actionMap[props.audit.action] || 'secondary'
21
- })
22
-
23
- const actionIcon = computed(() => {
24
- const iconMap: Record<string, string> = {
25
- 'CREATE': 'mdi-plus-circle',
26
- 'UPDATE': 'mdi-pencil-circle',
27
- 'DELETE': 'mdi-delete-circle',
28
- 'READ': 'mdi-eye-circle',
29
- 'EXPORT': 'mdi-download-circle'
153
+ action: string
154
+ ip: string
155
+ userAgent: string
156
+ changes?: Array<{
157
+ field: string
158
+ old?: string
159
+ new?: string
160
+ }>
161
+ sessionId?: string
162
+ requestId?: string
163
+ detail?: string
164
+ tenant?: {
165
+ id: string
166
+ name: string
30
167
  }
31
- return iconMap[props.audit.action] || 'mdi-information-circle'
32
- })
33
-
34
- const formattedDate = computed(() => {
35
- if (!props.audit.createdAt) return 'N/A'
36
- return new Date(props.audit.createdAt).toLocaleString('es-ES', {
37
- year: 'numeric',
38
- month: '2-digit',
39
- day: '2-digit',
40
- hour: '2-digit',
41
- minute: '2-digit',
42
- second: '2-digit'
43
- })
44
- })
45
-
46
- const hasChanges = computed(() => {
47
- return props.audit.changes && props.audit.changes.length > 0
48
- })
49
-
50
- const copyToClipboard = (text: string) => {
51
- navigator.clipboard.writeText(text)
168
+ apiKey?: {
169
+ id: string
170
+ name: string
171
+ }
172
+ createdAt?: string
173
+ updatedAt?: string
52
174
  }
53
- </script>
54
-
55
- <template>
56
- <v-card class="audit-view-card" elevation="2">
57
- <!-- Header con acción y fecha -->
58
- <v-card-title class="d-flex align-center pa-4 bg-gradient">
59
- <v-icon :color="actionColor" size="32" class="mr-3">
60
- {{ actionIcon }}
61
- </v-icon>
62
- <div class="flex-grow-1">
63
- <div class="text-h6 font-weight-bold">
64
- {{ audit.action }}
65
- </div>
66
- <div class="text-caption text-medium-emphasis">
67
- {{ formattedDate }}
68
- </div>
69
- </div>
70
- <v-chip :color="actionColor" variant="flat" size="small" class="font-weight-bold">
71
- {{ audit.entity }}
72
- </v-chip>
73
- </v-card-title>
74
175
 
75
- <v-divider />
176
+ const {t} = useI18n()
76
177
 
77
- <v-card-text class="pa-6">
78
- <!-- Información General -->
79
- <div class="mb-8">
80
- <div class="section-header mb-4">
81
- <v-icon color="primary" class="mr-2">mdi-information</v-icon>
82
- <span class="text-subtitle-1 font-weight-bold">Información General</span>
83
- </div>
84
- <v-card variant="outlined" class="pa-4">
85
- <div class="info-row mb-4">
86
- <span class="info-label">Acción:</span>
87
- <v-chip :color="actionColor" variant="tonal" size="small" class="ml-2">
88
- {{ audit.action }}
89
- </v-chip>
90
- </div>
91
- <v-divider class="my-3" />
92
- <div class="info-row">
93
- <span class="info-label">Entidad:</span>
94
- <span class="info-value">{{ audit.entity }}</span>
95
- </div>
96
- </v-card>
97
- </div>
98
-
99
- <!-- Información del Usuario -->
100
- <div class="mb-8">
101
- <div class="section-header mb-4">
102
- <v-icon color="primary" class="mr-2">mdi-account-circle</v-icon>
103
- <span class="text-subtitle-1 font-weight-bold">Usuario</span>
104
- </div>
105
- <v-card variant="outlined" class="pa-4">
106
- <div class="info-row mb-4">
107
- <span class="info-label">Nombre de Usuario:</span>
108
- <span class="info-value">{{ audit.user.username }}</span>
109
- </div>
110
- <v-divider class="my-3" />
111
- <div class="info-row mb-4">
112
- <span class="info-label">Rol:</span>
113
- <v-chip size="small" color="primary" variant="tonal" class="ml-2">
114
- {{ audit.user.rolName }}
115
- </v-chip>
116
- </div>
117
- <v-divider class="my-3" />
118
- <div class="info-row">
119
- <span class="info-label">ID de Usuario:</span>
120
- <div class="d-flex align-center gap-2 ml-2">
121
- <span class="info-value font-monospace text-caption">{{ audit.user.id }}</span>
122
- <v-btn
123
- icon
124
- size="x-small"
125
- variant="text"
126
- @click="copyToClipboard(audit.user.id)"
127
- >
128
- <v-icon size="small">mdi-content-copy</v-icon>
129
- </v-btn>
130
- </div>
131
- </div>
132
- </v-card>
133
- </div>
134
-
135
- <!-- Información de Conexión -->
136
- <div class="mb-8">
137
- <div class="section-header mb-4">
138
- <v-icon color="info" class="mr-2">mdi-network</v-icon>
139
- <span class="text-subtitle-1 font-weight-bold">Información de Conexión</span>
140
- </div>
141
- <v-card variant="outlined" class="pa-4">
142
- <div class="info-row mb-4">
143
- <span class="info-label">
144
- <v-icon size="x-small" class="mr-1">mdi-ip</v-icon>
145
- Dirección IP:
146
- </span>
147
- <div class="d-flex align-center gap-2 ml-2">
148
- <v-chip size="small" variant="tonal" color="info">
149
- {{ audit.ip }}
150
- </v-chip>
151
- <v-btn
152
- icon
153
- size="x-small"
154
- variant="text"
155
- @click="copyToClipboard(audit.ip)"
156
- >
157
- <v-icon size="small">mdi-content-copy</v-icon>
158
- </v-btn>
159
- </div>
160
- </div>
161
- <v-divider class="my-3" />
162
- <div class="info-row">
163
- <span class="info-label">
164
- <v-icon size="x-small" class="mr-1">mdi-web</v-icon>
165
- User Agent:
166
- </span>
167
- <span class="info-value text-caption">{{ audit.userAgent }}</span>
168
- </div>
169
- </v-card>
170
- </div>
171
-
172
- <!-- Cambios Realizados -->
173
- <div v-if="hasChanges" class="mb-8">
174
- <div class="section-header mb-4">
175
- <v-icon color="warning" class="mr-2">mdi-file-document-edit</v-icon>
176
- <span class="text-subtitle-1 font-weight-bold">Cambios Realizados</span>
177
- <v-chip size="x-small" color="warning" variant="tonal" class="ml-2">
178
- {{ audit.changes?.length }} cambios
179
- </v-chip>
180
- </div>
181
- <v-card variant="outlined" class="overflow-hidden">
182
- <v-list lines="three" class="pa-0">
183
- <template v-for="(change, index) in audit.changes" :key="index">
184
- <v-list-item class="change-item">
185
- <template #prepend>
186
- <v-avatar color="warning" size="40" class="font-weight-bold">
187
- <v-icon size="small">mdi-swap-horizontal</v-icon>
188
- </v-avatar>
189
- </template>
190
- <div class="w-100">
191
- <v-list-item-title class="font-weight-bold text-base mb-3">
192
- Campo: {{ change.field }}
193
- </v-list-item-title>
194
- <div v-if="change.old" class="mb-2">
195
- <span class="info-label">Valor Anterior:</span>
196
- <v-chip
197
- size="small"
198
- color="error"
199
- variant="tonal"
200
- class="font-monospace ml-2 mt-1"
201
- >
202
- <v-icon start size="x-small">mdi-minus</v-icon>
203
- {{ change.old }}
204
- </v-chip>
205
- </div>
206
- <div v-if="change.new">
207
- <span class="info-label">Valor Nuevo:</span>
208
- <v-chip
209
- size="small"
210
- color="success"
211
- variant="tonal"
212
- class="font-monospace ml-2 mt-1"
213
- >
214
- <v-icon start size="x-small">mdi-plus</v-icon>
215
- {{ change.new }}
216
- </v-chip>
217
- </div>
218
- </div>
219
- </v-list-item>
220
- <v-divider v-if="index < audit.changes!.length - 1" />
221
- </template>
222
- </v-list>
223
- </v-card>
224
- </div>
178
+ defineProps<{
179
+ audit: IAudit
180
+ }>()
225
181
 
226
- <!-- Detalles Adicionales -->
227
- <div v-if="audit.detail" class="mb-8">
228
- <div class="section-header mb-4">
229
- <v-icon color="info" class="mr-2">mdi-text-box</v-icon>
230
- <span class="text-subtitle-1 font-weight-bold">Detalles Adicionales</span>
231
- </div>
232
- <v-card variant="outlined" class="pa-4">
233
- <pre class="detail-text">{{ audit.detail }}</pre>
234
- </v-card>
235
- </div>
182
+ const changeHeaders = [
183
+ {title: t('audit.field.field'), key: 'field'},
184
+ {title: t('audit.field.old'), key: 'old'},
185
+ {title: t('audit.field.new'), key: 'new'},
186
+ ]
236
187
 
237
- <!-- Información del Tenant -->
238
- <div v-if="audit.tenant" class="mb-8">
239
- <div class="section-header mb-4">
240
- <v-icon color="secondary" class="mr-2">mdi-domain</v-icon>
241
- <span class="text-subtitle-1 font-weight-bold">Tenant</span>
242
- </div>
243
- <v-card variant="outlined" class="pa-4">
244
- <div class="info-row mb-4">
245
- <span class="info-label">Nombre del Tenant:</span>
246
- <span class="info-value">{{ audit.tenant.name }}</span>
247
- </div>
248
- <v-divider class="my-3" />
249
- <div class="info-row">
250
- <span class="info-label">ID del Tenant:</span>
251
- <div class="d-flex align-center gap-2 ml-2">
252
- <span class="info-value text-caption font-monospace">{{ audit.tenant.id }}</span>
253
- <v-btn
254
- icon
255
- size="x-small"
256
- variant="text"
257
- @click="copyToClipboard(audit.tenant.id)"
258
- >
259
- <v-icon size="small">mdi-content-copy</v-icon>
260
- </v-btn>
261
- </div>
262
- </div>
263
- </v-card>
264
- </div>
265
188
 
266
- <!-- IDs de Sesión y Request -->
267
- <div>
268
- <div class="section-header mb-4">
269
- <v-icon color="grey" class="mr-2">mdi-identifier</v-icon>
270
- <span class="text-subtitle-1 font-weight-bold">Identificadores de Sesión</span>
271
- </div>
272
- <v-card variant="outlined" class="pa-4">
273
- <div class="info-row mb-4">
274
- <span class="info-label">ID de Auditoría:</span>
275
- <div class="d-flex align-center gap-2 ml-2">
276
- <span class="info-value text-caption font-monospace">{{ audit._id }}</span>
277
- <v-btn
278
- icon
279
- size="x-small"
280
- variant="text"
281
- @click="copyToClipboard(audit._id)"
282
- >
283
- <v-icon size="small">mdi-content-copy</v-icon>
284
- </v-btn>
285
- </div>
286
- </div>
287
- <v-divider class="my-3" />
288
- <div v-if="audit.sessionId" class="info-row mb-4">
289
- <span class="info-label">ID de Sesión:</span>
290
- <div class="d-flex align-center gap-2 ml-2">
291
- <span class="info-value text-caption font-monospace">{{ audit.sessionId }}</span>
292
- <v-btn
293
- icon
294
- size="x-small"
295
- variant="text"
296
- @click="copyToClipboard(audit.sessionId)"
297
- >
298
- <v-icon size="small">mdi-content-copy</v-icon>
299
- </v-btn>
300
- </div>
301
- </div>
302
- <v-divider class="my-3" />
303
- <div v-if="audit.requestId" class="info-row">
304
- <span class="info-label">ID de Petición:</span>
305
- <div class="d-flex align-center gap-2 ml-2">
306
- <span class="info-value text-caption font-monospace">{{ audit.requestId }}</span>
307
- <v-btn
308
- icon
309
- size="x-small"
310
- variant="text"
311
- @click="copyToClipboard(audit.requestId)"
312
- >
313
- <v-icon size="small">mdi-content-copy</v-icon>
314
- </v-btn>
315
- </div>
316
- </div>
317
- </v-card>
318
- </div>
319
- </v-card-text>
320
- </v-card>
321
- </template>
189
+ </script>
322
190
 
323
191
  <style scoped>
324
- .audit-view-card {
325
- border-radius: 12px;
326
- overflow: hidden;
327
- transition: all 0.3s ease;
328
- }
329
-
330
- .audit-view-card:hover {
331
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12) !important;
332
- }
333
-
334
- .bg-gradient {
335
- background: linear-gradient(135deg, rgb(var(--v-theme-surface-variant)) 0%, rgb(var(--v-theme-surface)) 100%);
336
- }
337
-
338
- .section-header {
192
+ .audit-row {
339
193
  display: flex;
340
- align-items: center;
341
- padding-bottom: 8px;
342
- border-bottom: 2px solid rgba(var(--v-theme-primary), 0.2);
343
- }
344
-
345
- .info-row {
346
- display: flex;
347
- flex-direction: column;
348
- gap: 6px;
349
- }
350
-
351
- .info-label {
352
- font-size: 0.875rem;
353
- font-weight: 700;
354
- color: rgb(var(--v-theme-on-surface-variant));
355
- text-transform: uppercase;
356
- }
357
-
358
- .info-value {
359
- font-size: 0.875rem;
360
- color: rgb(var(--v-theme-on-surface));
361
- }
362
-
363
- .detail-text {
364
- white-space: pre-wrap;
365
- word-break: break-word;
366
- margin: 0;
367
- padding: 0;
368
- font-family: 'Courier New', monospace;
369
- font-size: 0.875rem;
370
- color: rgb(var(--v-theme-on-surface));
371
- }
372
-
373
- .change-item {
374
- transition: background-color 0.2s ease;
194
+ align-items: flex-start;
195
+ gap: 12px;
375
196
  }
376
197
 
377
- .change-item:hover {
378
- background-color: rgba(var(--v-theme-surface-variant), 0.5);
198
+ .audit-label {
199
+ min-width: 150px;
200
+ text-align: right;
201
+ flex-shrink: 0;
379
202
  }
380
203
  </style>
@@ -42,16 +42,16 @@ class AuditCrud extends EntityCrud implements IEntityCrud {
42
42
  get headers(): IEntityCrudHeader[] {
43
43
  return [
44
44
  {title: 'createdAt',key:'createdAt', align: 'start'},
45
+ {title: 'action',key:'action', align: 'start'},
45
46
  {title: 'entity',key:'entity', align: 'start'},
47
+ {title: 'tenant',key:'tenant', align: 'start'},
46
48
  {title: 'user',key:'user', align: 'start'},
47
- {title: 'action',key:'action', align: 'start'},
48
49
  {title: 'ip',key:'ip', align: 'start'},
49
50
  {title: 'userAgent',key:'userAgent', align: 'start'},
50
51
  {title: 'changes',key:'changes', align: 'start'},
51
52
  {title: 'sessionId',key:'sessionId', align: 'start'},
52
53
  {title: 'requestId',key:'requestId', align: 'start'},
53
- {title: 'detail',key:'detail', align: 'start'},
54
- {title: 'tenant',key:'tenant', align: 'start'}
54
+ {title: 'detail',key:'detail', align: 'start'}
55
55
  ]
56
56
  }
57
57
 
@@ -99,6 +99,7 @@ class AuditCrud extends EntityCrud implements IEntityCrud {
99
99
 
100
100
  get fields(): IEntityCrudField[] {
101
101
  return [
102
+ {name: 'action', type: 'string', label: 'action', default: ''},
102
103
  {name: 'entity', type: 'string', label: 'entity', default: ''},
103
104
  {
104
105
  name: 'user',
@@ -109,7 +110,6 @@ class AuditCrud extends EntityCrud implements IEntityCrud {
109
110
  {name: 'username', type: 'string', label: 'username', default: ''},
110
111
  {name: 'rolName', type: 'string', label: 'rolName', default: ''}]
111
112
  },
112
- {name: 'action', type: 'string', label: 'action', default: ''},
113
113
  {name: 'ip', type: 'string', label: 'ip', default: ''},
114
114
  {name: 'userAgent', type: 'string', label: 'userAgent', default: ''},
115
115
  {
@@ -138,9 +138,25 @@ class AuditCrud extends EntityCrud implements IEntityCrud {
138
138
  get filters(): IEntityCrudFilter[] {
139
139
  return [
140
140
  //{name: '_id', type: 'string', label: 'ID', default: '', operator: 'eq' },
141
+ {name: 'action', type: 'string', label: 'action', default: '', operator: 'eq'},
142
+ {name: 'entity', type: 'string', label: 'entity', default: '', operator: 'eq'},
143
+ {name: 'tenant.name', type: 'string', label: 'tenant', default: '', operator: 'eq'},
144
+ {
145
+ name: 'user.username',
146
+ type: 'string',
147
+ label: 'Username',
148
+ default: '',
149
+ operator:'eq'
150
+ },
151
+ {name: 'ip', type: 'string', label: 'ip', default: '', operator: 'eq'},
152
+ {name: 'userAgent', type: 'string', label: 'userAgent', default: '', operator: 'eq'},
141
153
  ]
142
154
  }
143
155
 
156
+ get searchEnable() {
157
+ return false
158
+ }
159
+
144
160
  get isViewable() {
145
161
  return true
146
162
  }
@@ -16,6 +16,26 @@ const store = useCrudStore();
16
16
  {{ formatDateTime(value) }}
17
17
  </template>
18
18
 
19
+ <template v-slot:item.user="{value}">
20
+ {{ value.username }} ({{value.rolName}})
21
+ </template>
22
+
23
+ <template v-slot:item.tenant="{value}">
24
+ {{ value?.name }}
25
+ </template>
26
+
27
+ <template v-slot:item.changes="{value}">
28
+ <div v-if="value && value.length > 0" class="changes-container">
29
+ <div v-for="(change, index) in value" :key="index" class="change-item">
30
+ <span class="field-name">{{ change.field }}:</span>
31
+ <span class="old-value">{{ change.old }}</span>
32
+ <span class="arrow">→</span>
33
+ <span class="new-value">{{ change.new }}</span>
34
+ </div>
35
+ </div>
36
+ <span v-else class="no-changes">Sin cambios</span>
37
+ </template>
38
+
19
39
  <template v-slot:form>
20
40
  <audit-view :audit="store.form"></audit-view>
21
41
  </template>
@@ -25,4 +45,3 @@ const store = useCrudStore();
25
45
  <style scoped>
26
46
 
27
47
  </style>
28
-