@ramathibodi/nuxt-commons 0.1.74 → 0.1.75

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 (96) hide show
  1. package/README.md +115 -115
  2. package/dist/module.json +1 -1
  3. package/dist/runtime/components/Alert.vue +58 -58
  4. package/dist/runtime/components/BarcodeReader.vue +130 -130
  5. package/dist/runtime/components/ExportCSV.vue +110 -110
  6. package/dist/runtime/components/FileBtn.vue +79 -79
  7. package/dist/runtime/components/ImportCSV.vue +151 -151
  8. package/dist/runtime/components/MrzReader.vue +168 -168
  9. package/dist/runtime/components/SplitterPanel.vue +67 -67
  10. package/dist/runtime/components/TabsGroup.vue +39 -39
  11. package/dist/runtime/components/TextBarcode.vue +66 -66
  12. package/dist/runtime/components/device/IdCardButton.vue +95 -95
  13. package/dist/runtime/components/device/IdCardWebSocket.vue +207 -207
  14. package/dist/runtime/components/device/Scanner.vue +350 -350
  15. package/dist/runtime/components/dialog/Confirm.vue +112 -112
  16. package/dist/runtime/components/dialog/Host.vue +88 -88
  17. package/dist/runtime/components/dialog/Index.vue +84 -84
  18. package/dist/runtime/components/dialog/Loading.vue +51 -51
  19. package/dist/runtime/components/dialog/default/Confirm.vue +112 -112
  20. package/dist/runtime/components/dialog/default/Loading.vue +60 -60
  21. package/dist/runtime/components/dialog/default/Notify.vue +82 -82
  22. package/dist/runtime/components/dialog/default/Printing.vue +46 -46
  23. package/dist/runtime/components/dialog/default/VerifyUser.vue +144 -144
  24. package/dist/runtime/components/document/Form.vue +50 -50
  25. package/dist/runtime/components/document/TemplateBuilder.vue +536 -536
  26. package/dist/runtime/components/form/ActionPad.vue +156 -156
  27. package/dist/runtime/components/form/Birthdate.vue +116 -116
  28. package/dist/runtime/components/form/CheckboxGroup.vue +99 -99
  29. package/dist/runtime/components/form/CodeEditor.vue +45 -45
  30. package/dist/runtime/components/form/Date.vue +270 -270
  31. package/dist/runtime/components/form/DateTime.vue +220 -220
  32. package/dist/runtime/components/form/Dialog.vue +178 -178
  33. package/dist/runtime/components/form/EditPad.vue +157 -157
  34. package/dist/runtime/components/form/File.vue +295 -295
  35. package/dist/runtime/components/form/Hidden.vue +44 -44
  36. package/dist/runtime/components/form/Iterator.vue +538 -538
  37. package/dist/runtime/components/form/Login.vue +143 -143
  38. package/dist/runtime/components/form/Pad.vue +399 -399
  39. package/dist/runtime/components/form/SignPad.vue +226 -226
  40. package/dist/runtime/components/form/System.vue +34 -34
  41. package/dist/runtime/components/form/Table.vue +391 -391
  42. package/dist/runtime/components/form/TableData.vue +236 -236
  43. package/dist/runtime/components/form/Time.vue +177 -177
  44. package/dist/runtime/components/form/images/Capture.vue +245 -245
  45. package/dist/runtime/components/form/images/Edit.vue +133 -133
  46. package/dist/runtime/components/form/images/Field.vue +331 -331
  47. package/dist/runtime/components/form/images/Pad.vue +54 -54
  48. package/dist/runtime/components/label/Date.vue +37 -37
  49. package/dist/runtime/components/label/DateAgo.vue +102 -102
  50. package/dist/runtime/components/label/DateCount.vue +152 -152
  51. package/dist/runtime/components/label/Field.vue +111 -111
  52. package/dist/runtime/components/label/FormatMoney.vue +37 -37
  53. package/dist/runtime/components/label/Mask.vue +46 -46
  54. package/dist/runtime/components/label/Object.vue +21 -21
  55. package/dist/runtime/components/master/Autocomplete.vue +89 -89
  56. package/dist/runtime/components/master/Combobox.vue +88 -88
  57. package/dist/runtime/components/master/RadioGroup.vue +90 -90
  58. package/dist/runtime/components/master/Select.vue +70 -70
  59. package/dist/runtime/components/master/label.vue +55 -55
  60. package/dist/runtime/components/model/Autocomplete.vue +91 -91
  61. package/dist/runtime/components/model/Combobox.vue +90 -90
  62. package/dist/runtime/components/model/Pad.vue +114 -114
  63. package/dist/runtime/components/model/Select.vue +78 -84
  64. package/dist/runtime/components/model/Table.vue +370 -370
  65. package/dist/runtime/components/model/iterator.vue +497 -497
  66. package/dist/runtime/components/model/label.vue +58 -58
  67. package/dist/runtime/components/pdf/Print.vue +75 -75
  68. package/dist/runtime/components/pdf/View.vue +146 -146
  69. package/dist/runtime/composables/dialog.d.ts +1 -1
  70. package/dist/runtime/composables/graphql.d.ts +1 -1
  71. package/dist/runtime/composables/graphqlModel.d.ts +9 -9
  72. package/dist/runtime/composables/graphqlModelItem.d.ts +7 -7
  73. package/dist/runtime/composables/graphqlModelOperation.d.ts +6 -6
  74. package/dist/runtime/composables/userPermission.d.ts +1 -1
  75. package/dist/runtime/labs/Calendar.vue +99 -99
  76. package/dist/runtime/labs/form/EditMobile.vue +152 -152
  77. package/dist/runtime/labs/form/TextFieldMask.vue +43 -43
  78. package/dist/runtime/plugins/clientConfig.d.ts +1 -1
  79. package/dist/runtime/plugins/default.d.ts +1 -1
  80. package/dist/runtime/plugins/dialogManager.d.ts +1 -1
  81. package/dist/runtime/plugins/permission.d.ts +1 -1
  82. package/dist/runtime/types/alert.d.ts +11 -11
  83. package/dist/runtime/types/clientConfig.d.ts +13 -13
  84. package/dist/runtime/types/dialogManager.d.ts +35 -35
  85. package/dist/runtime/types/formDialog.d.ts +5 -5
  86. package/dist/runtime/types/graphqlOperation.d.ts +23 -23
  87. package/dist/runtime/types/menu.d.ts +31 -31
  88. package/dist/runtime/types/modules.d.ts +7 -7
  89. package/dist/runtime/types/permission.d.ts +13 -13
  90. package/package.json +131 -131
  91. package/scripts/enrich-vue-docs-from-ai.mjs +197 -197
  92. package/scripts/generate-ai-summary.mjs +321 -321
  93. package/scripts/generate-composables-md.mjs +129 -129
  94. package/scripts/postInstall.cjs +70 -70
  95. package/templates/.codegen/codegen.ts +32 -32
  96. package/templates/.codegen/plugin-schema-object.js +161 -161
