@innertia-solutions/nuxt-theme-spark 0.1.87 → 0.1.89

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,96 +1,73 @@
1
1
  <script setup lang="ts">
2
- import * as icons from '@tabler/icons-vue'
2
+ const isOpen = ref(false)
3
+ const open = () => { isOpen.value = true }
4
+ const close = () => { isOpen.value = false }
3
5
 
4
- const props = defineProps({
5
- floating: { type: Boolean, default: false },
6
- user: { type: Object as () => { name?: string; email?: string } | null, default: null },
7
- })
8
-
9
- const emit = defineEmits(['logout'])
10
-
11
- const getInitials = (name?: string) => {
12
- if (!name) return 'U'
13
- const parts = name.split(' ')
14
- return parts.length > 1 ? parts[0][0] + parts[1][0] : parts[0][0]
15
- }
6
+ provide('vantage:sidebar', { isOpen, open, close })
16
7
  </script>
17
8
 
18
9
  <template>
19
- <aside
20
- id="hs-pro-sidebar"
21
- :class="[
22
- 'hs-overlay [--auto-close:lg]',
23
- 'hs-overlay-open:translate-x-0 -translate-x-full transition-transform duration-300 transform',
24
- 'w-65 hidden fixed z-60',
25
- 'lg:block lg:translate-x-0 lg:inset-e-auto',
26
- floating
27
- ? 'inset-y-3 start-3 rounded-2xl shadow-sm border border-sidebar-line bg-sidebar'
28
- : 'inset-y-0 inset-s-0 h-full border-e border-sidebar-line bg-sidebar lg:bottom-0',
29
- ]"
30
- tabindex="-1"
31
- aria-label="Sidebar"
32
- >
33
- <div class="relative flex flex-col h-full max-h-full pt-3">
34
- <!-- Logo + close button (mobile) -->
35
- <header class="h-11.5 ps-2 pe-2 lg:ps-5 flex items-center gap-x-1 shrink-0">
36
- <slot name="logo" />
37
- <div class="lg:hidden ms-auto">
38
- <button
39
- type="button"
40
- class="w-6 h-7 inline-flex justify-center items-center gap-x-2 text-sm font-medium rounded-md bg-layer border border-layer-line text-layer-foreground shadow-2xs hover:bg-layer-hover disabled:opacity-50 disabled:pointer-events-none focus:outline-hidden focus:bg-layer-focus"
41
- data-hs-overlay="#hs-pro-sidebar"
42
- >
43
- <svg class="shrink-0 size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
44
- <polyline points="7 8 3 12 7 16" />
45
- <line x1="21" x2="11" y1="12" y2="12" />
46
- <line x1="21" x2="11" y1="6" y2="6" />
47
- <line x1="21" x2="11" y1="18" y2="18" />
48
- </svg>
49
- </button>
50
- </div>
51
- </header>
10
+ <div class="bg-slate-50 dark:bg-slate-900 min-h-screen">
52
11
 
53
- <!-- Search slot -->
54
- <div v-if="$slots.search" class="px-4 py-2 shrink-0">
55
- <slot name="search" />
56
- </div>
12
+ <!-- Backdrop mobile -->
13
+ <Transition enter-from-class="opacity-0" enter-active-class="transition-opacity duration-300"
14
+ leave-to-class="opacity-0" leave-active-class="transition-opacity duration-300">
15
+ <div v-if="isOpen" class="lg:hidden fixed inset-0 z-50 bg-black/40 backdrop-blur-sm" @click="close" />
16
+ </Transition>
57
17
 
58
- <!-- Nav content - scrollable -->
59
- <div class="flex-1 min-h-0 mt-1.5 overflow-y-auto [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-track]:bg-scrollbar-track [&::-webkit-scrollbar-thumb]:bg-scrollbar-thumb">
60
- <slot name="menu" />
61
- </div>
18
+ <!-- Sidebar -->
19
+ <aside
20
+ tabindex="-1"
21
+ aria-label="Sidebar"
22
+ :class="[
23
+ 'fixed inset-y-0 start-0 z-60 w-65 h-full',
24
+ 'bg-white dark:bg-slate-800 border-e border-slate-200 dark:border-slate-700',
25
+ 'transition-transform duration-300',
26
+ 'lg:translate-x-0',
27
+ isOpen ? 'translate-x-0' : 'max-lg:-translate-x-full',
28
+ ]"
29
+ >
30
+ <div class="flex flex-col h-full pt-3 lg:pt-6">
62
31
 
