@ramathibodi/nuxt-commons 0.1.63 → 0.1.65
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
CHANGED
|
@@ -45,6 +45,7 @@ watch(() => alert?.hasAlert(), (hasAlert) => {
|
|
|
45
45
|
elevation="2"
|
|
46
46
|
theme="dark"
|
|
47
47
|
variant="flat"
|
|
48
|
+
v-bind="currentItem.alertIcon ? { icon: currentItem.alertIcon } : {}"
|
|
48
49
|
@click:close="renewAlert()"
|
|
49
50
|
>
|
|
50
51
|
{{ currentItem.statusCode ? currentItem.statusCode + ' ' : '' }} {{ currentItem.message }} {{ !isEmpty(currentItem.data) ? currentItem.data : '' }}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
2
|
import '@vuepic/vue-datepicker/dist/main.css'
|
|
3
|
-
import {computed,
|
|
3
|
+
import {computed, onMounted, ref} from 'vue'
|
|
4
4
|
import {VTextField} from "vuetify/components/VTextField";
|
|
5
|
-
import {isArray
|
|
6
|
-
import {useRules} from "../../composables/utils/validation";
|
|
5
|
+
import {isArray} from "lodash-es";
|
|
7
6
|
|
|
8
7
|
interface IDobPrecision {
|
|
9
8
|
yearMonthDay: string
|
|
10
9
|
yearMonth: string
|
|
11
10
|
year: string
|
|
11
|
+
estimated: string
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
interface Props {
|
|
@@ -31,10 +31,11 @@ const dobPrecisionText = computed(() => {
|
|
|
31
31
|
yearMonthDay: 'YMD',
|
|
32
32
|
yearMonth: 'YM',
|
|
33
33
|
year: 'Y',
|
|
34
|
+
estimated: 'EST',
|
|
34
35
|
}
|
|
35
36
|
})
|
|
36
|
-
const dobPrecisionChoice = ref<('yearMonthDay' | 'yearMonth' | 'year')[]>(['yearMonthDay', 'yearMonth', 'year'])
|
|
37
|
-
const dobPrecisionSelected = defineModel<'yearMonthDay' | 'yearMonth' | 'year'>('dobPrecision')
|
|
37
|
+
const dobPrecisionChoice = ref<('yearMonthDay' | 'yearMonth' | 'year' | 'estimated')[]>(['yearMonthDay', 'yearMonth', 'year', 'estimated'])
|
|
38
|
+
const dobPrecisionSelected = defineModel<'yearMonthDay' | 'yearMonth' | 'year' | 'estimated'>('dobPrecision')
|
|
38
39
|
onMounted(()=>{
|
|
39
40
|
if (!dobPrecisionSelected.value) {
|
|
40
41
|
dobPrecisionSelected.value = 'yearMonthDay'
|
|
@@ -70,6 +71,9 @@ const dobFormat = computed(() => {
|
|
|
70
71
|
if (dobPrecisionSelected.value=="year") {
|
|
71
72
|
displayFormat = "yyyy"
|
|
72
73
|
}
|
|
74
|
+
if (dobPrecisionSelected.value=="estimated") {
|
|
75
|
+
displayFormat = "~ MMM yyyy"
|
|
76
|
+
}
|
|
73
77
|
return displayFormat
|
|
74
78
|
})
|
|
75
79
|
</script>
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
import {watchDebounced} from '@vueuse/core'
|
|
16
16
|
import {useRules} from '../../composables/utils/validation'
|
|
17
17
|
import {useDocumentTemplate} from '../../composables/document/template'
|
|
18
|
-
import { isObject, isArray, isString, isPlainObject, isEqual } from 'lodash-es'
|
|
18
|
+
import { isObject, isArray, isString, isPlainObject, isEqual, debounce } from 'lodash-es'
|
|
19
19
|
import FormPad from './Pad.vue'
|
|
20
20
|
|
|
21
21
|
defineOptions({
|
|
@@ -87,7 +87,9 @@ function isBlankString(v: unknown): v is string {
|
|
|
87
87
|
return isString(v) && v.trim().length === 0
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+
const sanitizeBlankStrings = debounce(sanitizeBlankStringsRaw, 500)
|
|
91
|
+
|
|
92
|
+
function sanitizeBlankStringsRaw(val: any, original?: any): void {
|
|
91
93
|
if (!original && props.originalData) {
|
|
92
94
|
sanitizeBlankStrings(val, props.originalData)
|
|
93
95
|
return
|
|
@@ -101,7 +103,7 @@ function sanitizeBlankStrings(val: any, original?: any): void {
|
|
|
101
103
|
} else if (isPlainObject(item) || isArray(item)) {
|
|
102
104
|
sanitizeBlankStrings(item,{})
|
|
103
105
|
} else {
|
|
104
|
-
if (item && typeof item
|
|
106
|
+
if (item && typeof item === 'string') val[i] = item.replace(/[ \t]+$/, '')
|
|
105
107
|
}
|
|
106
108
|
}
|
|
107
109
|
return
|
|
@@ -117,7 +119,7 @@ function sanitizeBlankStrings(val: any, original?: any): void {
|
|
|
117
119
|
let originalChild = (original && (isPlainObject(original[key]) || isArray(original[key]))) ? original[key] : {}
|
|
118
120
|
sanitizeBlankStrings(v, originalChild)
|
|
119
121
|
} else {
|
|
120
|
-
if (v && typeof v
|
|
122
|
+
if (v && typeof v === 'string') val[key] = v.replace(/[ \t]+$/, '')
|
|
121
123
|
}
|
|
122
124
|
}
|
|
123
125
|
}
|
|
@@ -2,39 +2,142 @@
|
|
|
2
2
|
import { DateTime } from "luxon";
|
|
3
3
|
import { computed } from "vue";
|
|
4
4
|
|
|
5
|
-
type
|
|
5
|
+
type Unit = 'years' | 'months' | 'days' | 'hours' | 'minutes' | 'seconds';
|
|
6
|
+
type Locale = 'th' | 'en' | 'en-US' | 'th-TH';
|
|
6
7
|
|
|
7
8
|
interface Props {
|
|
8
9
|
modelValue: DateTime;
|
|
9
10
|
endDate?: DateTime;
|
|
10
11
|
locale?: Locale;
|
|
11
|
-
|
|
12
|
+
showSuffix?: boolean;
|
|
13
|
+
units?: Unit[];
|
|
14
|
+
zeroBase?: boolean; // true = start at 0, false = start at 1 (inclusive)
|
|
12
15
|
}
|
|
13
16
|
|
|
14
17
|
const props = withDefaults(defineProps<Props>(), {
|
|
15
|
-
locale:
|
|
18
|
+
locale: 'th',
|
|
19
|
+
showSuffix: true,
|
|
16
20
|
zeroBase: true,
|
|
17
21
|
});
|
|
18
22
|
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
// Fallback map: map complex locale (e.g., en-US) to base (e.g., en)
|
|
24
|
+
const normalizeLocale = (locale: Locale): 'en' | 'th' => {
|
|
25
|
+
if (locale.startsWith('en')) return 'en';
|
|
26
|
+
if (locale.startsWith('th')) return 'th';
|
|
27
|
+
return 'th'; // default fallback
|
|
28
|
+
};
|
|
21
29
|
|
|
22
|
-
const
|
|
23
|
-
|
|
30
|
+
const labelsEnPlural: Record<Unit, string> = {
|
|
31
|
+
years: 'years',
|
|
32
|
+
months: 'months',
|
|
33
|
+
days: 'days',
|
|
34
|
+
hours: 'hours',
|
|
35
|
+
minutes: 'minutes',
|
|
36
|
+
seconds: 'seconds',
|
|
37
|
+
};
|
|
38
|
+
const labelsEnSingular: Record<Unit, string> = {
|
|
39
|
+
years: 'year',
|
|
40
|
+
months: 'month',
|
|
41
|
+
days: 'day',
|
|
42
|
+
hours: 'hour',
|
|
43
|
+
minutes: 'minute',
|
|
44
|
+
seconds: 'second',
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const localizedLabels: Record<'en' | 'th', Record<Unit, string>> = {
|
|
48
|
+
en: labelsEnPlural, // จะสลับเป็น singular ตามค่าจริงตอน render
|
|
49
|
+
th: {
|
|
50
|
+
years: 'ปี',
|
|
51
|
+
months: 'เดือน',
|
|
52
|
+
days: 'วัน',
|
|
53
|
+
hours: 'ชั่วโมง',
|
|
54
|
+
minutes: 'นาที',
|
|
55
|
+
seconds: 'วินาที',
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const localizedSuffix: Record<'en' | 'th', string> = {
|
|
60
|
+
en: 'ago',
|
|
61
|
+
th: 'ที่ผ่านมา',
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const outputText = computed(() => {
|
|
65
|
+
const base = props.endDate ?? DateTime.now(); // ใช้ endDate ถ้ามี ไม่งั้น now
|
|
24
66
|
const baseLocale = normalizeLocale(props.locale);
|
|
25
67
|
|
|
26
|
-
|
|
68
|
+
const units: Unit[] = props.units?.length
|
|
69
|
+
? props.units
|
|
70
|
+
: ['years', 'months', 'days', 'hours', 'minutes', 'seconds'];
|
|
71
|
+
|
|
72
|
+
const diffObj = base.diff(props.modelValue, units).toObject();
|
|
73
|
+
|
|
74
|
+
// helper: คืน label ตาม singular/plural (เฉพาะ en)
|
|
75
|
+
const labelFor = (unit: Unit, value: number) => {
|
|
76
|
+
if (baseLocale === 'en') {
|
|
77
|
+
return value === 1 ? labelsEnSingular[unit] : labelsEnPlural[unit];
|
|
78
|
+
}
|
|
79
|
+
return localizedLabels[baseLocale][unit];
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// ---------- โหมด single unit ----------
|
|
83
|
+
if (!props.units) {
|
|
84
|
+
const foundUnit = units.find((unit) => (diffObj[unit] ?? 0) >= 1);
|
|
85
|
+
if (foundUnit) {
|
|
86
|
+
const raw = Math.floor(diffObj[foundUnit] ?? 0); // >= 1
|
|
87
|
+
const value = props.zeroBase ? raw : raw + 1; // inclusive (+1)
|
|
88
|
+
const label = labelFor(foundUnit, value);
|
|
89
|
+
const suffix = props.showSuffix ? localizedSuffix[baseLocale] : '';
|
|
90
|
+
return `${value} ${label}${suffix ? ` ${suffix}` : ''}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ถ้าไม่มีหน่วยใด >= 1 ให้ใช้หน่วยเล็กสุด
|
|
94
|
+
const lastUnit = units.at(-1)!;
|
|
95
|
+
const raw = Math.max(0, Math.floor(diffObj[lastUnit] ?? 0));
|
|
96
|
+
const value = props.zeroBase ? raw : 1; // inclusive: อย่างน้อย 1
|
|
97
|
+
const label = labelFor(lastUnit, value);
|
|
98
|
+
const suffix = props.showSuffix ? localizedSuffix[baseLocale] : '';
|
|
99
|
+
return `${value} ${label}${suffix ? ` ${suffix}` : ''}`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ---------- โหมด multi-unit ----------
|
|
103
|
+
// เก็บค่าแบบตัวเลขก่อน แล้วค่อยเรนเดอร์
|
|
104
|
+
const values = units.map((unit) => ({
|
|
105
|
+
unit,
|
|
106
|
+
raw: Math.max(0, Math.floor(diffObj[unit] ?? 0)),
|
|
107
|
+
}));
|
|
108
|
+
|
|
109
|
+
// เลือกหน่วยที่เล็กที่สุด
|
|
110
|
+
const smallest = values[values.length - 1];
|
|
111
|
+
|
|
112
|
+
// ถ้ามีค่าอย่างน้อยหนึ่งหน่วย > 0 และเป็น inclusive (zeroBase=false) => +1 ที่หน่วยเล็กสุด
|
|
113
|
+
const anyPositive = values.some((v) => v.raw > 0);
|
|
114
|
+
const adjusted = values.map((v, idx) => {
|
|
115
|
+
if (!props.zeroBase && anyPositive && idx === values.length - 1) {
|
|
116
|
+
return { ...v, raw: v.raw + 1 };
|
|
117
|
+
}
|
|
118
|
+
return v;
|
|
119
|
+
});
|
|
27
120
|
|
|
28
|
-
|
|
121
|
+
// สร้างข้อความจากหน่วยที่มีค่า > 0
|
|
122
|
+
const parts = adjusted
|
|
123
|
+
.filter((v) => v.raw > 0)
|
|
124
|
+
.map((v) => `${v.raw} ${labelFor(v.unit, v.raw)}`);
|
|
29
125
|
|
|
30
|
-
|
|
31
|
-
|
|
126
|
+
// หากทั้งหมดเป็น 0:
|
|
127
|
+
if (parts.length === 0) {
|
|
128
|
+
const minValue = props.zeroBase ? 0 : 1;
|
|
129
|
+
const label = labelFor(smallest.unit, minValue);
|
|
130
|
+
const suffix = props.showSuffix ? localizedSuffix[baseLocale] : '';
|
|
131
|
+
return `${minValue} ${label}${suffix ? ` ${suffix}` : ''}`;
|
|
32
132
|
}
|
|
33
133
|
|
|
34
|
-
|
|
134
|
+
const suffix = props.showSuffix ? localizedSuffix[baseLocale] : '';
|
|
135
|
+
return `${parts.join(' ')}${suffix ? ` ${suffix}` : ''}`;
|
|
35
136
|
});
|
|
36
137
|
</script>
|
|
37
138
|
|
|
38
139
|
<template>
|
|
39
|
-
<span>
|
|
140
|
+
<span>
|
|
141
|
+
<i v-if="props.zeroBase" class="mdi mdi-clock-time-twelve-outline" aria-hidden="true" ></i>
|
|
142
|
+
{{ outputText }}</span>
|
|
40
143
|
</template>
|