@@ -1,497 +1,497 @@
1
- <script lang="ts" setup>
2
- /**
3
- * ModelIterator connects model metadata to reusable selection, labeling, iterator, or table UI patterns.
4
- * This doc block is consumed by vue-docgen for generated API documentation.
5
- */
6
- import {computed, nextTick, ref, useAttrs, useSlots, watch} from 'vue'
7
- import {VDataIterator} from 'vuetify/components/VDataIterator'
8
- import {VDataTable} from "vuetify/components/VDataTable";
9
- import {omit} from 'lodash-es'
10
- import type {GraphqlModelProps} from '../../composables/graphqlModel'
11
- import {useGraphqlModel} from '../../composables/graphqlModel'
12
- import {useDisplay} from 'vuetify'
13
-
14
- defineOptions({
15
- inheritAttrs: false,
16
- })
17
-
18
- interface Props extends /* @vue-ignore */ InstanceType<typeof VDataIterator['$props']> {
19
- title: string // Toolbar title for this model-backed iterator/table block.
20
- noDataText?: string // Empty-state message shown when model query has no results.
21
- dialogFullscreen?: boolean // Opens create/edit dialog fullscreen by default.
22
- initialData?: Record<string, any> // Default values merged with `modelBy` before opening create dialog.
23
- toolbarColor?: string // Toolbar/action color theme token.
24
- importable?: boolean // Enables import action for creating many model rows at once.
25
- exportable?: boolean // Enables export action for current model result set.
26
- insertable?: boolean // Enables create action for new model rows.
27
- searchable?: boolean // Shows search input and updates GraphQL model search state.
28
- search?: string // External search value synced into internal search ref via watcher.
29
-
30
- viewSwitch?: boolean // Displays control for switching between card iterator and table presentations.
31
- viewSwitchMultiple?: boolean // Allows multiple selected view chips when switch UI supports it.
32
-
33
- cols?: string | number | boolean // Base columns used by iterator card grid.
34
- xxl?: string | number | boolean // Card grid columns at `xxl` breakpoint.
35
- xl?: string | number | boolean // Card grid columns at `xl` breakpoint.
36
- lg?: string | number | boolean // Card grid columns at `lg` breakpoint.
37
- md?: string | number | boolean // Card grid columns at `md` breakpoint.
38
- sm?: string | number | boolean // Card grid columns at `sm` breakpoint.
39
- itemsPerPage?: string | number // Items per page for iterator/table pagination (`all` supported).
40
-
41
- preferTable?: string | number | boolean // Auto view strategy: always table (`true`) or table after N items (number).
42
- preferTableXxl?: string | number | boolean // `xxl` override for `preferTable`.
43
- preferTableXl?: string | number | boolean // `xl` override for `preferTable`.
44
- preferTableLg?: string | number | boolean // `lg` override for `preferTable`.
45
- preferTableMd?: string | number | boolean // `md` override for `preferTable`.
46
- preferTableSm?: string | number | boolean // `sm` override for `preferTable`.
47
- }
48
-
49
- /**
50
- * Public props accepted by ModelIterator.
51
- * Document each prop field with intent, defaults, and side effects for clear generated docs.
52
- */
53
- const props = withDefaults(defineProps<Props & GraphqlModelProps>(), {
54
- noDataText: 'ไม่พบข้อมูล',
55
- dialogFullscreen: false,
56
- toolbarColor: 'primary',
57
- importable: false,
58
- exportable: false,
59
- insertable: true,
60
- searchable: true,
61
- modelKey: 'id',
62
- modelBy: undefined,
63
- fields: () => [],
64
-
65
- viewSwitch: false,
66
- viewSwitchMultiple:false,
67
-
68
- cols: 12,
69
- xxl: false,
70
- xl: false,
71
- lg: 2,
72
- md: 4,
73
- sm: 6,
74
- itemsPerPage: 12,
75
- })
76
-
77
- const attrs = useAttrs()
78
- const plainAttrs = computed(() => {
79
- const returnAttrs = omit(attrs, ['modelValue', 'onUpdate:modelValue'])
80
- if (props.headers) returnAttrs['headers'] = props.headers
81
- return returnAttrs
82
- })
83
- const slots = useSlots()
84
- const tableSlots = computed(() => {
85
- return omit(slots, ['item'])
86
- })
87
-
88
- const display = useDisplay()
89
- const isProgrammaticSet = ref(false)
90
- const userOverrodeView = ref(false)
91
-
92
- const viewType = ref<string[] | string>('iterator')
93
-
94
- function setViewTypeSafely(val: 'iterator' | 'table') {
95
- isProgrammaticSet.value = true
96
- if (Array.isArray(viewType.value)) {
97
- viewType.value = [val]
98
- } else {
99
- viewType.value = val
100
- }
101
- nextTick(() => { isProgrammaticSet.value = false })
102
- }
103
-
104
- watch(viewType, () => {
105
- if (!isProgrammaticSet.value) userOverrodeView.value = true
106
- })
107
-
108
- function parsePrefer(val: unknown): boolean | number {
109
- if (val === true) return true
110
- const n = Number(val)
111
- return Number.isFinite(n) && n > 0 ? n : false
112
- }
113
-
114
- const computedPreferTable = computed<boolean | number>(() => {
115
- const bp = display.name?.value
116
- if (bp === 'xxl' && props.preferTableXxl !== undefined) return parsePrefer(props.preferTableXxl)
117
- if (bp === 'xl' && props.preferTableXl !== undefined) return parsePrefer(props.preferTableXl)
118
- if (bp === 'lg' && props.preferTableLg !== undefined) return parsePrefer(props.preferTableLg)
119
- if (bp === 'md' && props.preferTableMd !== undefined) return parsePrefer(props.preferTableMd)
120
- if (bp === 'sm' && props.preferTableSm !== undefined) return parsePrefer(props.preferTableSm)
121
- return parsePrefer(props.preferTable)
122
- })
123
-
124
- const currentItem = ref<Record<string, any> | undefined>(undefined)
125
- const isDialogOpen = ref<boolean>(false)
126
-
127
- const { items, itemsLength,
128
- search, setSearch, currentOptions,
129
- canServerPageable, canServerSearch, canCreate, canUpdate, canDelete,
130
- createItem, importItems, updateItem, deleteItem,
131
- loadItems, reload,
132
- isLoading } = useGraphqlModel(props)
133
-
134
- function openDialog(item?: object) {
135
- currentItem.value = item
136
- nextTick(() => {
137
- isDialogOpen.value = true
138
- })
139
- }
140
-
141
- const itemsPerPageInternal = ref<string | number>()
142
- watch(() => props.itemsPerPage, (newValue) => {
143
- if (newValue.toString().toLowerCase() == 'all') itemsPerPageInternal.value = '-1'
144
- else if (newValue) itemsPerPageInternal.value = newValue
145
- }, { immediate: true })
146
-
147
- type SortItem = { key: string, order?: boolean | 'asc' | 'desc' }
148
- const sortBy = ref<SortItem[]>()
149
-
150
- const pageCount = computed(() => {
151
- if (!itemsPerPageInternal.value || itemsPerPageInternal.value == 'All' || itemsPerPageInternal.value == '-1' || Number(itemsPerPageInternal.value) <= 0) return 1
152
- else return Math.ceil(itemsLength.value / Number(itemsPerPageInternal.value))
153
- })
154
- const currentPage = ref<number>(1)
155
-
156
- watch([currentPage, itemsPerPageInternal, sortBy], () => {
157
- if (canServerPageable.value) {
158
- loadItems({
159
- page: currentPage.value,
160
- itemsPerPage: (!itemsPerPageInternal.value || itemsPerPageInternal.value == 'All' || Number(itemsPerPageInternal.value) <= 0) ? '-1' : itemsPerPageInternal.value,
161
- sortBy: sortBy.value,
162
- })
163
- }
164
- }, { immediate: true })
165
-
166
- watch(
167
- [itemsLength, computedPreferTable, () => display.name?.value],
168
- ([len, prefer]) => {
169
- if (userOverrodeView.value) return // respect explicit user choice forever (until remount)
170
-
171
- let target: 'iterator' | 'table' = 'iterator'
172
- if (prefer === true) {
173
- target = 'table'
174
- } else if (typeof prefer === 'number') {
175
- if (Number(len) >= prefer) target = 'table'
176
- }
177
-
178
- if (!viewType.value?.includes(target)) setViewTypeSafely(target)
179
- },
180
- { immediate: true }
181
- )
182
-
183
- const operation = ref({ openDialog, createItem, importItems, updateItem, deleteItem, reload, setSearch, canServerPageable, canServerSearch, canCreate, canUpdate, canDelete })
184
-
185
- const computedInitialData = computed(() => {
186
- return Object.assign({}, props.initialData, props.modelBy)
187
- })
188
-
189
- const computedSkeletonPerPage = computed(() => {
190
- if (!itemsPerPageInternal.value || itemsPerPageInternal.value == 'All' || Number(itemsPerPageInternal.value) <= 0) return 1
191
- else return Number(itemsPerPageInternal.value)
192
- })
193
-
194
- watch(()=>props.search,()=>{
195
- search.value = props.search
196
- },{immediate:true})
197
-
198
- defineExpose({ reload,operation })
199
- </script>
200
-
201
- <template>
202
- <v-card>
203
- <v-data-iterator
204
- v-bind="plainAttrs"
205
- v-model:items-per-page="itemsPerPageInternal"
206
- v-model:sort-by="sortBy"
207
- :items="items"
208
- :item-value="modelKey"
209
- :search="search"
210
- :loading="isLoading"
211
- >
212
- <template #default="defaultProps" v-if="viewType.includes('iterator')">
213
- <slot
214
- v-bind="defaultProps"
215
- :operation="operation"
216
- >
217
- <v-container fluid>
218
- <v-row>
219
- <v-col
220
- v-for="(item, index) in defaultProps.items"
221
- :key="index"
222
- :cols="cols"
223
- :sm="sm"
224
- :md="md"
225
- :lg="lg"
226
- :xl="xl"
227
- :xxl="xxl"
228
- >
229
- <slot
230
- name="item"
231
- :item="item"
232
- :operation="operation"
233
- />
234
- </v-col>
235
- </v-row>
236
- </v-container>
237
- </slot>
238
- </template>
239
- <template #loader="loaderProps" v-if="viewType.includes('iterator')">
240
- <slot
241
- name="loader"
242
- v-bind="loaderProps"
243
- >
244
- <v-container fluid>
245
- <v-row>
246
- <v-col
247
- v-for="key in computedSkeletonPerPage"
248
- :key="key"
249
- :cols="cols"
250
- :sm="sm"
251
- :md="md"
252
- :lg="lg"
253
- :xl="xl"
254
- :xxl="xxl"
255
- >
256
- <slot name="loaderItem">
257
- <v-skeleton-loader
258
- type="paragraph"
259
- />
260
- </slot>
261
- </v-col>
262
- </v-row>
263
- </v-container>
264
- </slot>
265
- </template>
266
- <template #header="headerProps">
267
- <slot
268
- name="header"
269
- v-bind="headerProps"
270
- :items="items"
271
- :operation="operation"
272
- >
273
- <VToolbar :color="toolbarColor">
274
- <v-row
275
- justify="end"
276
- class="ma-1"
277
- dense
278
- no-gutters
279
- align="center"
280
- >
281
- <v-col cols="7">
282
- <VToolbarTitle class="pl-3">
283
- <slot
284
- name="title"
285
- :reload="reload"
286
- >
287
- {{ title }}
288
- <v-icon
289
- size="small"
290
- @click="reload"
291
- >
292
- mdi mdi-refresh
293
- </v-icon>
294
- </slot>
295
- </VToolbarTitle>
296
- </v-col>
297
- <v-col cols="5">
298
- <slot name="search" :items="items" :operation="operation" v-if="props.searchable">
299
- <VTextField
300
- v-model="search"
301
- class="justify-end w-100"
302
- density="compact"
303
- hide-details
304
- placeholder="ค้นหา"
305
- clearable
306
- variant="solo"
307
- />
308
- </slot>
309
- </v-col>
310
- </v-row>
311
-
312
- <VToolbarItems>
313
- <slot name="toolbarItems" :items="items" :operation="operation"/>
314
- <ImportCSV
315
- v-if="props.importable && canCreate && props.insertable"
316
- icon="mdi mdi-file-upload"
317
- variant="flat"
318
- :color="toolbarColor"
319
- @import="importItems"
320
- />
321
- <ExportCSV
322
- v-if="props.exportable && items.length"
323
- icon="mdi mdi-file-download"
324
- variant="flat"
325
- :file-name="title"
326
- :model-value="items"
327
- :color="toolbarColor"
328
- />
329
- <VBtn
330
- v-if="canCreate && props.insertable"
331
- :color="toolbarColor"
332
- prepend-icon="mdi mdi-plus"
333
- variant="flat"
334
- @click="openDialog()"
335
- >
336
- add
337
- </VBtn>
338
- <v-row align="center" class="mr-2 ml-2" v-if="viewSwitch">
339
- <v-btn-toggle v-model="viewType" :multiple="viewSwitchMultiple">
340
- <v-btn icon="mdi mdi-view-grid-outline" value="iterator"></v-btn>
341
- <v-btn icon="mdi mdi-table-large" value="table"></v-btn>
342
- </v-btn-toggle>
343
- </v-row>
344
- </VToolbarItems>
345
- </VToolbar>
346
- </slot>
347
- <template v-if="viewType.includes('table')">
348
- <v-data-table-server
349
- v-if="canServerPageable"
350
- v-bind="plainAttrs"
351
- color="primary"
352
- :items="items"
353
- :items-length="itemsLength"
354
- :item-value="props.modelKey"
355
- :search="search"
356
- :loading="isLoading"
357
- @update:options="loadItems"
358
- >
359
- <!-- @ts-ignore -->
360
- <template
361
- v-for="(_, name, index) in (tableSlots as {})"
362
- :key="index"
363
- #[name]="slotData"
364
- >
365
- <slot
366
- :name="name"
367
- v-bind="((slotData || {}) as object)"
368
- :operation="operation"
369
- />
370
- </template>
371
- <template
372
- v-if="!$slots['item.action']"
373
- #item.action="{ item }"
374
- >
375
- <v-btn
376
- v-if="canUpdate"
377
- variant="flat"
378
- density="compact"
379
- icon="mdi mdi-note-edit"
380
- @click="openDialog(item)"
381
- />
382
- <v-btn
383
- v-if="canDelete"
384
- variant="flat"
385
- density="compact"
386
- icon="mdi mdi-delete"
387
- @click="deleteItem(item)"
388
- />
389
- </template>
390
- </v-data-table-server>
391
- <v-data-table
392
- v-else
393
- v-bind="plainAttrs"
394
- color="primary"
395
- :items="items"
396
- :item-value="props.modelKey"
397
- :search="search"
398
- :loading="isLoading"
399
- >
400
- <!-- @ts-ignore -->
401
- <template
402
- v-for="(_, name, index) in (tableSlots as {})"
403
- :key="index"
404
- #[name]="slotData"
405
- >
406
- <slot
407
- :name="name"
408
- v-bind="((slotData || {}) as object)"
409
- :operation="operation"
410
- />
411
- </template>
412
- <template
413
- v-if="!$slots['item.action']"
414
- #item.action="{ item }"
415
- >
416
- <v-btn
417
- v-if="canUpdate"
418
- variant="flat"
419
- density="compact"
420
- icon="mdi mdi-note-edit"
421
- @click="openDialog(item)"
422
- />
423
- <v-btn
424
- v-if="canDelete"
425
- variant="flat"
426
- density="compact"
427
- icon="mdi mdi-delete"
428
- @click="deleteItem(item)"
429
- />
430
- </template>
431
- </v-data-table>
432
- </template>
433
- </template>
434
- <template #footer="footerProps" v-if="viewType.includes('iterator')">
435
- <v-container fluid>
436
- <v-row
437
- align-content="center"
438
- justify="end"
439
- dense
440
- >
441
- <v-spacer />
442
- <v-select
443
- v-model="itemsPerPageInternal"
444
- density="compact"
445
- variant="outlined"
446
- :items="[6, 12, 18, 24, 30, { title: 'All', value: '-1' }]"
447
- min-width="220"
448
- max-width="220"
449
- hide-details
450
- >
451
- <template #prepend>
452
- Items per page:
453
- </template>
454
- </v-select>
455
- <v-pagination
456
- v-if="!canServerPageable"
457
- v-model="currentPage"
458
- density="compact"
459
- :length="footerProps.pageCount"
460
- total-visible="6"
461
- show-first-last-page
462
- @first="footerProps.setPage(1)"
463
- @last="footerProps.setPage(footerProps.pageCount)"
464
- @next="footerProps.nextPage"
465
- @prev="footerProps.prevPage"
466
- @update:model-value="footerProps.setPage"
467
- />
468
- <v-pagination
469
- v-else
470
- v-model="currentPage"
471
- density="compact"
472
- :length="pageCount"
473
- total-visible="6"
474
- show-first-last-page
475
- />
476
- </v-row>
477
- </v-container>
478
- </template>
479
- </v-data-iterator>
480
- <FormDialog
481
- v-model="isDialogOpen"
482
- :title="title"
483
- :fullscreen="dialogFullscreen"
484
- :initial-data="computedInitialData"
485
- :form-data="currentItem"
486
- @create="createItem"
487
- @update="updateItem"
488
- >
489
- <template #default="slotData">
490
- <slot
491
- name="form"
492
- v-bind="slotData"
493
- />
494
- </template>
495
- </FormDialog>
496
- </v-card>
497
- </template>
1
+ <script lang="ts" setup>
2
+ /**
3
+ * ModelIterator connects model metadata to reusable selection, labeling, iterator, or table UI patterns.
4
+ * This doc block is consumed by vue-docgen for generated API documentation.
5
+ */
6
+ import {computed, nextTick, ref, useAttrs, useSlots, watch} from 'vue'
7
+ import {VDataIterator} from 'vuetify/components/VDataIterator'
8
+ import {VDataTable} from "vuetify/components/VDataTable";
9
+ import {omit} from 'lodash-es'
10
+ import type {GraphqlModelProps} from '../../composables/graphqlModel'
11
+ import {useGraphqlModel} from '../../composables/graphqlModel'
12
+ import {useDisplay} from 'vuetify'
13
+
14
+ defineOptions({
15
+ inheritAttrs: false,
16
+ })
17
+
18
+ interface Props extends /* @vue-ignore */ InstanceType<typeof VDataIterator['$props']> {
19
+ title: string // Toolbar title for this model-backed iterator/table block.
20
+ noDataText?: string // Empty-state message shown when model query has no results.
21
+ dialogFullscreen?: boolean // Opens create/edit dialog fullscreen by default.
22
+ initialData?: Record<string, any> // Default values merged with `modelBy` before opening create dialog.
23
+ toolbarColor?: string // Toolbar/action color theme token.
24
+ importable?: boolean // Enables import action for creating many model rows at once.
25
+ exportable?: boolean // Enables export action for current model result set.
26
+ insertable?: boolean // Enables create action for new model rows.
27
+ searchable?: boolean // Shows search input and updates GraphQL model search state.
28
+ search?: string // External search value synced into internal search ref via watcher.
29
+
30
+ viewSwitch?: boolean // Displays control for switching between card iterator and table presentations.
31
+ viewSwitchMultiple?: boolean // Allows multiple selected view chips when switch UI supports it.
32
+
33
+ cols?: string | number | boolean // Base columns used by iterator card grid.
34
+ xxl?: string | number | boolean // Card grid columns at `xxl` breakpoint.
35
+ xl?: string | number | boolean // Card grid columns at `xl` breakpoint.
36
+ lg?: string | number | boolean // Card grid columns at `lg` breakpoint.
37
+ md?: string | number | boolean // Card grid columns at `md` breakpoint.
38
+ sm?: string | number | boolean // Card grid columns at `sm` breakpoint.
39
+ itemsPerPage?: string | number // Items per page for iterator/table pagination (`all` supported).
40
+
41
+ preferTable?: string | number | boolean // Auto view strategy: always table (`true`) or table after N items (number).
42
+ preferTableXxl?: string | number | boolean // `xxl` override for `preferTable`.
43
+ preferTableXl?: string | number | boolean // `xl` override for `preferTable`.
44
+ preferTableLg?: string | number | boolean // `lg` override for `preferTable`.
45
+ preferTableMd?: string | number | boolean // `md` override for `preferTable`.
46
+ preferTableSm?: string | number | boolean // `sm` override for `preferTable`.
47
+ }
48
+
49
+ /**
50
+ * Public props accepted by ModelIterator.
51
+ * Document each prop field with intent, defaults, and side effects for clear generated docs.
52
+ */
53
+ const props = withDefaults(defineProps<Props & GraphqlModelProps>(), {
54
+ noDataText: 'ไม่พบข้อมูล',
55
+ dialogFullscreen: false,
56
+ toolbarColor: 'primary',
57
+ importable: false,
58
+ exportable: false,
59
+ insertable: true,
60
+ searchable: true,
61
+ modelKey: 'id',
62
+ modelBy: undefined,
63
+ fields: () => [],
64
+
65
+ viewSwitch: false,
66
+ viewSwitchMultiple:false,
67
+
68
+ cols: 12,
69
+ xxl: false,
70
+ xl: false,
71
+ lg: 2,
72
+ md: 4,
73
+ sm: 6,
74
+ itemsPerPage: 12,
75
+ })
76
+
77
+ const attrs = useAttrs()
78
+ const plainAttrs = computed(() => {
79
+ const returnAttrs = omit(attrs, ['modelValue', 'onUpdate:modelValue'])
80
+ if (props.headers) returnAttrs['headers'] = props.headers
81
+ return returnAttrs
82
+ })
83
+ const slots = useSlots()
84
+ const tableSlots = computed(() => {
85
+ return omit(slots, ['item'])
86
+ })
87
+
88
+ const display = useDisplay()
89
+ const isProgrammaticSet = ref(false)
90
+ const userOverrodeView = ref(false)
91
+
92
+ const viewType = ref<string[] | string>('iterator')
93
+
94
+ function setViewTypeSafely(val: 'iterator' | 'table') {
95
+ isProgrammaticSet.value = true
96
+ if (Array.isArray(viewType.value)) {
97
+ viewType.value = [val]
98
+ } else {
99
+ viewType.value = val
100
+ }
101
+ nextTick(() => { isProgrammaticSet.value = false })
102
+ }
103
+
104
+ watch(viewType, () => {
105
+ if (!isProgrammaticSet.value) userOverrodeView.value = true
106
+ })
107
+
108
+ function parsePrefer(val: unknown): boolean | number {
109
+ if (val === true) return true
110
+ const n = Number(val)
111
+ return Number.isFinite(n) && n > 0 ? n : false
112
+ }
113
+
114
+ const computedPreferTable = computed<boolean | number>(() => {
115
+ const bp = display.name?.value
116
+ if (bp === 'xxl' && props.preferTableXxl !== undefined) return parsePrefer(props.preferTableXxl)
117
+ if (bp === 'xl' && props.preferTableXl !== undefined) return parsePrefer(props.preferTableXl)
118
+ if (bp === 'lg' && props.preferTableLg !== undefined) return parsePrefer(props.preferTableLg)
119
+ if (bp === 'md' && props.preferTableMd !== undefined) return parsePrefer(props.preferTableMd)
120
+ if (bp === 'sm' && props.preferTableSm !== undefined) return parsePrefer(props.preferTableSm)
121
+ return parsePrefer(props.preferTable)
122
+ })
123
+
124
+ const currentItem = ref<Record<string, any> | undefined>(undefined)
125
+ const isDialogOpen = ref<boolean>(false)
126
+
127
+ const { items, itemsLength,
128
+ search, setSearch, currentOptions,
129
+ canServerPageable, canServerSearch, canCreate, canUpdate, canDelete,
130
+ createItem, importItems, updateItem, deleteItem,
131
+ loadItems, reload,
132
+ isLoading } = useGraphqlModel(props)
133
+
134
+ function openDialog(item?: object) {
135
+ currentItem.value = item
136
+ nextTick(() => {
137
+ isDialogOpen.value = true
138
+ })
139
+ }
140
+
141
+ const itemsPerPageInternal = ref<string | number>()
142
+ watch(() => props.itemsPerPage, (newValue) => {
143
+ if (newValue.toString().toLowerCase() == 'all') itemsPerPageInternal.value = '-1'
144
+ else if (newValue) itemsPerPageInternal.value = newValue
145
+ }, { immediate: true })
146
+
147
+ type SortItem = { key: string, order?: boolean | 'asc' | 'desc' }
148
+ const sortBy = ref<SortItem[]>()
149
+
150
+ const pageCount = computed(() => {
151
+ if (!itemsPerPageInternal.value || itemsPerPageInternal.value == 'All' || itemsPerPageInternal.value == '-1' || Number(itemsPerPageInternal.value) <= 0) return 1
152
+ else return Math.ceil(itemsLength.value / Number(itemsPerPageInternal.value))
153
+ })
154
+ const currentPage = ref<number>(1)
155
+
156
+ watch([currentPage, itemsPerPageInternal, sortBy], () => {
157
+ if (canServerPageable.value) {
158
+ loadItems({
159
+ page: currentPage.value,
160
+ itemsPerPage: (!itemsPerPageInternal.value || itemsPerPageInternal.value == 'All' || Number(itemsPerPageInternal.value) <= 0) ? '-1' : itemsPerPageInternal.value,
161
+ sortBy: sortBy.value,
162
+ })
163
+ }
164
+ }, { immediate: true })
165
+
166
+ watch(
167
+ [itemsLength, computedPreferTable, () => display.name?.value],
168
+ ([len, prefer]) => {
169
+ if (userOverrodeView.value) return // respect explicit user choice forever (until remount)
170
+
171
+ let target: 'iterator' | 'table' = 'iterator'
172
+ if (prefer === true) {
173
+ target = 'table'
174
+ } else if (typeof prefer === 'number') {
175
+ if (Number(len) >= prefer) target = 'table'
176
+ }
177
+
178
+ if (!viewType.value?.includes(target)) setViewTypeSafely(target)
179
+ },
180
+ { immediate: true }
181
+ )
182
+
183
+ const operation = ref({ openDialog, createItem, importItems, updateItem, deleteItem, reload, setSearch, canServerPageable, canServerSearch, canCreate, canUpdate, canDelete })
184
+
185
+ const computedInitialData = computed(() => {
186
+ return Object.assign({}, props.initialData, props.modelBy)
187
+ })
188
+
189
+ const computedSkeletonPerPage = computed(() => {
190
+ if (!itemsPerPageInternal.value || itemsPerPageInternal.value == 'All' || Number(itemsPerPageInternal.value) <= 0) return 1
191
+ else return Number(itemsPerPageInternal.value)
192
+ })
193
+
194
+ watch(()=>props.search,()=>{
195
+ search.value = props.search
196
+ },{immediate:true})
197
+
198
+ defineExpose({ reload,operation })
199
+ </script>
200
+
201
+ <template>
202
+ <v-card>
203
+ <v-data-iterator
204
+ v-bind="plainAttrs"
205
+ v-model:items-per-page="itemsPerPageInternal"
206
+ v-model:sort-by="sortBy"
207
+ :items="items"
208
+ :item-value="modelKey"
209
+ :search="search"
210
+ :loading="isLoading"
211
+ >
212
+ <template #default="defaultProps" v-if="viewType.includes('iterator')">
213
+ <slot
214
+ v-bind="defaultProps"
215
+ :operation="operation"
216
+ >
217
+ <v-container fluid>
218
+ <v-row>
219
+ <v-col
220
+ v-for="(item, index) in defaultProps.items"
221
+ :key="index"
222
+ :cols="cols"
223
+ :sm="sm"
224
+ :md="md"
225
+ :lg="lg"
226
+ :xl="xl"
227
+ :xxl="xxl"
228
+ >
229
+ <slot
230
+ name="item"
231
+ :item="item"
232
+ :operation="operation"
233
+ />
234
+ </v-col>
235
+ </v-row>
236
+ </v-container>
237
+ </slot>
238
+ </template>
239
+ <template #loader="loaderProps" v-if="viewType.includes('iterator')">
240
+ <slot
241
+ name="loader"
242
+ v-bind="loaderProps"
243
+ >
244
+ <v-container fluid>
245
+ <v-row>
246
+ <v-col
247
+ v-for="key in computedSkeletonPerPage"
248
+ :key="key"
249
+ :cols="cols"
250
+ :sm="sm"
251
+ :md="md"
252
+ :lg="lg"
253
+ :xl="xl"
254
+ :xxl="xxl"
255
+ >
256
+ <slot name="loaderItem">
257
+ <v-skeleton-loader
258
+ type="paragraph"
259
+ />
260
+ </slot>
261
+ </v-col>
262
+ </v-row>
263
+ </v-container>
264
+ </slot>
265
+ </template>
266
+ <template #header="headerProps">
267
+ <slot
268
+ name="header"
269
+ v-bind="headerProps"
270
+ :items="items"
271
+ :operation="operation"
272
+ >
273
+ <VToolbar :color="toolbarColor">
274
+ <v-row
275
+ justify="end"
276
+ class="ma-1"
277
+ dense
278
+ no-gutters
279
+ align="center"
280
+ >
281
+ <v-col cols="7">
282
+ <VToolbarTitle class="pl-3">
283
+ <slot
284
+ name="title"
285
+ :reload="reload"
286
+ >
287
+ {{ title }}
288
+ <v-icon
289
+ size="small"
290
+ @click="reload"
291
+ >
292
+ mdi mdi-refresh
293
+ </v-icon>
294
+ </slot>
295
+ </VToolbarTitle>
296
+ </v-col>
297
+ <v-col cols="5">
298
+ <slot name="search" :items="items" :operation="operation" v-if="props.searchable">
299
+ <VTextField
300
+ v-model="search"
301
+ class="justify-end w-100"
302
+ density="compact"
303
+ hide-details
304
+ placeholder="ค้นหา"
305
+ clearable
306
+ variant="solo"
307
+ />
308
+ </slot>
309
+ </v-col>
310
+ </v-row>
311
+
312
+ <VToolbarItems>
313
+ <slot name="toolbarItems" :items="items" :operation="operation"/>
314
+ <ImportCSV
315
+ v-if="props.importable && canCreate && props.insertable"
316
+ icon="mdi mdi-file-upload"
317
+ variant="flat"
318
+ :color="toolbarColor"
319
+ @import="importItems"
320
+ />
321
+ <ExportCSV
322
+ v-if="props.exportable && items.length"
323
+ icon="mdi mdi-file-download"
324
+ variant="flat"
325
+ :file-name="title"
326
+ :model-value="items"
327
+ :color="toolbarColor"
328
+ />
329
+ <VBtn
330
+ v-if="canCreate && props.insertable"
331
+ :color="toolbarColor"
332
+ prepend-icon="mdi mdi-plus"
333
+ variant="flat"
334
+ @click="openDialog()"
335
+ >
336
+ add
337
+ </VBtn>
338
+ <v-row align="center" class="mr-2 ml-2" v-if="viewSwitch">
339
+ <v-btn-toggle v-model="viewType" :multiple="viewSwitchMultiple">
340
+ <v-btn icon="mdi mdi-view-grid-outline" value="iterator"></v-btn>
341
+ <v-btn icon="mdi mdi-table-large" value="table"></v-btn>
342
+ </v-btn-toggle>
343
+ </v-row>
344
+ </VToolbarItems>
345
+ </VToolbar>
346
+ </slot>
347
+ <template v-if="viewType.includes('table')">
348
+ <v-data-table-server
349
+ v-if="canServerPageable"
350
+ v-bind="plainAttrs"
351
+ color="primary"
352
+ :items="items"
353
+ :items-length="itemsLength"
354
+ :item-value="props.modelKey"
355
+ :search="search"
356
+ :loading="isLoading"
357
+ @update:options="loadItems"
358
+ >
359
+ <!-- @ts-ignore -->
360
+ <template
361
+ v-for="(_, name, index) in (tableSlots as {})"
362
+ :key="index"
363
+ #[name]="slotData"
364
+ >
365
+ <slot
366
+ :name="name"
367
+ v-bind="((slotData || {}) as object)"
368
+ :operation="operation"
369
+ />
370
+ </template>
371
+ <template
372
+ v-if="!$slots['item.action']"
373
+ #item.action="{ item }"
374
+ >
375
+ <v-btn
376
+ v-if="canUpdate"
377
+ variant="flat"
378
+ density="compact"
379
+ icon="mdi mdi-note-edit"
380
+ @click="openDialog(item)"
381
+ />
382
+ <v-btn
383
+ v-if="canDelete"
384
+ variant="flat"
385
+ density="compact"
386
+ icon="mdi mdi-delete"
387
+ @click="deleteItem(item)"
388
+ />
389
+ </template>
390
+ </v-data-table-server>
391
+ <v-data-table
392
+ v-else
393
+ v-bind="plainAttrs"
394
+ color="primary"
395
+ :items="items"
396
+ :item-value="props.modelKey"
397
+ :search="search"
398
+ :loading="isLoading"
399
+ >
400
+ <!-- @ts-ignore -->
401
+ <template
402
+ v-for="(_, name, index) in (tableSlots as {})"
403
+ :key="index"
404
+ #[name]="slotData"
405
+ >
406
+ <slot
407
+ :name="name"
408
+ v-bind="((slotData || {}) as object)"
409
+ :operation="operation"
410
+ />
411
+ </template>
412
+ <template
413
+ v-if="!$slots['item.action']"
414
+ #item.action="{ item }"
415
+ >
416
+ <v-btn
417
+ v-if="canUpdate"
418
+ variant="flat"
419
+ density="compact"
420
+ icon="mdi mdi-note-edit"
421
+ @click="openDialog(item)"
422
+ />
423
+ <v-btn
424
+ v-if="canDelete"
425
+ variant="flat"
426
+ density="compact"
427
+ icon="mdi mdi-delete"
428
+ @click="deleteItem(item)"
429
+ />
430
+ </template>
431
+ </v-data-table>
432
+ </template>
433
+ </template>
434
+ <template #footer="footerProps" v-if="viewType.includes('iterator')">
435
+ <v-container fluid>
436
+ <v-row
437
+ align-content="center"
438
+ justify="end"
439
+ dense
440
+ >
441
+ <v-spacer />
442
+ <v-select
443
+ v-model="itemsPerPageInternal"
444
+ density="compact"
445
+ variant="outlined"
446
+ :items="[6, 12, 18, 24, 30, { title: 'All', value: '-1' }]"
447
+ min-width="220"
448
+ max-width="220"
449
+ hide-details
450
+ >
451
+ <template #prepend>
452
+ Items per page:
453
+ </template>
454
+ </v-select>
455
+ <v-pagination
456
+ v-if="!canServerPageable"
457
+ v-model="currentPage"
458
+ density="compact"
459
+ :length="footerProps.pageCount"
460
+ total-visible="6"
461
+ show-first-last-page
462
+ @first="footerProps.setPage(1)"
463
+ @last="footerProps.setPage(footerProps.pageCount)"
464
+ @next="footerProps.nextPage"
465
+ @prev="footerProps.prevPage"
466
+ @update:model-value="footerProps.setPage"
467
+ />
468
+ <v-pagination
469
+ v-else
470
+ v-model="currentPage"
471
+ density="compact"
472
+ :length="pageCount"
473
+ total-visible="6"
474
+ show-first-last-page
475
+ />
476
+ </v-row>
477
+ </v-container>
478
+ </template>
479
+ </v-data-iterator>
480
+ <FormDialog
481
+ v-model="isDialogOpen"
482
+ :title="title"
483
+ :fullscreen="dialogFullscreen"
484
+ :initial-data="computedInitialData"
485
+ :form-data="currentItem"
486
+ @create="createItem"
487
+ @update="updateItem"
488
+ >
489
+ <template #default="slotData">
490
+ <slot
491
+ name="form"
492
+ v-bind="slotData"
493
+ />
494
+ </template>
495
+ </FormDialog>
496
+ </v-card>
497
+ </template>