63
- <!-- Sidebar footer -->
64
- <div class="shrink-0">
65
- <!-- Controls slot (dark mode, alerts, etc.) -->
66
- <div v-if="$slots['user-controls']" class="flex items-center gap-x-2 px-3 pt-2.5">
67
- <slot name="user-controls" />
68
- </div>
69
-
70
- <!-- User info -->
71
- <div class="flex items-center gap-x-2.5 px-3 py-5">
72
- <div class="size-8 rounded-full bg-blue-600 text-white text-xs font-semibold flex items-center justify-center uppercase shrink-0">
73
- {{ getInitials(user?.name) }}
74
- </div>
75
- <div class="min-w-0 flex-1">
76
- <p class="text-sm font-medium text-slate-800 dark:text-slate-200 truncate leading-tight">{{ user?.name ?? 'Usuario' }}</p>
77
- <p class="text-xs text-slate-400 dark:text-slate-500 truncate leading-tight">{{ user?.email ?? '—' }}</p>
32
+ <!-- Logo + close mobile -->
33
+ <header class="h-11.5 ps-2 pe-2 lg:ps-5 flex items-center gap-x-1 shrink-0">
34
+ <slot name="logo" />
35
+ <div class="lg:hidden ms-auto">
36
+ <button type="button"
37
+ class="w-6 h-7 inline-flex justify-center items-center rounded-md border border-slate-200 bg-white text-slate-500 hover:bg-slate-50 dark:bg-slate-800 dark:border-slate-700 dark:text-slate-300 dark:hover:bg-slate-700"
38
+ @click="close">
39
+ <svg class="shrink-0 size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
40
+ fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
41
+ <polyline points="7 8 3 12 7 16" />
42
+ <line x1="21" x2="11" y1="12" y2="12" />
43
+ <line x1="21" x2="11" y1="6" y2="6" />
44
+ <line x1="21" x2="11" y1="18" y2="18" />
45
+ </svg>
46
+ </button>
78
47
  </div>
79
- <button type="button" @click="emit('logout')" title="Cerrar sesión"
80
- class="shrink-0 inline-flex items-center justify-center size-7 rounded-lg text-slate-400 dark:text-slate-500 hover:bg-red-50 hover:text-red-500 dark:hover:bg-red-500/10 dark:hover:text-red-400 transition-colors"
81
- >
82
- <icons.IconLogout class="size-4" />
83
- </button>
48
+ </header>
49
+
50
+ <!-- Menu scrollable -->
51
+ <div class="flex-1 min-h-0 mt-1.5 overflow-y-auto
52
+ [&::-webkit-scrollbar]:w-2
53
+ [&::-webkit-scrollbar-thumb]:rounded-full
54
+ [&::-webkit-scrollbar-track]:bg-slate-100
55
+ [&::-webkit-scrollbar-thumb]:bg-slate-300
56
+ dark:[&::-webkit-scrollbar-track]:bg-slate-700
57
+ dark:[&::-webkit-scrollbar-thumb]:bg-slate-500">
58
+ <slot name="menu" />
84
59
  </div>
85
60
 
86
- <!-- Env bar -->
87
- <div class="px-3 pb-3">
88
- <App.DevEnvironmentBar sidebar />
61
+ <!-- User footer -->
62
+ <div class="shrink-0 border-t border-slate-200 dark:border-slate-700 p-5">
63
+ <slot name="user-footer" />
89
64
  </div>
65
+
90
66
  </div>
91
- </div>
92
- </aside>
67
+ </aside>
68
+
69
+ <!-- Main content -->
70
+ <slot />
93
71
 
94
- <!-- Main content -->
95
- <slot />
72
+ </div>
96
73
  </template>
