@innertia-solutions/innertia-nuxt 0.1.30 → 0.1.32

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.
@@ -7,13 +7,22 @@ const props = defineProps<{
7
7
  hint?: string
8
8
  error?: string
9
9
  disabled?: boolean
10
+ /** Opciones cargándose desde BD: muestra un loader en vez del select. */
11
+ loading?: boolean
12
+ /** Falló la carga de opciones: muestra mensaje + botón Reintentar (emite 'retry'). */
13
+ loadError?: boolean | string
10
14
  }>()
11
15
 
12
16
  const emit = defineEmits<{
13
17
  'update:modelValue': [value: string | number | null]
14
18
  change: [value: string | number | null]
19
+ retry: []
15
20
  }>()
16
21
 
22
+ const loadErrorMessage = computed(() =>
23
+ typeof props.loadError === 'string' && props.loadError ? props.loadError : 'No se pudieron cargar las opciones.'
24
+ )
25
+
17
26
  const selectRef = ref<HTMLSelectElement | null>(null)
18
27
 
19
28
  /**
@@ -69,8 +78,26 @@ const handleChange = (e: Event) => {
69
78
  {{ label }}
70
79
  </label>
71
80
 
81
+ <!-- Cargando opciones desde BD -->
82
+ <div v-if="loading" class="innertia-field flex items-center gap-2 text-muted-foreground cursor-default">
83
+ <svg class="animate-spin size-4 shrink-0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
84
+ <circle cx="12" cy="12" r="10" opacity=".25" /><path d="M22 12a10 10 0 0 1-10 10" />
85
+ </svg>
86
+ <span class="text-sm">Cargando…</span>
87
+ </div>
88
+
89
+ <!-- Error al cargar opciones → botón reintentar -->
90
+ <div v-else-if="loadError" class="innertia-field flex items-center justify-between gap-2 select-error">
91
+ <span class="text-sm text-red-500 truncate">{{ loadErrorMessage }}</span>
92
+ <button type="button" @click="emit('retry')"
93
+ class="shrink-0 inline-flex items-center gap-1 text-xs font-medium text-primary hover:underline">
94
+ <svg class="size-3.5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 9-9 9 9 0 0 0-6.36 2.64L3 8" /><path d="M3 3v5h5" /></svg>
95
+ Reintentar
96
+ </button>
97
+ </div>
98
+
72
99
  <!-- Select (HSSelect) -->
73
- <ClientOnly>
100
+ <ClientOnly v-else>
74
101
  <template #fallback>
75
102
  <div class="innertia-field bg-surface animate-pulse" />
76
103
  </template>
@@ -211,6 +211,41 @@ const fetchInitial = async () => {
211
211
  }
212
212
  }
213
213
 
214
+ /**
215
+ * Refresca el árbol PRESERVANDO la expansión actual: re-fetchea los roots y los
216
+ * hijos de cada nodo expandido (datos frescos, sin colapsar ni resetear scroll).
217
+ * Útil tras mutar un nodo (ej. marcar revisado) sin remontar el componente.
218
+ */
219
+ const reload = async () => {
220
+ loading.value = true
221
+ isFetching.value = true
222
+ isDataFromCache.value = false
223
+ try {
224
+ const res = await api.post(props.endpoint, buildBody())
225
+ roots.value = res?.data ?? []
226
+ meta.value = res?.meta ?? null
227
+
228
+ const expanded = [...expandedSet.value]
229
+ const fresh = {}
230
+ await Promise.all(expanded.map(async (nodeId) => {
231
+ try {
232
+ const r = await api.post(props.endpoint, buildBody({ expand: nodeId }))
233
+ fresh[nodeId] = r?.data ?? []
234
+ } catch {
235
+ fresh[nodeId] = childrenById.value[nodeId] ?? []
236
+ }
237
+ }))
238
+ childrenById.value = fresh
239
+ if (props.cached) saveToCache()
240
+ emit('loaded', res)
241
+ } catch (e) {
242
+ console.error('[Tree.Standard] reload failed:', e)
243
+ } finally {
244
+ loading.value = false
245
+ isFetching.value = false
246
+ }
247
+ }
248
+
214
249
  // ─── Cache ────────────────────────────────────────────────────────────────────
215
250
  const cacheKey = computed(() => {
216
251
  if (!props.cached || !props.name) return null
@@ -527,6 +562,7 @@ onBeforeUnmount(() => {
527
562
 
528
563
  defineExpose({
529
564
  refresh: fetchInitial,
565
+ reload, // refresca preservando expansión (sin colapsar)
530
566
  clearChecked,
531
567
  getChecked: () => [...checkedSet.value],
532
568
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@innertia-solutions/innertia-nuxt",
3
- "version": "0.1.30",
3
+ "version": "0.1.32",
4
4
  "description": "Innertia Solutions — Nuxt layer unificada: core, auth, multitenancy, theme y app contexts",
5
5
  "keywords": [
6
6
  "nuxt",