@ramathibodi/nuxt-commons 0.1.53 → 0.1.55
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/dist/module.json +1 -1
- package/dist/runtime/components/document/TemplateBuilder.vue +3 -3
- package/dist/runtime/components/form/Iterator.vue +59 -0
- package/dist/runtime/components/label/DateAgo.vue +74 -23
- package/dist/runtime/components/model/Table.vue +17 -10
- package/dist/runtime/components/model/iterator.vue +59 -0
- package/dist/runtime/components/model/label.vue +1 -1
- package/package.json +1 -1
package/dist/module.json
CHANGED
|
@@ -3,7 +3,7 @@ import {computed, ref, watch} from 'vue'
|
|
|
3
3
|
import * as prettier from 'prettier'
|
|
4
4
|
import prettierPluginHtml from 'prettier/plugins/html'
|
|
5
5
|
import {useDocumentTemplate, validationRulesRegex,optionStringToChoiceObject} from '../../composables/document/template'
|
|
6
|
-
import {cloneDeep} from "lodash-es";
|
|
6
|
+
import {cloneDeep , filter} from "lodash-es";
|
|
7
7
|
|
|
8
8
|
interface Props {
|
|
9
9
|
title?: string
|
|
@@ -342,14 +342,14 @@ const ruleOptions = (inputType: string) => (value: any) => {
|
|
|
342
342
|
</v-container>
|
|
343
343
|
</template>
|
|
344
344
|
|
|
345
|
-
<template v-for="header of
|
|
345
|
+
<template v-for="header of filter(optionData.headers,(item)=> item.template)" #[`item.${header.key}`]="{item}">
|
|
346
346
|
<form-pad
|
|
347
347
|
:template="header.template"
|
|
348
348
|
v-model="item[header.key]">
|
|
349
349
|
</form-pad>
|
|
350
350
|
|
|
351
351
|
</template>
|
|
352
|
-
<template v-for="header of
|
|
352
|
+
<template v-for="header of filter(optionData.headers,(item)=> item.headerTemplate)" #[`header.${header.key}`]="{item}">
|
|
353
353
|
<form-pad
|
|
354
354
|
:template="header.headerTemplate"
|
|
355
355
|
>
|
|
@@ -5,6 +5,7 @@ import type {FormDialogCallback} from '../../types/formDialog'
|
|
|
5
5
|
import {VDataIterator} from "vuetify/components/VDataIterator";
|
|
6
6
|
import {VDataTable} from "vuetify/components/VDataTable";
|
|
7
7
|
import {VInput} from 'vuetify/components/VInput'
|
|
8
|
+
import {useDisplay} from 'vuetify'
|
|
8
9
|
|
|
9
10
|
defineOptions({
|
|
10
11
|
inheritAttrs: false,
|
|
@@ -35,6 +36,13 @@ interface Props extends /* @vue-ignore */ InstanceType<typeof VDataIterator['$pr
|
|
|
35
36
|
md?: string | number | boolean
|
|
36
37
|
sm?: string | number | boolean
|
|
37
38
|
itemsPerPage?: string | number
|
|
39
|
+
|
|
40
|
+
preferTable?: string | number | boolean
|
|
41
|
+
preferTableXxl?: string | number | boolean
|
|
42
|
+
preferTableXl?: string | number | boolean
|
|
43
|
+
preferTableLg?: string | number | boolean
|
|
44
|
+
preferTableMd?: string | number | boolean
|
|
45
|
+
preferTableSm?: string | number | boolean
|
|
38
46
|
}
|
|
39
47
|
|
|
40
48
|
const props = withDefaults(defineProps<Props>(), {
|
|
@@ -75,8 +83,42 @@ const tableSlots = computed(() => {
|
|
|
75
83
|
return omit(slots, ['item'])
|
|
76
84
|
})
|
|
77
85
|
|
|
86
|
+
const display = useDisplay()
|
|
87
|
+
const isProgrammaticSet = ref(false)
|
|
88
|
+
const userOverrodeView = ref(false)
|
|
89
|
+
|
|
78
90
|
const viewType = ref<string[] | string>('iterator')
|
|
79
91
|
|
|
92
|
+
function setViewTypeSafely(val: 'iterator' | 'table') {
|
|
93
|
+
isProgrammaticSet.value = true
|
|
94
|
+
if (Array.isArray(viewType.value)) {
|
|
95
|
+
viewType.value = [val]
|
|
96
|
+
} else {
|
|
97
|
+
viewType.value = val
|
|
98
|
+
}
|
|
99
|
+
nextTick(() => { isProgrammaticSet.value = false })
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
watch(viewType, () => {
|
|
103
|
+
if (!isProgrammaticSet.value) userOverrodeView.value = true
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
function parsePrefer(val: unknown): boolean | number {
|
|
107
|
+
if (val === true) return true
|
|
108
|
+
const n = Number(val)
|
|
109
|
+
return Number.isFinite(n) && n > 0 ? n : false
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const computedPreferTable = computed<boolean | number>(() => {
|
|
113
|
+
const bp = display.name?.value
|
|
114
|
+
if (bp === 'xxl' && props.preferTableXxl !== undefined) return parsePrefer(props.preferTableXxl)
|
|
115
|
+
if (bp === 'xl' && props.preferTableXl !== undefined) return parsePrefer(props.preferTableXl)
|
|
116
|
+
if (bp === 'lg' && props.preferTableLg !== undefined) return parsePrefer(props.preferTableLg)
|
|
117
|
+
if (bp === 'md' && props.preferTableMd !== undefined) return parsePrefer(props.preferTableMd)
|
|
118
|
+
if (bp === 'sm' && props.preferTableSm !== undefined) return parsePrefer(props.preferTableSm)
|
|
119
|
+
return parsePrefer(props.preferTable)
|
|
120
|
+
})
|
|
121
|
+
|
|
80
122
|
const items = ref<Record<string, any>[]>([])
|
|
81
123
|
const search = ref<string>()
|
|
82
124
|
const currentItem = ref<Record<string, any> | undefined>(undefined)
|
|
@@ -109,6 +151,23 @@ watch(items, (newValue) => {
|
|
|
109
151
|
emit('update:modelValue', newValue)
|
|
110
152
|
}, { deep: true })
|
|
111
153
|
|
|
154
|
+
watch(
|
|
155
|
+
[() => items.value?.length, computedPreferTable, () => display.name?.value],
|
|
156
|
+
([len, prefer]) => {
|
|
157
|
+
if (userOverrodeView.value) return // respect explicit user choice forever (until remount)
|
|
158
|
+
|
|
159
|
+
let target: 'iterator' | 'table' = 'iterator'
|
|
160
|
+
if (prefer === true) {
|
|
161
|
+
target = 'table'
|
|
162
|
+
} else if (typeof prefer === 'number') {
|
|
163
|
+
if (Number(len) >= prefer) target = 'table'
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (!viewType.value?.includes(target)) setViewTypeSafely(target)
|
|
167
|
+
},
|
|
168
|
+
{ immediate: true }
|
|
169
|
+
)
|
|
170
|
+
|
|
112
171
|
const itemsPerPageInternal = ref<string | number>()
|
|
113
172
|
watch(() => props.itemsPerPage, (newValue) => {
|
|
114
173
|
if (newValue.toString().toLowerCase() == 'all') itemsPerPageInternal.value = '-1'
|
|
@@ -1,41 +1,92 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { DateTime
|
|
2
|
+
import { DateTime } from "luxon";
|
|
3
3
|
import { computed } from "vue";
|
|
4
4
|
|
|
5
|
+
type Unit = 'years' | 'months' | 'days' | 'hours' | 'minutes' | 'seconds';
|
|
6
|
+
type Locale = 'th' | 'en' | 'en-US' | 'th-TH';
|
|
7
|
+
|
|
5
8
|
interface Props {
|
|
6
9
|
modelValue: DateTime;
|
|
7
|
-
locale?:
|
|
10
|
+
locale?: Locale;
|
|
11
|
+
showSuffix?: boolean;
|
|
12
|
+
units?: Unit[];
|
|
8
13
|
}
|
|
9
14
|
|
|
10
15
|
const props = withDefaults(defineProps<Props>(), {
|
|
11
|
-
locale: '
|
|
16
|
+
locale: 'th',
|
|
17
|
+
showSuffix: true
|
|
12
18
|
});
|
|
13
19
|
|
|
14
|
-
|
|
20
|
+
// Fallback map: map complex locale (e.g., en-US) to base (e.g., en)
|
|
21
|
+
const normalizeLocale = (locale: Locale): 'en' | 'th' => {
|
|
22
|
+
if (locale.startsWith('en')) return 'en';
|
|
23
|
+
if (locale.startsWith('th')) return 'th';
|
|
24
|
+
return 'th'; // default fallback
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const localizedLabels: Record<'en' | 'th', Record<Unit, string>> = {
|
|
28
|
+
en: {
|
|
29
|
+
years: 'years',
|
|
30
|
+
months: 'months',
|
|
31
|
+
days: 'days',
|
|
32
|
+
hours: 'hours',
|
|
33
|
+
minutes: 'minutes',
|
|
34
|
+
seconds: 'seconds',
|
|
35
|
+
},
|
|
36
|
+
th: {
|
|
37
|
+
years: 'ปี',
|
|
38
|
+
months: 'เดือน',
|
|
39
|
+
days: 'วัน',
|
|
40
|
+
hours: 'ชั่วโมง',
|
|
41
|
+
minutes: 'นาที',
|
|
42
|
+
seconds: 'วินาที',
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const localizedSuffix: Record<'en' | 'th', string> = {
|
|
47
|
+
en: 'ago',
|
|
48
|
+
th: 'ที่ผ่านมา',
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const outputText = computed(() => {
|
|
15
52
|
const now = DateTime.now();
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
53
|
+
const baseLocale = normalizeLocale(props.locale);
|
|
54
|
+
|
|
55
|
+
const units: Unit[] = props.units?.length
|
|
56
|
+
? props.units
|
|
57
|
+
: ['years', 'months', 'days', 'hours', 'minutes', 'seconds'];
|
|
58
|
+
|
|
59
|
+
const diff = now.diff(props.modelValue, units).toObject();
|
|
60
|
+
|
|
61
|
+
if (!props.units) {
|
|
62
|
+
const foundUnit = units.find(unit => (diff[unit] ?? 0) >= 1);
|
|
63
|
+
const value = Math.floor(diff[foundUnit!] ?? 0);
|
|
64
|
+
const label = localizedLabels[baseLocale][foundUnit!];
|
|
65
|
+
const suffix = props.showSuffix ? localizedSuffix[baseLocale] : '';
|
|
66
|
+
return `${value} ${label}${suffix}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const parts = units.map(unit => {
|
|
70
|
+
const value = Math.floor(diff[unit] ?? 0);
|
|
71
|
+
if (value > 0) {
|
|
72
|
+
const label = localizedLabels[baseLocale][unit];
|
|
73
|
+
return `${value} ${label}`;
|
|
74
|
+
}
|
|
75
|
+
return '';
|
|
76
|
+
}).filter(Boolean);
|
|
77
|
+
|
|
78
|
+
if (parts.length === 0) {
|
|
79
|
+
const lastUnit = units.at(-1)!;
|
|
80
|
+
const label = localizedLabels[baseLocale][lastUnit];
|
|
81
|
+
const suffix = props.showSuffix ? localizedSuffix[baseLocale] : '';
|
|
82
|
+
return `0 ${label} ${suffix}`;
|
|
29
83
|
}
|
|
30
84
|
|
|
31
|
-
|
|
85
|
+
const suffix = props.showSuffix ? localizedSuffix[baseLocale] : '';
|
|
86
|
+
return `${parts.join(' ')} ${suffix}`;
|
|
32
87
|
});
|
|
33
88
|
</script>
|
|
34
89
|
|
|
35
90
|
<template>
|
|
36
|
-
<span>{{
|
|
91
|
+
<span>{{ outputText }}</span>
|
|
37
92
|
</template>
|
|
38
|
-
|
|
39
|
-
<style scoped>
|
|
40
|
-
|
|
41
|
-
</style>
|
|
@@ -269,18 +269,25 @@ defineExpose({ reload,operation })
|
|
|
269
269
|
#item.action="{ item }"
|
|
270
270
|
>
|
|
271
271
|
<v-btn
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
272
|
+
v-if="!canUpdate || !canEditRow(item)"
|
|
273
|
+
variant="flat"
|
|
274
|
+
density="compact"
|
|
275
|
+
icon="mdi mdi-note-search"
|
|
276
|
+
@click="openDialogReadonly(item)"
|
|
277
277
|
/>
|
|
278
278
|
<v-btn
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
279
|
+
v-if="canUpdate && canEditRow(item)"
|
|
280
|
+
variant="flat"
|
|
281
|
+
density="compact"
|
|
282
|
+
icon="mdi mdi-note-edit"
|
|
283
|
+
@click="openDialog(item)"
|
|
284
|
+
/>
|
|
285
|
+
<v-btn
|
|
286
|
+
v-if="canDelete && canEditRow(item)"
|
|
287
|
+
variant="flat"
|
|
288
|
+
density="compact"
|
|
289
|
+
icon="mdi mdi-delete"
|
|
290
|
+
@click="confirmDeleteItem(item)"
|
|
284
291
|
/>
|
|
285
292
|
</template>
|
|
286
293
|
</v-data-table>
|
|
@@ -5,6 +5,7 @@ import {VDataTable} from "vuetify/components/VDataTable";
|
|
|
5
5
|
import {omit} from 'lodash-es'
|
|
6
6
|
import type {GraphqlModelProps} from '../../composables/graphqlModel'
|
|
7
7
|
import {useGraphqlModel} from '../../composables/graphqlModel'
|
|
8
|
+
import {useDisplay} from 'vuetify'
|
|
8
9
|
|
|
9
10
|
defineOptions({
|
|
10
11
|
inheritAttrs: false,
|
|
@@ -32,6 +33,13 @@ interface Props extends /* @vue-ignore */ InstanceType<typeof VDataIterator['$pr
|
|
|
32
33
|
md?: string | number | boolean
|
|
33
34
|
sm?: string | number | boolean
|
|
34
35
|
itemsPerPage?: string | number
|
|
36
|
+
|
|
37
|
+
preferTable?: string | number | boolean
|
|
38
|
+
preferTableXxl?: string | number | boolean
|
|
39
|
+
preferTableXl?: string | number | boolean
|
|
40
|
+
preferTableLg?: string | number | boolean
|
|
41
|
+
preferTableMd?: string | number | boolean
|
|
42
|
+
preferTableSm?: string | number | boolean
|
|
35
43
|
}
|
|
36
44
|
|
|
37
45
|
const props = withDefaults(defineProps<Props & GraphqlModelProps>(), {
|
|
@@ -69,8 +77,42 @@ const tableSlots = computed(() => {
|
|
|
69
77
|
return omit(slots, ['item'])
|
|
70
78
|
})
|
|
71
79
|
|
|
80
|
+
const display = useDisplay()
|
|
81
|
+
const isProgrammaticSet = ref(false)
|
|
82
|
+
const userOverrodeView = ref(false)
|
|
83
|
+
|
|
72
84
|
const viewType = ref<string[] | string>('iterator')
|
|
73
85
|
|
|
86
|
+
function setViewTypeSafely(val: 'iterator' | 'table') {
|
|
87
|
+
isProgrammaticSet.value = true
|
|
88
|
+
if (Array.isArray(viewType.value)) {
|
|
89
|
+
viewType.value = [val]
|
|
90
|
+
} else {
|
|
91
|
+
viewType.value = val
|
|
92
|
+
}
|
|
93
|
+
nextTick(() => { isProgrammaticSet.value = false })
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
watch(viewType, () => {
|
|
97
|
+
if (!isProgrammaticSet.value) userOverrodeView.value = true
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
function parsePrefer(val: unknown): boolean | number {
|
|
101
|
+
if (val === true) return true
|
|
102
|
+
const n = Number(val)
|
|
103
|
+
return Number.isFinite(n) && n > 0 ? n : false
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const computedPreferTable = computed<boolean | number>(() => {
|
|
107
|
+
const bp = display.name?.value
|
|
108
|
+
if (bp === 'xxl' && props.preferTableXxl !== undefined) return parsePrefer(props.preferTableXxl)
|
|
109
|
+
if (bp === 'xl' && props.preferTableXl !== undefined) return parsePrefer(props.preferTableXl)
|
|
110
|
+
if (bp === 'lg' && props.preferTableLg !== undefined) return parsePrefer(props.preferTableLg)
|
|
111
|
+
if (bp === 'md' && props.preferTableMd !== undefined) return parsePrefer(props.preferTableMd)
|
|
112
|
+
if (bp === 'sm' && props.preferTableSm !== undefined) return parsePrefer(props.preferTableSm)
|
|
113
|
+
return parsePrefer(props.preferTable)
|
|
114
|
+
})
|
|
115
|
+
|
|
74
116
|
const currentItem = ref<Record<string, any> | undefined>(undefined)
|
|
75
117
|
const isDialogOpen = ref<boolean>(false)
|
|
76
118
|
|
|
@@ -113,6 +155,23 @@ watch([currentPage, itemsPerPageInternal, sortBy], () => {
|
|
|
113
155
|
}
|
|
114
156
|
}, { immediate: true })
|
|
115
157
|
|
|
158
|
+
watch(
|
|
159
|
+
[itemsLength, computedPreferTable, () => display.name?.value],
|
|
160
|
+
([len, prefer]) => {
|
|
161
|
+
if (userOverrodeView.value) return // respect explicit user choice forever (until remount)
|
|
162
|
+
|
|
163
|
+
let target: 'iterator' | 'table' = 'iterator'
|
|
164
|
+
if (prefer === true) {
|
|
165
|
+
target = 'table'
|
|
166
|
+
} else if (typeof prefer === 'number') {
|
|
167
|
+
if (Number(len) >= prefer) target = 'table'
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (!viewType.value?.includes(target)) setViewTypeSafely(target)
|
|
171
|
+
},
|
|
172
|
+
{ immediate: true }
|
|
173
|
+
)
|
|
174
|
+
|
|
116
175
|
const operation = ref({ openDialog, createItem, importItems, updateItem, deleteItem, reload, setSearch, canServerPageable, canServerSearch, canCreate, canUpdate, canDelete })
|
|
117
176
|
|
|
118
177
|
const computedInitialData = computed(() => {
|