@@ -0,0 +1,32 @@
1
+ <script setup lang="ts">
2
+ defineProps<{
3
+ title?: string
4
+ }>()
5
+
6
+ const sidebar = inject('vantage:sidebar', null) as any
7
+ </script>
8
+
9
+ <template>
10
+ <header class="sticky top-0 z-40 bg-white dark:bg-slate-800 border-b border-slate-200 dark:border-slate-700 px-4 lg:px-6 h-14 flex items-center gap-x-3">
11
+ <!-- Hamburger mobile -->
12
+ <button type="button"
13
+ class="lg:hidden size-8 flex items-center justify-center text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200"
14
+ @click="sidebar?.open()">
15
+ <svg class="size-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
16
+ <line x1="3" y1="6" x2="21" y2="6" /><line x1="3" y1="12" x2="21" y2="12" /><line x1="3" y1="18" x2="21" y2="18" />
17
+ </svg>
18
+ </button>
19
+
20
+ <!-- Left slot -->
21
+ <div class="flex-1 flex items-center gap-x-3">
22
+ <slot name="left">
23
+ <span v-if="title" class="text-sm font-medium text-slate-700 dark:text-slate-300">{{ title }}</span>
24
+ </slot>
25
+ </div>
26
+
27
+ <!-- Right slot -->
28
+ <div class="flex items-center gap-x-2">
29
+ <slot name="right" />
30
+ </div>
31
+ </header>
32
+ </template>
@@ -1,73 +1,7 @@
1
- <script setup lang="ts">
2
- import * as icons from '@tabler/icons-vue'
3
-
4
- const props = defineProps({
5
- title: { type: String, default: '' },
6
- description: { type: String, default: '' },
7
- icon: { type: String, default: '' },
8
- color: { type: String, default: 'gray' },
9
- sticky: { type: Boolean, default: true },
10
- })
11
-
12
- const colorMap: Record<string, { icon: string; bg: string; border: string }> = {
13
- gray: { icon: 'text-slate-500 dark:text-slate-400', bg: 'bg-slate-100 dark:bg-slate-800', border: 'border-slate-200 dark:border-slate-700' },
14
- blue: { icon: 'text-blue-600 dark:text-blue-400', bg: 'bg-blue-50 dark:bg-blue-500/10', border: 'border-blue-200 dark:border-blue-500/30' },
15
- green: { icon: 'text-green-600 dark:text-green-400', bg: 'bg-green-50 dark:bg-green-500/10', border: 'border-green-200 dark:border-green-500/30' },
16
- red: { icon: 'text-red-600 dark:text-red-400', bg: 'bg-red-50 dark:bg-red-500/10', border: 'border-red-200 dark:border-red-500/30' },
17
- amber: { icon: 'text-amber-600 dark:text-amber-400', bg: 'bg-amber-50 dark:bg-amber-500/10', border: 'border-amber-200 dark:border-amber-500/30' },
18
- purple: { icon: 'text-purple-600 dark:text-purple-400', bg: 'bg-purple-50 dark:bg-purple-500/10', border: 'border-purple-200 dark:border-purple-500/30' },
19
- teal: { icon: 'text-teal-600 dark:text-teal-400', bg: 'bg-teal-50 dark:bg-teal-500/10', border: 'border-teal-200 dark:border-teal-500/30' },
20
- }
21
-
22
- const accent = computed(() => colorMap[props.color] ?? colorMap.gray)
23
- const iconComponent = computed(() => props.icon ? (icons as any)[props.icon] : null)
24
- </script>
1
+ <script setup></script>
25
2
 
26
3
  <template>
