@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.
- package/.github/workflows/auto-publish.yml +64 -0
- package/.github/workflows/release.yml +59 -0
- package/README.md +60 -0
- package/app.config.ts +70 -0
- package/components/Admin/Base.vue +144 -0
- package/components/Admin/Header.vue +32 -0
- package/components/Admin/Page.vue +65 -0
- package/components/Admin/PageHeader.vue +31 -0
- package/components/App/Button.vue +59 -0
- package/components/App/DevEnvironmentBar.vue +43 -0
- package/components/App/Dropdown.vue +286 -0
- package/components/App/EmptyState.vue +433 -0
- package/components/App/LoadingState.vue +40 -0
- package/components/App/PageLoadingSpinner.vue +118 -0
- package/components/App/PreviewDock.vue +64 -0
- package/components/App/SwitchColorTheme.vue +51 -0
- package/components/App/Tag.vue +193 -0
- package/components/DataTable.vue +713 -0
- package/components/Forms/DatePicker.vue +255 -0
- package/components/Forms/Input.vue +75 -0
- package/components/Forms/Select.vue +100 -0
- package/components/Forms/SelectServer.vue +726 -0
- package/components/Layout/Admin.vue +32 -0
- package/components/Layout/Auth.vue +29 -0
- package/components/Layout/SidebarWithAppColumn.vue +388 -0
- package/components/Layout/TopBar.vue +113 -0
- package/components/MobileBlocker.vue +85 -0
- package/components/MobileLoginPicker.vue +83 -0
- package/components/Modal/Base.vue +29 -0
- package/components/Modal/DeleteConfirm.vue +48 -0
- package/components/Modal.vue +103 -0
- package/components/Nav/Tabs.vue +55 -0
- package/components/PermissionsTree.vue +272 -0
- package/components/Table/Database.vue +183 -0
- package/components/Table/DownloadDropdown.vue +111 -0
- package/components/Table/Enterprise.vue +540 -0
- package/components/Table/FilterDropdown.vue +226 -0
- package/components/Table/Grid.vue +62 -0
- package/components/Table/Kanban.vue +188 -0
- package/components/Table/List.vue +128 -0
- package/components/Table/PreviewTimeline.vue +118 -0
- package/components/Table/Standard.vue +1217 -0
- package/components/Table/index.vue +974 -0
- package/components/TableExportable.vue +172 -0
- package/components/TableFilter.vue +93 -0
- package/components/Toast/Alert.vue +113 -0
- package/components/Toast/Container.vue +34 -0
- package/components/Toast/Notification.vue +45 -0
- package/components/Toast/Process.vue +88 -0
- package/composables/useApi.js +95 -0
- package/composables/useApp.ts +46 -0
- package/composables/useAuth.js +82 -0
- package/composables/useContext.js +44 -0
- package/composables/useDate.js +241 -0
- package/composables/useDevice.js +21 -0
- package/composables/useDockedPreviews.js +56 -0
- package/composables/useDownload.js +87 -0
- package/composables/useEntity.js +82 -0
- package/composables/useForm.js +119 -0
- package/composables/useInnertiaMode.ts +25 -0
- package/composables/useMobileGuard.ts +81 -0
- package/composables/useNotifications.js +22 -0
- package/composables/usePermissions.js +23 -0
- package/composables/useRealtime.js +123 -0
- package/composables/useRequestInterceptors.js +27 -0
- package/composables/useRoles.js +53 -0
- package/composables/useRutFormatter.js +39 -0
- package/composables/useTable.ts +94 -0
- package/composables/useTablePreferences.ts +33 -0
- package/composables/useTenant.js +27 -0
- package/composables/useTimeAgo.js +37 -0
- package/composables/useToast.js +69 -0
- package/composables/useUserRealtime.js +17 -0
- package/composables/useUsers.js +111 -0
- package/css/themes/autumn.css +401 -0
- package/css/themes/bubblegum.css +408 -0
- package/css/themes/cashmere.css +412 -0
- package/css/themes/harvest.css +416 -0
- package/css/themes/moon.css +140 -0
- package/css/themes/ocean.css +273 -0
- package/css/themes/olive.css +413 -0
- package/css/themes/retro.css +431 -0
- package/css/themes/theme.css +725 -0
- package/error.vue +78 -0
- package/middleware/01.detect-subdomain.global.ts +43 -0
- package/middleware/02.validate-tenant.global.ts +67 -0
- package/middleware/03.apps.global.ts +88 -0
- package/middleware/auth.ts +9 -0
- package/middleware/guest.ts +9 -0
- package/nuxt.config.ts +42 -0
- package/package.json +60 -0
- package/pages/tenant-error.vue +50 -0
- package/plugins/api-auth.ts +12 -0
- package/plugins/api-tenant.client.ts +21 -0
- package/plugins/appearance.ts +8 -0
- package/plugins/auth-init.ts +34 -0
- package/plugins/dark-state.client.ts +29 -0
- package/plugins/dockedPreviewsSync.client.js +17 -0
- package/plugins/preline.client.ts +68 -0
- package/plugins/theme.client.ts +7 -0
- package/plugins/vue-query.ts +29 -0
- package/public/init-theme.js +15 -0
- package/spark.css +721 -0
- package/stores/auth.js +130 -0
- package/stores/dockedPreviews.js +34 -0
- package/stores/notifications.js +24 -0
- package/stores/tenant.js +54 -0
- package/stores/toast.js +129 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { IconSearch, IconLoader2, IconCheck, IconX } from '@tabler/icons-vue'
|
|
3
|
+
|
|
4
|
+
// Dense database view with inline cell editing — click cell → input → blur → mutation
|
|
5
|
+
const props = defineProps({
|
|
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 },
|
|
9
|
+
params: { type: Object, default: () => ({}) },
|
|
10
|
+
updateMutation: { type: Function, default: null }, // (id, field, value) => Promise
|
|
11
|
+
cached: { type: Boolean, default: false },
|
|
12
|
+
searchPlaceholder: { type: String, default: 'Buscar...' },
|
|
13
|
+
showSearch: { type: Boolean, default: true },
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
const emit = defineEmits(['row-click', 'cell-save'])
|
|
17
|
+
|
|
18
|
+
const tableRef = ref(null)
|
|
19
|
+
|
|
20
|
+
// ─── Inline editing ───────────────────────────────────────────────────────────
|
|
21
|
+
const editingCell = ref(null) // { rowId, key }
|
|
22
|
+
const editingValue = ref('')
|
|
23
|
+
const savingCell = ref(null)
|
|
24
|
+
const cellError = ref(null)
|
|
25
|
+
|
|
26
|
+
const startEdit = (row, col) => {
|
|
27
|
+
if (!col.editable) return
|
|
28
|
+
editingCell.value = { rowId: row.id, key: col.key }
|
|
29
|
+
editingValue.value = row[col.key] ?? ''
|
|
30
|
+
cellError.value = null
|
|
31
|
+
nextTick(() => {
|
|
32
|
+
const input = document.querySelector(`[data-cell-input="${row.id}-${col.key}"]`)
|
|
33
|
+
input?.focus()
|
|
34
|
+
input?.select()
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const cancelEdit = () => {
|
|
39
|
+
editingCell.value = null
|
|
40
|
+
editingValue.value = ''
|
|
41
|
+
cellError.value = null
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const saveEdit = async (row, col) => {
|
|
45
|
+
if (!editingCell.value) return
|
|
46
|
+
const newValue = editingValue.value
|
|
47
|
+
const oldValue = row[col.key]
|
|
48
|
+
|
|
49
|
+
if (newValue === String(oldValue ?? '')) {
|
|
50
|
+
cancelEdit()
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
editingCell.value = null
|
|
55
|
+
savingCell.value = { rowId: row.id, key: col.key }
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
if (props.updateMutation) {
|
|
59
|
+
await props.updateMutation(row.id, col.key, newValue)
|
|
60
|
+
}
|
|
61
|
+
// Patch local row
|
|
62
|
+
row[col.key] = newValue
|
|
63
|
+
emit('cell-save', { id: row.id, field: col.key, value: newValue, oldValue })
|
|
64
|
+
tableRef.value?.reload()
|
|
65
|
+
} catch (e) {
|
|
66
|
+
cellError.value = { rowId: row.id, key: col.key, message: e.message }
|
|
67
|
+
} finally {
|
|
68
|
+
savingCell.value = null
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const isEditing = (rowId, key) => editingCell.value?.rowId === rowId && editingCell.value?.key === key
|
|
73
|
+
const isSaving = (rowId, key) => savingCell.value?.rowId === rowId && savingCell.value?.key === key
|
|
74
|
+
|
|
75
|
+
const search = ref('')
|
|
76
|
+
const mergedParams = computed(() => ({ ...props.params }))
|
|
77
|
+
|
|
78
|
+
const reload = () => tableRef.value?.reload()
|
|
79
|
+
defineExpose({ reload })
|
|
80
|
+
</script>
|
|
81
|
+
|
|
82
|
+
<template>
|
|
83
|
+
<div class="flex flex-col gap-3">
|
|
84
|
+
<div v-if="showSearch" class="relative max-w-sm">
|
|
85
|
+
<div class="absolute inset-y-0 start-0 flex items-center ps-3 pointer-events-none">
|
|
86
|
+
<IconSearch class="size-4 text-slate-400" stroke="1.5" />
|
|
87
|
+
</div>
|
|
88
|
+
<input
|
|
89
|
+
v-model="search"
|
|
90
|
+
type="search"
|
|
91
|
+
:placeholder="searchPlaceholder"
|
|
92
|
+
class="block w-full rounded-lg border border-card-line bg-card text-foreground py-1.5 ps-9 pe-4 text-sm focus:outline-none focus:ring-1 focus:ring-indigo-500"
|
|
93
|
+
/>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<!-- Dense table wrapper — override Table's default padding with compact styles -->
|
|
97
|
+
<div class="overflow-x-auto border border-card-line rounded-xl">
|
|
98
|
+
<Table
|
|
99
|
+
ref="tableRef"
|
|
100
|
+
:endpoint="endpoint"
|
|
101
|
+
:columns="columns"
|
|
102
|
+
:name="name"
|
|
103
|
+
:params="mergedParams"
|
|
104
|
+
:search="search"
|
|
105
|
+
:cached="cached"
|
|
106
|
+
:show-reload-button="false"
|
|
107
|
+
class="[&_td]:py-1 [&_td]:px-2 [&_th]:py-1.5 [&_th]:px-2 [&_td]:text-xs [&_th]:text-xs"
|
|
108
|
+
@row-click="emit('row-click', $event)"
|
|
109
|
+
>
|
|
110
|
+
<!-- Override each cell slot to support inline editing -->
|
|
111
|
+
<template
|
|
112
|
+
v-for="col in columns"
|
|
113
|
+
:key="col.key"
|
|
114
|
+
#[`cell-${col.key}`]="{ row }"
|
|
115
|
+
>
|
|
116
|
+
<!-- Editing state -->
|
|
117
|
+
<div v-if="isEditing(row.id, col.key)" class="flex items-center gap-1 -mx-1">
|
|
118
|
+
<select
|
|
119
|
+
v-if="col.type === 'select' && col.options"
|
|
120
|
+
v-model="editingValue"
|
|
121
|
+
:data-cell-input="`${row.id}-${col.key}`"
|
|
122
|
+
@blur="saveEdit(row, col)"
|
|
123
|
+
@keydown.enter="saveEdit(row, col)"
|
|
124
|
+
@keydown.escape="cancelEdit"
|
|
125
|
+
class="flex-1 min-w-0 rounded border border-indigo-400 bg-card text-foreground py-0.5 px-1.5 text-xs focus:outline-none focus:ring-1 focus:ring-indigo-500"
|
|
126
|
+
>
|
|
127
|
+
<option v-for="opt in col.options" :key="opt.value" :value="opt.value">{{ opt.label }}</option>
|
|
128
|
+
</select>
|
|
129
|
+
<input
|
|
130
|
+
v-else
|
|
131
|
+
v-model="editingValue"
|
|
132
|
+
:type="col.type === 'number' ? 'number' : 'text'"
|
|
133
|
+
:data-cell-input="`${row.id}-${col.key}`"
|
|
134
|
+
@blur="saveEdit(row, col)"
|
|
135
|
+
@keydown.enter="saveEdit(row, col)"
|
|
136
|
+
@keydown.escape="cancelEdit"
|
|
137
|
+
class="flex-1 min-w-0 rounded border border-indigo-400 bg-card text-foreground py-0.5 px-1.5 text-xs focus:outline-none focus:ring-1 focus:ring-indigo-500"
|
|
138
|
+
/>
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<!-- Saving state -->
|
|
142
|
+
<div v-else-if="isSaving(row.id, col.key)" class="flex items-center gap-1 text-slate-400">
|
|
143
|
+
<IconLoader2 class="size-3 animate-spin shrink-0" stroke="1.5" />
|
|
144
|
+
<span class="truncate">{{ row[col.key] }}</span>
|
|
145
|
+
</div>
|
|
146
|
+
|
|
147
|
+
<!-- Error state -->
|
|
148
|
+
<div
|
|
149
|
+
v-else-if="cellError?.rowId === row.id && cellError?.key === col.key"
|
|
150
|
+
:title="cellError.message"
|
|
151
|
+
class="flex items-center gap-1 text-red-500 cursor-pointer"
|
|
152
|
+
@click="startEdit(row, col)"
|
|
153
|
+
>
|
|
154
|
+
<IconX class="size-3 shrink-0" stroke="2" />
|
|
155
|
+
<span class="truncate text-xs">{{ row[col.key] }}</span>
|
|
156
|
+
</div>
|
|
157
|
+
|
|
158
|
+
<!-- View state -->
|
|
159
|
+
<div
|
|
160
|
+
v-else
|
|
161
|
+
:class="[
|
|
162
|
+
'truncate',
|
|
163
|
+
col.editable ? 'cursor-text hover:bg-indigo-50 dark:hover:bg-indigo-900/20 rounded px-1 -mx-1 group relative' : ''
|
|
164
|
+
]"
|
|
165
|
+
@click="col.editable ? startEdit(row, col) : emit('row-click', row)"
|
|
166
|
+
>
|
|
167
|
+
<slot :name="`cell-${col.key}`" :row="row">
|
|
168
|
+
{{ row[col.key] }}
|
|
169
|
+
</slot>
|
|
170
|
+
<span
|
|
171
|
+
v-if="col.editable"
|
|
172
|
+
class="absolute inset-y-0 right-0 flex items-center opacity-0 group-hover:opacity-100 pr-1"
|
|
173
|
+
>
|
|
174
|
+
<svg class="size-3 text-indigo-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
175
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
|
|
176
|
+
</svg>
|
|
177
|
+
</span>
|
|
178
|
+
</div>
|
|
179
|
+
</template>
|
|
180
|
+
</Table>
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
</template>
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import {
|
|
3
|
+
IconFileTypeXls,
|
|
4
|
+
IconCodeDots,
|
|
5
|
+
IconFileTypePdf,
|
|
6
|
+
IconFileTypeCsv,
|
|
7
|
+
IconDownload,
|
|
8
|
+
} from "@tabler/icons-vue";
|
|
9
|
+
|
|
10
|
+
const props = defineProps({
|
|
11
|
+
tableRef: { type: Object, required: true },
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const exportAllPages = ref(true);
|
|
15
|
+
const exportFilteredRows = ref(true);
|
|
16
|
+
|
|
17
|
+
const exportTable = (format) => {
|
|
18
|
+
if (props.tableRef) {
|
|
19
|
+
props.tableRef.exportTable(
|
|
20
|
+
format,
|
|
21
|
+
exportAllPages.value,
|
|
22
|
+
exportFilteredRows.value
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
</script>
|
|
27
|
+
<template>
|
|
28
|
+
<div
|
|
29
|
+
class="hs-dropdown [--auto-close:inside] [--placement:bottom-right] relative inline-block"
|
|
30
|
+
>
|
|
31
|
+
<button
|
|
32
|
+
id="hs-as-table-table-export-dropdown"
|
|
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-card-line bg-card text-foreground shadow-2xs hover:bg-muted-hover disabled:opacity-50 disabled:pointer-events-none focus:outline-hidden focus:bg-muted-hover"
|
|
35
|
+
aria-haspopup="menu"
|
|
36
|
+
aria-expanded="false"
|
|
37
|
+
aria-label="Dropdown"
|
|
38
|
+
>
|
|
39
|
+
<IconDownload class="shrink-0 size-4" stroke="1.5" />
|
|
40
|
+
Exportar
|
|
41
|
+
</button>
|
|
42
|
+
<div
|
|
43
|
+
class="hs-dropdown-menu transition-[opacity,margin] duration hs-dropdown-open:opacity-100 opacity-0 hidden divide-y divide-card-line min-w-48 z-10 bg-dropdown shadow-md rounded-lg p-2 mt-2 dark:border dark:border-dropdown-line border-t border-card-line"
|
|
44
|
+
role="menu"
|
|
45
|
+
aria-orientation="vertical"
|
|
46
|
+
aria-labelledby="hs-as-table-table-export-dropdown"
|
|
47
|
+
>
|
|
48
|
+
<div class="py-2 first:pt-0 last:pb-0">
|
|
49
|
+
<a
|
|
50
|
+
class="flex items-center gap-x-3 py-2 px-3 rounded-lg text-sm text-foreground hover:bg-muted-hover focus:outline-hidden focus:bg-muted-hover"
|
|
51
|
+
href="#"
|
|
52
|
+
@click="exportTable('xlsx')"
|
|
53
|
+
>
|
|
54
|
+
<IconFileTypeXls class="shrink-0 size-5" stroke="1.5" />
|
|
55
|
+
Excel
|
|
56
|
+
</a>
|
|
57
|
+
<a
|
|
58
|
+
class="flex items-center gap-x-3 py-2 px-3 rounded-lg text-sm text-foreground hover:bg-muted-hover focus:outline-hidden focus:bg-muted-hover"
|
|
59
|
+
href="#"
|
|
60
|
+
@click="exportTable('csv')"
|
|
61
|
+
>
|
|
62
|
+
<IconFileTypeCsv class="shrink-0 size-5" stroke="1.5" />
|
|
63
|
+
CSV
|
|
64
|
+
</a>
|
|
65
|
+
<a
|
|
66
|
+
class="flex items-center gap-x-3 py-2 px-3 rounded-lg text-sm text-foreground hover:bg-muted-hover focus:outline-hidden focus:bg-muted-hover"
|
|
67
|
+
href="#"
|
|
68
|
+
@click="exportTable('pdf')"
|
|
69
|
+
>
|
|
70
|
+
<IconFileTypePdf class="shrink-0 size-5" stroke="1.5" />
|
|
71
|
+
PDF
|
|
72
|
+
</a>
|
|
73
|
+
<a
|
|
74
|
+
class="flex items-center gap-x-3 py-2 px-3 rounded-lg text-sm text-foreground hover:bg-muted-hover focus:outline-hidden focus:bg-muted-hover"
|
|
75
|
+
href="#"
|
|
76
|
+
@click="exportTable('json')"
|
|
77
|
+
>
|
|
78
|
+
<IconCodeDots class="shrink-0 size-5" stroke="1.5" />
|
|
79
|
+
JSON
|
|
80
|
+
</a>
|
|
81
|
+
</div>
|
|
82
|
+
<!-- checkbox - todas las paginas -->
|
|
83
|
+
<div
|
|
84
|
+
class="flex flex-col gap-y-2 py-2 px-3"
|
|
85
|
+
data-hs-dropdown-ignore-click
|
|
86
|
+
>
|
|
87
|
+
<label
|
|
88
|
+
class="inline-flex items-center gap-2 text-sm text-foreground"
|
|
89
|
+
>
|
|
90
|
+
<input
|
|
91
|
+
type="checkbox"
|
|
92
|
+
v-model="exportAllPages"
|
|
93
|
+
class="shrink-0 border-card-line rounded-sm text-blue-600 focus:ring-blue-500 dark:bg-card"
|
|
94
|
+
/>
|
|
95
|
+
Todas las páginas
|
|
96
|
+
</label>
|
|
97
|
+
|
|
98
|
+
<label
|
|
99
|
+
class="inline-flex items-center gap-2 text-sm text-foreground"
|
|
100
|
+
>
|
|
101
|
+
<input
|
|
102
|
+
type="checkbox"
|
|
103
|
+
v-model="exportFilteredRows"
|
|
104
|
+
class="shrink-0 border-card-line rounded-sm text-blue-600 focus:ring-blue-500 dark:bg-card"
|
|
105
|
+
/>
|
|
106
|
+
Solo filas filtradas
|
|
107
|
+
</label>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
</template>
|