@innertia-solutions/innertia-nuxt 0.1.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.
Files changed (108) hide show
  1. package/.github/workflows/auto-publish.yml +64 -0
  2. package/.github/workflows/release.yml +59 -0
  3. package/README.md +60 -0
  4. package/app.config.ts +70 -0
  5. package/components/Admin/Base.vue +144 -0
  6. package/components/Admin/Header.vue +32 -0
  7. package/components/Admin/Page.vue +65 -0
  8. package/components/Admin/PageHeader.vue +31 -0
  9. package/components/App/Button.vue +59 -0
  10. package/components/App/DevEnvironmentBar.vue +43 -0
  11. package/components/App/Dropdown.vue +286 -0
  12. package/components/App/EmptyState.vue +433 -0
  13. package/components/App/LoadingState.vue +40 -0
  14. package/components/App/PageLoadingSpinner.vue +118 -0
  15. package/components/App/PreviewDock.vue +64 -0
  16. package/components/App/SwitchColorTheme.vue +51 -0
  17. package/components/App/Tag.vue +193 -0
  18. package/components/DataTable.vue +713 -0
  19. package/components/Forms/DatePicker.vue +255 -0
  20. package/components/Forms/Input.vue +75 -0
  21. package/components/Forms/Select.vue +100 -0
  22. package/components/Forms/SelectServer.vue +726 -0
  23. package/components/Layout/Admin.vue +32 -0
  24. package/components/Layout/Auth.vue +29 -0
  25. package/components/Layout/SidebarWithAppColumn.vue +388 -0
  26. package/components/Layout/TopBar.vue +113 -0
  27. package/components/MobileBlocker.vue +85 -0
  28. package/components/MobileLoginPicker.vue +83 -0
  29. package/components/Modal/Base.vue +29 -0
  30. package/components/Modal/DeleteConfirm.vue +48 -0
  31. package/components/Modal.vue +103 -0
  32. package/components/Nav/Tabs.vue +55 -0
  33. package/components/PermissionsTree.vue +272 -0
  34. package/components/Table/Database.vue +183 -0
  35. package/components/Table/DownloadDropdown.vue +111 -0
  36. package/components/Table/Enterprise.vue +540 -0
  37. package/components/Table/FilterDropdown.vue +226 -0
  38. package/components/Table/Grid.vue +62 -0
  39. package/components/Table/Kanban.vue +188 -0
  40. package/components/Table/List.vue +128 -0
  41. package/components/Table/PreviewTimeline.vue +118 -0
  42. package/components/Table/Standard.vue +1217 -0
  43. package/components/Table/index.vue +974 -0
  44. package/components/TableExportable.vue +172 -0
  45. package/components/TableFilter.vue +93 -0
  46. package/components/Toast/Alert.vue +113 -0
  47. package/components/Toast/Container.vue +34 -0
  48. package/components/Toast/Notification.vue +45 -0
  49. package/components/Toast/Process.vue +88 -0
  50. package/composables/useApi.js +95 -0
  51. package/composables/useApp.ts +46 -0
  52. package/composables/useAuth.js +82 -0
  53. package/composables/useContext.js +44 -0
  54. package/composables/useDate.js +241 -0
  55. package/composables/useDevice.js +21 -0
  56. package/composables/useDockedPreviews.js +56 -0
  57. package/composables/useDownload.js +87 -0
  58. package/composables/useEntity.js +82 -0
  59. package/composables/useForm.js +119 -0
  60. package/composables/useInnertiaMode.ts +25 -0
  61. package/composables/useMobileGuard.ts +81 -0
  62. package/composables/useNotifications.js +22 -0
  63. package/composables/usePermissions.js +23 -0
  64. package/composables/useRealtime.js +123 -0
  65. package/composables/useRequestInterceptors.js +27 -0
  66. package/composables/useRoles.js +53 -0
  67. package/composables/useRutFormatter.js +39 -0
  68. package/composables/useTable.ts +94 -0
  69. package/composables/useTablePreferences.ts +33 -0
  70. package/composables/useTenant.js +27 -0
  71. package/composables/useTimeAgo.js +37 -0
  72. package/composables/useToast.js +69 -0
  73. package/composables/useUserRealtime.js +17 -0
  74. package/composables/useUsers.js +111 -0
  75. package/css/themes/autumn.css +401 -0
  76. package/css/themes/bubblegum.css +408 -0
  77. package/css/themes/cashmere.css +412 -0
  78. package/css/themes/harvest.css +416 -0
  79. package/css/themes/moon.css +140 -0
  80. package/css/themes/ocean.css +273 -0
  81. package/css/themes/olive.css +413 -0
  82. package/css/themes/retro.css +431 -0
  83. package/css/themes/theme.css +725 -0
  84. package/error.vue +78 -0
  85. package/middleware/01.detect-subdomain.global.ts +43 -0
  86. package/middleware/02.validate-tenant.global.ts +67 -0
  87. package/middleware/03.apps.global.ts +88 -0
  88. package/middleware/auth.ts +9 -0
  89. package/middleware/guest.ts +9 -0
  90. package/nuxt.config.ts +42 -0
  91. package/package.json +60 -0
  92. package/pages/tenant-error.vue +50 -0
  93. package/plugins/api-auth.ts +12 -0
  94. package/plugins/api-tenant.client.ts +21 -0
  95. package/plugins/appearance.ts +8 -0
  96. package/plugins/auth-init.ts +34 -0
  97. package/plugins/dark-state.client.ts +29 -0
  98. package/plugins/dockedPreviewsSync.client.js +17 -0
  99. package/plugins/preline.client.ts +68 -0
  100. package/plugins/theme.client.ts +7 -0
  101. package/plugins/vue-query.ts +29 -0
  102. package/public/init-theme.js +15 -0
  103. package/spark.css +721 -0
  104. package/stores/auth.js +130 -0
  105. package/stores/dockedPreviews.js +34 -0
  106. package/stores/notifications.js +24 -0
  107. package/stores/tenant.js +54 -0
  108. package/stores/toast.js +129 -0