27
- <div class="space-y-4">
28
-
29
- <!-- Page header card -->
30
- <div :class="sticky ? 'sticky top-3 z-20' : ''">
31
- <div class="flex items-center justify-between bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-2xl shadow-sm px-4 py-3">
32
- <div class="flex items-center gap-x-4">
33
-
34
- <!-- Icon -->
35
- <div v-if="iconComponent"
36
- class="shrink-0 size-10 rounded-xl flex items-center justify-center border"
37
- :class="[accent.bg, accent.border]"
38
- >
39
- <component :is="iconComponent" class="size-5" :class="accent.icon" />
40
- </div>
41
-
42
- <!-- Title + description -->
43
- <div>
44
- <div class="flex items-baseline gap-x-2">
45
- <h1 class="text-lg font-semibold text-slate-800 dark:text-slate-100">{{ title }}</h1>
46
- <template v-if="description">
47
- <span class="size-1 rounded-full bg-slate-300 dark:bg-slate-600 shrink-0 self-center"></span>
48
- <p class="text-sm text-slate-400 dark:text-slate-500">{{ description }}</p>
49
- </template>
50
- </div>
51
- <div v-if="$slots.breadcrumb" class="flex items-center gap-x-1 mt-0.5">
52
- <slot name="breadcrumb" />
53
- </div>
54
- </div>
55
- </div>
56
-
57
- <!-- Actions -->
58
- <div v-if="$slots.actions" class="flex items-center gap-x-2">
59
- <slot name="actions" />
60
- </div>
61
- </div>
62
- </div>
63
-
64
- <!-- Tabs -->
65
- <div v-if="$slots.tabs">
66
- <slot name="tabs" :color="color" />
67
- </div>
68
-
69
- <!-- Content -->
4
+ <div class="lg:ps-65">
70
5
  <slot />
71
-
72
6
  </div>
73
7
  </template>
@@ -57,7 +57,7 @@ onBeforeUnmount(() => {
57
57
  <style scoped>
58
58
  .page-loading-spinner {
59
59
  position: fixed;
60
- bottom: 0.625rem;
60
+ top: 0.625rem;
61
61
  right: 0.5rem;
62
62
  z-index: 2147483647;
63
63
  display: flex;
@@ -1,8 +1,4 @@
1
1
  <script setup>
2
- const props = defineProps({
3
- floating: { type: Boolean, default: false },
4
- })
5
-
6
2
  const showAnimation = ref(false)
7
3
  onMounted(() => {
8
4
  const seen = sessionStorage.getItem('auth-entered')
@@ -15,57 +11,12 @@ onMounted(() => {
15
11
 
16
12
  <template>
17
13
  <div :class="{ 'animate-entrance': showAnimation }">
18
- <!-- Header - matches Preline exactly -->
19
- <header class="lg:ms-65 fixed top-0 inset-x-0 flex flex-wrap md:justify-start md:flex-nowrap z-50 bg-navbar border-b border-navbar-line">
20
- <div class="flex justify-between xl:grid xl:grid-cols-3 basis-full items-center w-full py-2.5 px-2 sm:px-5">
21
-
22
- <!-- Left col: mobile toggle + desktop search -->
23
- <div class="xl:col-span-1 flex items-center md:gap-x-3">
24
- <!-- Mobile sidebar toggle -->
25
- <div class="lg:hidden">
26
- <button
27
- type="button"
28
- class="w-7 h-9.5 inline-flex justify-center items-center gap-x-2 text-sm font-medium rounded-lg bg-layer border border-layer-line text-layer-foreground shadow-2xs hover:bg-layer-hover disabled:opacity-50 disabled:pointer-events-none focus:outline-hidden focus:bg-layer-focus"
29
- aria-haspopup="dialog"
30
- aria-expanded="false"
31
- aria-controls="hs-pro-sidebar"
32
- aria-label="Toggle navigation"
33
- data-hs-overlay="#hs-pro-sidebar"
34
- >
35
- <svg class="shrink-0 size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
36
- <path d="M17 8L21 12L17 16M3 12H13M3 6H13M3 18H13" />
37
- </svg>
38
- </button>
39
- </div>
40
-
41
- <!-- Desktop search -->
42
- <div class="hidden lg:block min-w-80 xl:w-full">
43
- <slot name="header-left" />
44
- </div>
45
- </div>
46
-
47
- <!-- Right col: actions -->
48
- <div class="xl:col-span-2 flex justify-end items-center gap-x-2">
49
- <div class="flex items-center">
50
- <!-- Mobile search icon -->
51
- <div class="lg:hidden">
52
- <slot name="header-search-mobile" />
53
- </div>
54
-
55
- <slot name="header-right" />
56
- </div>
57
- </div>
58
-
59
- </div>
60
- </header>
61
-
62
- <!-- Sidebar + main content -->
63
- <AdminBase :floating="floating">
14
+ <AdminBase>
64
15
  <template #logo><slot name="logo" /></template>
65
16
  <template #menu><slot name="menu" /></template>
66
17
  <template #user-footer><slot name="user-footer" /></template>
67
18
 
68
- <div :class="floating ? 'lg:ps-70 pt-14' : 'lg:ps-65 pt-14'">
19
+ <div class="lg:ps-65">
69
20
  <slot />
70
21
  </div>
71
22
  </AdminBase>
@@ -10,26 +10,11 @@ interface Tab {
10
10
 
11
11
  const props = withDefaults(defineProps<{
12
12
  tabs: Tab[]
13
- color?: string
14
13
  activeClass?: string
15
14
  }>(), {
16
- color: 'blue',
15
+ activeClass: 'bg-white dark:bg-slate-800 text-blue-600 dark:text-blue-400 shadow-sm',
17
16
  })
18
17
 
19
- const colorMap: Record<string, string> = {
20
- gray: 'bg-white dark:bg-slate-800 text-slate-700 dark:text-slate-200 shadow-sm',
21
- blue: 'bg-white dark:bg-slate-800 text-blue-600 dark:text-blue-400 shadow-sm',
22
- green: 'bg-white dark:bg-slate-800 text-green-600 dark:text-green-400 shadow-sm',
23
- red: 'bg-white dark:bg-slate-800 text-red-600 dark:text-red-400 shadow-sm',
24
- amber: 'bg-white dark:bg-slate-800 text-amber-600 dark:text-amber-400 shadow-sm',
25
- purple: 'bg-white dark:bg-slate-800 text-purple-600 dark:text-purple-400 shadow-sm',
26
- teal: 'bg-white dark:bg-slate-800 text-teal-600 dark:text-teal-400 shadow-sm',
27
- }
28
-
29
- const resolvedActiveClass = computed(() =>
30
- props.activeClass ?? colorMap[props.color] ?? colorMap.blue
31
- )
32
-
33
18
  const route = useRoute()
34
19
 
35
20
  const isActive = (tab: Tab) =>
@@ -37,14 +22,14 @@ const isActive = (tab: Tab) =>
37
22
  </script>
38
23
 
39
24
  <template>
40
- <div class="flex items-center gap-x-1 p-1 bg-slate-100 dark:bg-slate-900/50 rounded-xl w-fit border border-slate-200 dark:border-slate-800">
25
+ <div class="flex items-center gap-x-1 p-1 bg-slate-100 dark:bg-slate-900/50 rounded-xl w-fit border border-slate-200 dark:border-slate-700">
41
26
  <NuxtLink
42
27
  v-for="tab in tabs"
43
28
  :key="tab.to"
44
29
  :to="tab.to"
45
30
  class="flex items-center gap-x-2 px-4 py-2 text-xs font-bold rounded-lg transition-all"
46
31
  :class="isActive(tab)
47
- ? resolvedActiveClass
32
+ ? activeClass
48
33
  : 'text-slate-500 dark:text-slate-400 hover:text-slate-700 dark:hover:text-slate-200'"
49
34
  >
50
35
  <component :is="tab.icon" v-if="tab.icon" class="size-4" />
@@ -3,10 +3,9 @@ import { IconSearch, IconLoader2, IconCheck, IconX } from '@tabler/icons-vue'
3
3
 
4
4
  // Dense database view with inline cell editing — click cell → input → blur → mutation
5
5
  const props = defineProps({
6
- table: { type: Object, default: null },
7
- endpoint: { type: String, default: undefined },
8
- columns: { type: Array, required: true }, // [{ key, label, editable?, type?: 'text'|'number'|'select', options?: [] }]
9
- name: { type: String, default: undefined },
6
+ endpoint: { type: String, required: true },
7
+ columns: { type: Array, required: true }, // [{ key, label, editable?, type?: 'text'|'number'|'select', options?: [] }]
8
+ name: { type: String, required: true },
10
9
  params: { type: Object, default: () => ({}) },
11
10
  updateMutation: { type: Function, default: null }, // (id, field, value) => Promise
12
11
  cached: { type: Boolean, default: false },
@@ -16,9 +15,6 @@ const props = defineProps({
16
15
 
17
16
  const emit = defineEmits(['row-click', 'cell-save'])
18
17
 
19
- const resolvedEndpoint = computed(() => props.table?.endpoint ?? props.endpoint)
20
- const resolvedName = computed(() => props.table?.name ?? props.name)
21
-
22
18
  const tableRef = ref(null)
23
19
 
24
20
  // ─── Inline editing ───────────────────────────────────────────────────────────
@@ -101,9 +97,9 @@ defineExpose({ reload })
101
97
  <div class="overflow-x-auto border border-slate-200 dark:border-slate-700 rounded-xl">
102
98
  <Table
103
99
  ref="tableRef"
104
- :endpoint="resolvedEndpoint"
100
+ :endpoint="endpoint"
105
101
  :columns="columns"
106
- :name="resolvedName"
102
+ :name="name"
107
103
  :params="mergedParams"
108
104
  :search="search"
109
105
  :cached="cached"
@@ -31,7 +31,7 @@ const exportTable = (format) => {
31
31
  <button
32
32
  id="hs-as-table-table-export-dropdown"
33
33
  type="button"
34
- class="py-1.5 sm:py-2 px-2.5 inline-flex items-center gap-x-1.5 text-sm sm:text-xs font-medium rounded-lg border border-slate-200 bg-white text-slate-800 shadow-2xs hover:bg-slate-50 disabled:opacity-50 disabled:pointer-events-none focus:outline-hidden focus:bg-slate-50 dark:bg-slate-900 dark:border-slate-800 dark:text-slate-300 dark:hover:bg-slate-700 dark:focus:bg-slate-700"
34
+ class="py-1.5 sm:py-2 px-2.5 inline-flex items-center gap-x-1.5 text-sm sm:text-xs font-medium rounded-lg border border-slate-200 bg-white text-slate-800 shadow-2xs hover:bg-slate-50 disabled:opacity-50 disabled:pointer-events-none focus:outline-hidden focus:bg-slate-50 dark:bg-slate-800 dark:border-slate-700 dark:text-slate-300 dark:hover:bg-slate-700 dark:focus:bg-slate-700"
35
35
  aria-haspopup="menu"
36
36
  aria-expanded="false"
37
37
  aria-label="Dropdown"
@@ -40,7 +40,7 @@ const exportTable = (format) => {
40
40
  Exportar
41
41
  </button>
42
42
  <div
43
- class="hs-dropdown-menu transition-[opacity,margin] duration hs-dropdown-open:opacity-100 opacity-0 hidden divide-y divide-slate-200 min-w-48 z-10 bg-white shadow-md rounded-lg p-2 mt-2 dark:divide-slate-800 dark:bg-slate-900 dark:border dark:border-slate-800 border-t border-slate-200 dark:border-slate-800"
43
+ class="hs-dropdown-menu transition-[opacity,margin] duration hs-dropdown-open:opacity-100 opacity-0 hidden divide-y divide-slate-200 min-w-48 z-10 bg-white shadow-md rounded-lg p-2 mt-2 dark:divide-slate-700 dark:bg-slate-800 dark:border dark:border-slate-700 border-t border-slate-200 dark:border-slate-700"
44
44
  role="menu"
45
45
  aria-orientation="vertical"
46
46
  aria-labelledby="hs-as-table-table-export-dropdown"
@@ -90,7 +90,7 @@ const exportTable = (format) => {
90
90
  <input
91
91
  type="checkbox"
92
92
  v-model="exportAllPages"
93
- class="shrink-0 border-slate-300 rounded-sm text-blue-600 focus:ring-blue-500 dark:bg-slate-900 dark:border-slate-600"
93
+ class="shrink-0 border-slate-300 rounded-sm text-blue-600 focus:ring-blue-500 dark:bg-slate-800 dark:border-slate-600"
94
94
  />
95
95
  Todas las páginas
96
96
  </label>
@@ -101,7 +101,7 @@ const exportTable = (format) => {
101
101
  <input
102
102
  type="checkbox"
103
103
  v-model="exportFilteredRows"
104
- class="shrink-0 border-slate-300 rounded-sm text-blue-600 focus:ring-blue-500 dark:bg-slate-900 dark:border-slate-600"
104
+ class="shrink-0 border-slate-300 rounded-sm text-blue-600 focus:ring-blue-500 dark:bg-slate-800 dark:border-slate-600"
105
105
  />
106
106
  Solo filas filtradas
107
107
  </label>
@@ -55,8 +55,8 @@ const displayText = computed(() => {
55
55
 
56
56
  const selectClasses = computed(() => {
57
57
  const base =
58
- "relative w-full rounded-lg border bg-white dark:bg-slate-900 transition-colors cursor-pointer text-slate-900 dark:text-white py-2 px-3 text-sm focus:outline-none focus:ring-0 focus:border-gray-400";
59
- const validation = "border-gray-200 dark:border-slate-800";
58
+ "relative w-full rounded-lg border bg-white dark:bg-slate-800 transition-colors cursor-pointer text-slate-900 dark:text-white py-2 px-3 text-sm focus:outline-none focus:ring-0 focus:border-gray-400";
59
+ const validation = "border-gray-200 dark:border-slate-700";
60
60
  const disabled = props.disabled ? "opacity-50 cursor-not-allowed" : "";
61
61
  return `${base} ${validation} ${disabled}`;
62
62
  });
@@ -169,7 +169,7 @@ onUnmounted(() => document.removeEventListener("mousedown", handleClickOutside))
169
169
  <div
170
170
  v-show="isOpen"
171
171
  :class="[
172
- 'absolute z-50 w-full mt-1 bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-xl shadow-xl max-h-60 overflow-auto',
172
+ 'absolute z-50 w-full mt-1 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-xl shadow-xl max-h-60 overflow-auto',
173
173
  menuClass,
174
174
  ]"
175
175
  >
@@ -3,10 +3,9 @@ import { IconSearch } from '@tabler/icons-vue'
3
3
 
4
4
  // Grid / card layout view — wraps DataTable with viewMode="grid"
5
5
  const props = defineProps({
6
- table: { type: Object, default: null },
7
- endpoint: { type: String, default: undefined },
8
- columns: { type: Array, required: true },
9
- name: { type: String, default: undefined },
6
+ endpoint: { type: String, required: true },
7
+ columns: { type: Array, required: true },
8
+ name: { type: String, required: true },
10
9
  params: { type: Object, default: () => ({}) },
11
10
  cached: { type: Boolean, default: true },
12
11
  searchPlaceholder: { type: String, default: 'Buscar...' },
@@ -17,9 +16,6 @@ const props = defineProps({
17
16
 
18
17
  const emit = defineEmits(['row-click', 'loaded'])
19
18
 
20
- const resolvedEndpoint = computed(() => props.table?.endpoint ?? props.endpoint)
21
- const resolvedName = computed(() => props.table?.name ?? props.name)
22
-
23
19
  const search = ref('')
24
20
  const tableRef = ref(null)
25
21
 
@@ -45,9 +41,9 @@ defineExpose({ reload, clearCache })
45
41
 
46
42
  <DataTable
47
43
  ref="tableRef"
48
- :endpoint="resolvedEndpoint"
44
+ :endpoint="endpoint"
49
45
  :columns="columns"
50
- :name="resolvedName"
46
+ :name="name"
51
47
  :params="params"
52
48
  :search="search"
53
49
  :cached="cached"
@@ -6,9 +6,8 @@ import { useQueryClient } from '@tanstack/vue-query'
6
6
  // Usage: <TableKanban endpoint="..." :states="[{key:'todo',label:'Pendiente',color:'slate'}]" state-key="status" ... />
7
7
 
8
8
  const props = defineProps({
9
- table: { type: Object, default: null },
10
- endpoint: { type: String, default: undefined },
11
- name: { type: String, default: undefined },
9
+ endpoint: { type: String, required: true }, // POST endpoint returning paginated list
10
+ name: { type: String, required: true }, // used as queryKey base
12
11
  params: { type: Object, default: () => ({}) },
13
12
  stateKey: { type: String, default: 'status' }, // field in row that holds the state key
14
13
  states: { // column definitions
@@ -26,22 +25,6 @@ const emit = defineEmits(['move', 'card-click'])
26
25
  const api = useApi()
27
26
  const queryClient = useQueryClient()
28
27
 
29
- const resolvedEndpoint = computed(() => props.table?.endpoint ?? props.endpoint)
30
- const resolvedName = computed(() => props.table?.name ?? props.name)
31
-
32
- // ─── TanStack invalidation signal ─────────────────────────────────────────────
33
- const { data: _invalidateSignal } = useQuery({
34
- queryKey: computed(() => ['table', resolvedName.value]),
35
- queryFn: () => Date.now(),
36
- initialData: 0,
37
- staleTime: Infinity,
38
- refetchOnWindowFocus: false,
39
- refetchOnMount: false,
40
- })
41
- let _signalReady = false
42
- onMounted(() => { _signalReady = true })
43
- watch(_invalidateSignal, () => { if (_signalReady) fetchAll() })
44
-
45
28
  // ─── Fetch all rows ───────────────────────────────────────────────────────────
46
29
  const loading = ref(false)
47
30
  const rows = ref([])
@@ -49,7 +32,7 @@ const rows = ref([])
49
32
  const fetchAll = async () => {
50
33
  loading.value = true
51
34
  try {
52
- const res = await api.post(resolvedEndpoint.value, { perPage: props.perPage, ...props.params })
35
+ const res = await api.post(props.endpoint, { perPage: props.perPage, ...props.params })
53
36
  rows.value = Array.isArray(res?.data) ? res.data : (Array.isArray(res) ? res : [])
54
37
  } catch (e) {
55
38
  console.error('[Kanban] fetch error', e)
@@ -4,9 +4,8 @@ import { useInfiniteQuery } from '@tanstack/vue-query'
4
4
 
5
5
  // Infinite scroll list — fetches next page when sentinel enters viewport
6
6
  const props = defineProps({
7
- table: { type: Object, default: null },
8
- endpoint: { type: String, default: undefined },
9
- name: { type: String, default: undefined },
7
+ endpoint: { type: String, required: true },
8
+ name: { type: String, required: true },
10
9
  params: { type: Object, default: () => ({}) },
11
10
  searchPlaceholder: { type: String, default: 'Buscar...' },
12
11
  showSearch: { type: Boolean, default: true },
@@ -16,10 +15,6 @@ const props = defineProps({
16
15
  const emit = defineEmits(['row-click'])
17
16
 
18
17
  const api = useApi()
19
-
20
- const resolvedEndpoint = computed(() => props.table?.endpoint ?? props.endpoint)
21
- const resolvedName = computed(() => props.table?.name ?? props.name)
22
-
23
18
  const search = ref('')
24
19
  const sentinel = ref(null)
25
20
 
@@ -30,12 +25,12 @@ watch(search, (v) => {
30
25
  searchTimeout = setTimeout(() => { debouncedSearch.value = v }, 400)
31
26
  })
32
27
 
33
- const queryKey = computed(() => ['table', resolvedName.value, 'infinite', debouncedSearch.value, props.params])
28
+ const queryKey = computed(() => [props.name, 'infinite', debouncedSearch.value, props.params])
34
29
 
35
30
  const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, refetch } = useInfiniteQuery({
36
31
  queryKey,
37
32
  queryFn: ({ pageParam = 1 }) =>
38
- api.post(resolvedEndpoint.value, {
33
+ api.post(props.endpoint, {
39
34
  search: debouncedSearch.value,
40
35
  page: pageParam,
41
36
  perPage: props.perPage,