@@ -0,0 +1,255 @@
1
+ <script setup>
2
+ import { ref, watch, onMounted, onBeforeUnmount, computed } from 'vue'
3
+ import { IconCalendar, IconX } from '@tabler/icons-vue'
4
+
5
+ const props = defineProps({
6
+ modelValue: {
7
+ type: [String, Date, Array],
8
+ default: null
9
+ },
10
+ mode: {
11
+ type: String,
12
+ default: 'date', // 'date' | 'datetime' | 'time' | 'range' | 'range-time'
13
+ },
14
+ placeholder: {
15
+ type: String,
16
+ default: 'Seleccionar fecha'
17
+ },
18
+ minDate: {
19
+ type: [String, Date],
20
+ default: null
21
+ },
22
+ maxDate: {
23
+ type: [String, Date],
24
+ default: null
25
+ },
26
+ disabled: {
27
+ type: Boolean,
28
+ default: false
29
+ },
30
+ clearable: {
31
+ type: Boolean,
32
+ default: true
33
+ }
34
+ })
35
+
36
+ const emit = defineEmits(['update:modelValue', 'change'])
37
+
38
+ const inputEl = ref(null)
39
+ const calendarInstance = ref(null)
40
+ const internalValue = ref('')
41
+
42
+ const isRange = computed(() => props.mode.includes('range'))
43
+ const hasTime = computed(() => props.mode.includes('time'))
44
+
45
+ const formatDateToDDMMYYYY = (isoString) => {
46
+ if (!isoString || typeof isoString !== 'string') return isoString
47
+
48
+ // isoString comes as YYYY-MM-DD or YYYY-MM-DD HH:mm
49
+ const parts = isoString.split(' ')
50
+ const datePart = parts[0].split('-')
51
+
52
+ if (datePart.length === 3) {
53
+ const formattedDate = `${datePart[2]}-${datePart[1]}-${datePart[0]}`
54
+ if (parts[1]) {
55
+ return `${formattedDate} ${parts[1]}`
56
+ }
57
+ return formattedDate
58
+ }
59
+ return isoString
60
+ }
61
+
62
+ const formatForDisplay = (val) => {
63
+ if (!val) return ''
64
+
65
+ // If it's a range, val is an array of 2 dates (YYYY-MM-DD or YYYY-MM-DD HH:mm)
66
+ if (Array.isArray(val) && val.length === 2) {
67
+ if (hasTime.value) {
68
+ return `${formatDateToDDMMYYYY(val[0])} - ${formatDateToDDMMYYYY(val[1])}`
69
+ }
70
+ return `${formatDateToDDMMYYYY(val[0].split(' ')[0])} - ${formatDateToDDMMYYYY(val[1].split(' ')[0])}`
71
+ }
72
+
73
+ // Single date/datetime string
74
+ if (typeof val === 'string') {
75
+ return formatDateToDDMMYYYY(val)
76
+ }
77
+
78
+ return val.toString()
79
+ }
80
+
81
+ // Convert prop modelValue to internal text representation
82
+ watch(() => props.modelValue, (newVal) => {
83
+ internalValue.value = formatForDisplay(newVal)
84
+ }, { immediate: true })
85
+
86
+
87
+ onMounted(async () => {
88
+ // Dynamic import to avoid SSR issues
89
+ try {
90
+ const { Calendar: VanillaCalendar } = await import('vanilla-calendar-pro')
91
+ await import('vanilla-calendar-pro/styles/index.css')
92
+
93
+ // Prepare config options based on props.mode
94
+ let type = 'default'
95
+ let time = false
96
+ let selectionDatesMode = 'single'
97
+
98
+ if (props.mode === 'datetime') {
99
+ time = true
100
+ } else if (props.mode === 'time') {
101
+ type = 'time'
102
+ } else if (props.mode === 'range') {
103
+ selectionDatesMode = 'multiple-ranged'
104
+ } else if (props.mode === 'range-time') {
105
+ selectionDatesMode = 'multiple-ranged'
106
+ time = true
107
+ }
108
+
109
+ // Set initial theme based on HTML class
110
+ const isDarkGlobal = useState('isDark')
111
+
112
+ let options = {
113
+ inputMode: true,
114
+ locale: 'es',
115
+ selectedTheme: isDarkGlobal.value ? 'dark' : 'light',
116
+ selectionDatesMode: selectionDatesMode,
117
+ selectionTimeMode: time ? 24 : false,
118
+ type: type,
119
+ onChangeToInput: (self, e) => {
120
+ let dates = self.context.selectedDates || []
121
+ let timeStr = self.context.selectedTime || ''
122
+
123
+ if (dates[0]) {
124
+ let res = dates[0]
125
+ if (dates[1]) res = [dates[0], dates[1]]
126
+
127
+ if (timeStr) {
128
+ if (dates[1]) {
129
+ res = [`${dates[0]} ${timeStr}`, `${dates[1]} ${timeStr}`]
130
+ } else {
131
+ res = `${dates[0]} ${timeStr}`
132
+ }
133
+ }
134
+ emit('update:modelValue', res)
135
+ emit('change', res)
136
+
137
+ if (!isRange.value && !hasTime.value) {
138
+ self.hide()
139
+ }
140
+ } else if (timeStr && type === 'time') {
141
+ emit('update:modelValue', timeStr)
142
+ emit('change', timeStr)
143
+ } else {
144
+ emit('update:modelValue', null)
145
+ emit('change', null)
146
+ }
147
+ }
148
+ }
149
+
150
+ if (props.mode === 'time') {
151
+ options.selectionDatesMode = false
152
+ options.type = 'time'
153
+ }
154
+
155
+ if (inputEl.value) {
156
+ calendarInstance.value = new VanillaCalendar(inputEl.value, options)
157
+ calendarInstance.value.init()
158
+
159
+ watch(() => isDarkGlobal.value, (newDark) => {
160
+ if (calendarInstance.value) {
161
+ calendarInstance.value.set({ selectedTheme: newDark ? 'dark' : 'light' })
162
+ }
163
+ })
164
+ }
165
+ } catch (e) {
166
+ // Vanilla Calendar Pro load failed — silently continue
167
+ }
168
+ })
169
+
170
+ onBeforeUnmount(() => {
171
+ // Teardown
172
+ })
173
+
174
+ const clear = () => {
175
+ emit('update:modelValue', null)
176
+ emit('change', null)
177
+ }
178
+ </script>
179
+
180
+ <template>
181
+ <div class="relative w-full">
182
+ <div class="relative group">
183
+ <div class="absolute inset-y-0 start-0 flex items-center pointer-events-none z-20 ps-3.5">
184
+ <IconCalendar class="size-4 text-gray-400 group-focus-within:text-blue-500 transition-colors" />
185
+ </div>
186
+
187
+ <input
188
+ ref="inputEl"
189
+ type="text"
190
+ :value="internalValue"
191
+ :disabled="disabled"
192
+ :placeholder="placeholder"
193
+ class="w-full py-2 ps-10 pe-10 border border-gray-300 rounded-lg text-sm dark:bg-card dark:border-card-line dark:text-white disabled:opacity-50 disabled:pointer-events-none cursor-pointer focus:border-blue-500 focus:ring-blue-500/20 outline-none transition-all block"
194
+ readonly
195
+ />
196
+
197
+ <!-- Clear button -->
198
+ <button
199
+ v-if="clearable && internalValue && !disabled"
200
+ @click.stop="clear"
201
+ type="button"
202
+ class="absolute inset-y-0 end-0 flex items-center z-20 pe-3 text-gray-400 hover:text-red-500 transition-colors focus:outline-none"
203
+ >
204
+ <IconX class="size-4" />
205
+ </button>
206
+ </div>
207
+ </div>
208
+ </template>
209
+
210
+ <style>
211
+ /*
212
+ Vanilla Calendar v3 compiles Tailwind classes directly into its themes.
213
+ We override the selected date and hover backgrounds for Light and Dark modes.
214
+ */
215
+
216
+ /* LIGHT MODE OVERRIDES */
217
+ [data-vc-theme=light] .vc-months__month[data-vc-months-month-selected],
218
+ [data-vc-theme=light] .vc-years__year[data-vc-years-year-selected],
219
+ [data-vc-theme=light] .vc-date[data-vc-date-selected=middle][data-vc-date-selected] .vc-date__btn,
220
+ [data-vc-theme=light] .vc-date[data-vc-date-selected] .vc-date__btn {
221
+ background-color: #2563eb !important; /* blue-600 */
222
+ color: #ffffff !important;
223
+ }
224
+
225
+ [data-vc-theme=light] .vc-months__month[data-vc-months-month-selected]:hover,
226
+ [data-vc-theme=light] .vc-years__year[data-vc-years-year-selected]:hover,
227
+ [data-vc-theme=light] .vc-date[data-vc-date-selected=middle][data-vc-date-selected] .vc-date__btn:hover,
228
+ [data-vc-theme=light] .vc-date[data-vc-date-selected] .vc-date__btn:hover {
229
+ background-color: #1d4ed8 !important; /* blue-700 */
230
+ }
231
+
232
+ [data-vc-theme=light] .vc-date[data-vc-date-today] .vc-date__btn {
233
+ color: #2563eb !important;
234
+ }
235
+
236
+ /* DARK MODE OVERRIDES */
237
+ [data-vc-theme=dark] .vc-months__month[data-vc-months-month-selected],
238
+ [data-vc-theme=dark] .vc-years__year[data-vc-years-year-selected],
239
+ [data-vc-theme=dark] .vc-date[data-vc-date-selected=middle][data-vc-date-selected] .vc-date__btn,
240
+ [data-vc-theme=dark] .vc-date[data-vc-date-selected] .vc-date__btn {
241
+ background-color: #3b82f6 !important; /* blue-500 */
242
+ color: #ffffff !important;
243
+ }
244
+
245
+ [data-vc-theme=dark] .vc-months__month[data-vc-months-month-selected]:hover,
246
+ [data-vc-theme=dark] .vc-years__year[data-vc-years-year-selected]:hover,
247
+ [data-vc-theme=dark] .vc-date[data-vc-date-selected=middle][data-vc-date-selected] .vc-date__btn:hover,
248
+ [data-vc-theme=dark] .vc-date[data-vc-date-selected] .vc-date__btn:hover {
249
+ background-color: #2563eb !important; /* blue-600 */
250
+ }
251
+
252
+ [data-vc-theme=dark] .vc-date[data-vc-date-today] .vc-date__btn {
253
+ color: #3b82f6 !important;
254
+ }
255
+ </style>
@@ -0,0 +1,75 @@
1
+ <script setup lang="ts">
2
+ import { ref, computed } from 'vue'
3
+ import { IconEye, IconEyeOff } from '@tabler/icons-vue'
4
+
5
+ const props = defineProps<{
6
+ type?: 'text' | 'email' | 'password' | 'number' | 'tel' | 'url' | 'search'
7
+ placeholder?: string
8
+ disabled?: boolean
9
+ error?: string | null
10
+ label?: string
11
+ hint?: string
12
+ iconLeft?: object | Function | null
13
+ autocomplete?: string
14
+ size?: 'sm' | 'md'
15
+ }>()
16
+
17
+ const modelValue = defineModel<string | number | null>({ default: '' })
18
+
19
+ const showPassword = ref(false)
20
+
21
+ const inputType = computed(() => {
22
+ if (props.type === 'password') return showPassword.value ? 'text' : 'password'
23
+ return props.type ?? 'text'
24
+ })
25
+
26
+ const baseClasses = computed(() =>
27
+ `${props.size === 'sm' ? 'py-1.5' : 'py-2'} px-3 block w-full rounded-lg text-sm text-slate-800 border border-card-line focus:ring-0 focus:border-gray-400 focus:outline-none disabled:opacity-50 dark:bg-transparent dark:text-muted-foreground-1 transition-colors placeholder:text-muted-foreground dark:placeholder:text-muted-foreground`
28
+ )
29
+ </script>
30
+
31
+ <template>
32
+ <div class="w-full">
33
+ <label v-if="label" class="block text-sm font-medium text-foreground mb-1.5">
34
+ {{ label }}
35
+ </label>
36
+
37
+ <div class="relative">
38
+ <!-- Ícono izquierdo -->
39
+ <div v-if="iconLeft" class="absolute inset-y-0 start-0 flex items-center ps-3 pointer-events-none text-muted-foreground">
40
+ <component :is="iconLeft" class="size-4" />
41
+ </div>
42
+
43
+ <input
44
+ v-model="modelValue"
45
+ :type="inputType"
46
+ :placeholder="placeholder"
47
+ :disabled="disabled"
48
+ :autocomplete="autocomplete"
49
+ :class="[
50
+ baseClasses,
51
+ iconLeft ? 'ps-9' : '',
52
+ type === 'password' ? 'pe-10' : '',
53
+ error ? '!border-red-400 dark:!border-red-500' : '',
54
+ ]"
55
+ />
56
+
57
+ <!-- Toggle contraseña -->
58
+ <button
59
+ v-if="type === 'password'"
60
+ type="button"
61
+ tabindex="-1"
62
+ class="absolute inset-y-0 end-0 flex items-center px-3 text-muted-foreground hover:text-muted-foreground-1 transition-colors"
63
+ @click="showPassword = !showPassword"
64
+ >
65
+ <component :is="showPassword ? IconEyeOff : IconEye" class="size-4" />
66
+ </button>
67
+ </div>
68
+
69
+ <!-- Error -->
70
+ <p v-if="error" class="text-xs text-red-500 dark:text-red-400 mt-1">{{ error }}</p>
71
+
72
+ <!-- Hint -->
73
+ <p v-else-if="hint" class="text-xs text-muted-foreground mt-1">{{ hint }}</p>
74
+ </div>
75
+ </template>
@@ -0,0 +1,100 @@
1
+ <script setup lang="ts">
2
+ const props = defineProps<{
3
+ options: { value: string | number; label: string }[]
4
+ modelValue?: string | number | null
5
+ label?: string
6
+ placeholder?: string
7
+ hint?: string
8
+ error?: string
9
+ disabled?: boolean
10
+ }>()
11
+
12
+ const emit = defineEmits<{
13
+ 'update:modelValue': [value: string | number | null]
14
+ change: [value: string | number | null]
15
+ }>()
16
+
17
+ const selectRef = ref<HTMLSelectElement | null>(null)
18
+
19
+ const reinitHsSelect = async () => {
20
+ await nextTick()
21
+ const el = selectRef.value
22
+ if (!el) return
23
+
24
+ const instance = (window as any).HSSelect?.getInstance?.(el)
25
+ if (instance?.destroy) instance.destroy()
26
+
27
+ new (window as any).HSSelect(el)
28
+ }
29
+
30
+ // Re-initialize when options change (async load)
31
+ watch(() => props.options, async () => {
32
+ await reinitHsSelect()
33
+ }, { deep: true })
34
+
35
+ onMounted(() => {
36
+ reinitHsSelect()
37
+ })
38
+
39
+ const handleChange = (e: Event) => {
40
+ const val = (e.target as HTMLSelectElement).value
41
+ emit('update:modelValue', val || null)
42
+ emit('change', val || null)
43
+ }
44
+ </script>
45
+
46
+ <template>
47
+ <div class="space-y-1.5">
48
+ <!-- Label -->
49
+ <label v-if="label" class="block text-sm font-medium text-foreground">
50
+ {{ label }}
51
+ </label>
52
+
53
+ <!-- Select (HSSelect) -->
54
+ <ClientOnly>
55
+ <template #fallback>
56
+ <div class="h-[38px] bg-surface animate-pulse rounded-lg" />
57
+ </template>
58
+
59
+ <div :class="['relative', error ? 'select-error' : '']">
60
+ <select
61
+ ref="selectRef"
62
+ class="hs-select w-full"
63
+ :value="modelValue ?? ''"
64
+ :disabled="disabled"
65
+ @change="handleChange"
66
+ data-hs-select='{
67
+ "placeholder": "Seleccionar...",
68
+ "toggleTag": "<button type=\"button\" aria-expanded=\"false\"></button>",
69
+ "toggleClasses": "hs-select-disabled:pointer-events-none hs-select-disabled:opacity-50 relative py-2 ps-4 pe-9 flex gap-x-2 text-nowrap w-full cursor-pointer bg-card border border-card-line rounded-lg text-start text-sm focus:outline-hidden focus:ring-2 focus:ring-blue-500 dark:text-muted-foreground-1 dark:focus:outline-hidden dark:focus:ring-1 dark:focus:ring-blue-600",
70
+ "dropdownClasses": "mt-1 z-50 w-full max-h-72 p-1 space-y-0.5 bg-dropdown border border-dropdown-line rounded-lg overflow-hidden overflow-y-auto shadow-lg",
71
+ "optionClasses": "py-2 px-4 w-full text-sm text-foreground cursor-pointer hover:bg-muted-hover rounded-lg focus:outline-hidden focus:bg-muted-hover dark:bg-card",
72
+ "optionTemplate": "<div class=\"flex justify-between items-center w-full\"><span data-title></span><span class=\"hidden hs-selected:block\"><svg class=\"shrink-0 size-3.5 text-blue-600 dark:text-blue-500\" 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\"><polyline points=\"20 6 9 17 4 12\"/></svg></span></div>"
73
+ }'
74
+ >
75
+ <option value="">{{ placeholder ?? 'Seleccionar...' }}</option>
76
+ <option
77
+ v-for="option in options"
78
+ :key="option.value"
79
+ :value="option.value"
80
+ >
81
+ {{ option.label }}
82
+ </option>
83
+ </select>
84
+ </div>
85
+ </ClientOnly>
86
+
87
+ <!-- Error -->
88
+ <p v-if="error" class="text-xs text-red-500 dark:text-red-400">{{ error }}</p>
89
+
90
+ <!-- Hint -->
91
+ <p v-else-if="hint" class="text-xs text-muted-foreground">{{ hint }}</p>
92
+ </div>
93
+ </template>
94
+
95
+ <style scoped>
96
+ /* Apply error border to the HSSelect toggle button when in error state */
97
+ .select-error :deep(button[aria-expanded]) {
98
+ border-color: rgb(248 113 113) !important; /* red-400 */
99
+ }
100
+ </style>