@finema/finework-layer 0.2.75 → 0.2.77

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 (51) hide show
  1. package/.husky/pre-commit +1 -1
  2. package/.playground/.env.example +5 -0
  3. package/.playground/app/assets/css/main.css +6 -6
  4. package/.playground/app/pages/layout-admin/[id]/index.vue +145 -145
  5. package/.playground/app/pages/layout-admin/test/[id]/index.vue +286 -286
  6. package/.playground/app/pages/layout-admin.vue +285 -285
  7. package/.playground/app/pages/layout-user.vue +284 -0
  8. package/.playground/app/pages/submenu/layout-admin.vue +210 -210
  9. package/.playground/public/admin/clock-in-admin-logo.png +0 -0
  10. package/.playground/public/admin/clock-in-logo.png +0 -0
  11. package/.playground/public/admin/clock-in.png +0 -0
  12. package/.playground/public/admin/pmo-logo.png +0 -0
  13. package/.playground/public/admin/spider-web.png +0 -0
  14. package/.playground/public/admin/super-admin-logo.png +0 -0
  15. package/.playground/public/admin/super-admin.png +0 -0
  16. package/.playground/public/admin/timesheet-admin-logo.png +0 -0
  17. package/.playground/public/admin/timesheet-logo.png +0 -0
  18. package/.playground/public/admin/timesheet.png +0 -0
  19. package/CHANGELOG.md +378 -364
  20. package/app/app.config.ts +144 -144
  21. package/app/app.vue +10 -10
  22. package/app/assets/css/main.css +77 -77
  23. package/app/components/Button/ActionIcon.vue +29 -29
  24. package/app/components/Button/Back.vue +22 -22
  25. package/app/components/Format/Currency.vue +17 -17
  26. package/app/components/Format/Date.vue +24 -24
  27. package/app/components/Format/DateTime.vue +24 -24
  28. package/app/components/Format/Number.vue +17 -17
  29. package/app/components/Format/Percent.vue +38 -38
  30. package/app/components/Format/TimeFromNow.vue +38 -38
  31. package/app/components/InfoItemList.vue +196 -196
  32. package/app/components/Layout/Admin/Sidebar.vue +329 -329
  33. package/app/components/Layout/Admin/index.vue +224 -270
  34. package/app/components/Layout/Apps.vue +45 -45
  35. package/app/components/Layout/User/index.vue +102 -145
  36. package/app/components/Notifications/index.vue +162 -0
  37. package/app/components/StatusBox.vue +56 -56
  38. package/app/composables/useAuth.ts +207 -207
  39. package/app/composables/useNotification.ts +76 -0
  40. package/app/composables/useRequestOptions.ts +86 -86
  41. package/app/constants/routes.ts +86 -86
  42. package/app/error.vue +218 -218
  43. package/app/middleware/auth.ts +45 -45
  44. package/app/middleware/common.ts +12 -12
  45. package/app/middleware/guest.ts +7 -7
  46. package/app/middleware/permissions.ts +29 -29
  47. package/bun.lock +2758 -2758
  48. package/index.d.ts +16 -16
  49. package/nuxt.config.ts +41 -41
  50. package/package.json +38 -38
  51. package/app/components/NotificationList.vue +0 -65
@@ -1,24 +1,24 @@
1
- <template>
2
- <component
3
- :is="props.as || 'span'"
4
- :title="getValue"
5
- >
6
- {{ getValue }}
7
- </component>
8
- </template>
9
-
10
- <script lang="ts" setup>
11
- const props = defineProps<{
12
- value: string | Date
13
- as?: string
14
- }>()
15
-
16
- const config = useAppConfig()
17
- const localeInject = inject('locale')
18
-
19
- const getValue = computed(() => {
20
- return (localeInject ?? config.core.locale) === 'th'
21
- ? `${TimeHelper.displayDateThai(props.value)} น.`
22
- : TimeHelper.displayDateTime(props.value)
23
- })
24
- </script>
1
+ <template>
2
+ <component
3
+ :is="props.as || 'span'"
4
+ :title="getValue"
5
+ >
6
+ {{ getValue }}
7
+ </component>
8
+ </template>
9
+
10
+ <script lang="ts" setup>
11
+ const props = defineProps<{
12
+ value: string | Date
13
+ as?: string
14
+ }>()
15
+
16
+ const config = useAppConfig()
17
+ const localeInject = inject('locale')
18
+
19
+ const getValue = computed(() => {
20
+ return (localeInject ?? config.core.locale) === 'th'
21
+ ? `${TimeHelper.displayDateThai(props.value)} น.`
22
+ : TimeHelper.displayDateTime(props.value)
23
+ })
24
+ </script>
@@ -1,17 +1,17 @@
1
- <template>
2
- <component :is="props.as || 'span'">
3
- {{ getValue }} {{ unit ? unit : '' }}
4
- </component>
5
- </template>
6
-
7
- <script lang="ts" setup>
8
- const props = defineProps<{
9
- value: number | string | null | undefined
10
- as?: string
11
- unit?: string
12
- }>()
13
-
14
- const getValue = computed(() => {
15
- return NumberHelper.withFixed(props.value || 0)
16
- })
17
- </script>
1
+ <template>
2
+ <component :is="props.as || 'span'">
3
+ {{ getValue }} {{ unit ? unit : '' }}
4
+ </component>
5
+ </template>
6
+
7
+ <script lang="ts" setup>
8
+ const props = defineProps<{
9
+ value: number | string | null | undefined
10
+ as?: string
11
+ unit?: string
12
+ }>()
13
+
14
+ const getValue = computed(() => {
15
+ return NumberHelper.withFixed(props.value || 0)
16
+ })
17
+ </script>
@@ -1,38 +1,38 @@
1
- <template>
2
- <component :is="props.as || 'span'">
3
- {{ getValue }}{{ unit || '%' }}
4
- </component>
5
- </template>
6
-
7
- <script lang="ts" setup>
8
- const props = defineProps<{
9
- value: number | string | null | undefined
10
- total: number | string | null | undefined
11
- as?: string
12
- unit?: string
13
- remaining?: boolean
14
- }>()
15
-
16
- const getValue = computed(() => {
17
- const total = Number(props.total)
18
- const value = Number(props.value)
19
-
20
- if (Number.isNaN(total) || total === 0) return '--'
21
-
22
- const used = Number.isNaN(value) ? 0 : value
23
-
24
- if (props.remaining) {
25
- return NumberHelper.withFixed(((total - used) / total) * 100)
26
- }
27
-
28
- let percent
29
-
30
- if (props.remaining) {
31
- percent = ((total - used) / total) * 100
32
- } else {
33
- percent = (used / total) * 100
34
- }
35
-
36
- return NumberHelper.withFixed(Math.abs(percent))
37
- })
38
- </script>
1
+ <template>
2
+ <component :is="props.as || 'span'">
3
+ {{ getValue }}{{ unit || '%' }}
4
+ </component>
5
+ </template>
6
+
7
+ <script lang="ts" setup>
8
+ const props = defineProps<{
9
+ value: number | string | null | undefined
10
+ total: number | string | null | undefined
11
+ as?: string
12
+ unit?: string
13
+ remaining?: boolean
14
+ }>()
15
+
16
+ const getValue = computed(() => {
17
+ const total = Number(props.total)
18
+ const value = Number(props.value)
19
+
20
+ if (Number.isNaN(total) || total === 0) return '--'
21
+
22
+ const used = Number.isNaN(value) ? 0 : value
23
+
24
+ if (props.remaining) {
25
+ return NumberHelper.withFixed(((total - used) / total) * 100)
26
+ }
27
+
28
+ let percent
29
+
30
+ if (props.remaining) {
31
+ percent = ((total - used) / total) * 100
32
+ } else {
33
+ percent = (used / total) * 100
34
+ }
35
+
36
+ return NumberHelper.withFixed(Math.abs(percent))
37
+ })
38
+ </script>
@@ -1,38 +1,38 @@
1
- <template>
2
- <component
3
- :is="props.as || 'span'"
4
- :title="TimeHelper.displayDateTime(props.value)"
5
- >
6
- {{ getValue }}
7
- </component>
8
- </template>
9
-
10
- <script lang="ts" setup>
11
- import { formatDistanceToNow, differenceInDays } from 'date-fns'
12
- import * as locales from 'date-fns/locale'
13
-
14
- const props = defineProps<{
15
- value: string | Date
16
- as?: string
17
- }>()
18
-
19
- const config = useAppConfig()
20
- const localeInject = inject('locale')
21
-
22
- const getValue = computed(() => {
23
- const locale = (localeInject ?? config.core.locale) === 'th'
24
- ? locales.th
25
- : locales.enUS
26
-
27
- const daysDiff = differenceInDays(new Date(), props.value)
28
-
29
- if (daysDiff <= 6) {
30
- return formatDistanceToNow(props.value, {
31
- addSuffix: true,
32
- locale,
33
- })
34
- }
35
-
36
- return TimeHelper.displayDateTime(props.value)
37
- })
38
- </script>
1
+ <template>
2
+ <component
3
+ :is="props.as || 'span'"
4
+ :title="TimeHelper.displayDateTime(props.value)"
5
+ >
6
+ {{ getValue }}
7
+ </component>
8
+ </template>
9
+
10
+ <script lang="ts" setup>
11
+ import { formatDistanceToNow, differenceInDays } from 'date-fns'
12
+ import * as locales from 'date-fns/locale'
13
+
14
+ const props = defineProps<{
15
+ value: string | Date
16
+ as?: string
17
+ }>()
18
+
19
+ const config = useAppConfig()
20
+ const localeInject = inject('locale')
21
+
22
+ const getValue = computed(() => {
23
+ const locale = (localeInject ?? config.core.locale) === 'th'
24
+ ? locales.th
25
+ : locales.enUS
26
+
27
+ const daysDiff = differenceInDays(new Date(), props.value)
28
+
29
+ if (daysDiff <= 6) {
30
+ return formatDistanceToNow(props.value, {
31
+ addSuffix: true,
32
+ locale,
33
+ })
34
+ }
35
+
36
+ return TimeHelper.displayDateTime(props.value)
37
+ })
38
+ </script>
@@ -1,196 +1,196 @@
1
- <template>
2
- <div
3
- :class="[
4
- 'grid w-full gap-4',
5
- {
6
- 'lg:grid-cols-2': !vertical,
7
- },
8
- customClass,
9
- ]"
10
- >
11
- <div
12
- v-for="item in items"
13
- :key="item.label"
14
- :class="[
15
- 'lg:flex',
16
- {
17
- 'flex-col': !inline,
18
- 'flex-row': inline,
19
- },
20
- item.customClass,
21
-
22
- ]"
23
- >
24
- <template v-if="!item.hide">
25
- <p
26
- v-if="item.type !== TYPE_INFO_ITEM.BOOLEAN"
27
- :class="[
28
- 'mb-1',
29
- {
30
- 'mb-1 block text-sm text-gray-500': !inline,
31
- 'mr-2 text-gray-500': inline,
32
- },
33
- ]"
34
- >
35
- {{ item.label }}
36
- </p>
37
- <component
38
- :is="item.component"
39
- v-if="item.component"
40
- v-bind="item.props"
41
- />
42
- <slot
43
- v-else-if="item.key && $slots[`${item.key}-item`]"
44
- :name="`${item.key}-item`"
45
- :row="item"
46
- :item="item"
47
- :value="item.value"
48
- :label="item.label"
49
- />
50
- <div
51
- v-else
52
- class="break-words whitespace-pre-line text-gray-900"
53
- >
54
- <span
55
- v-if="shouldTruncateText(item)"
56
- v-show="!expandedItems[item.label]"
57
- >
58
- {{ truncateText(item.value || '-', item.max || 0) }}
59
- <button
60
- class="
61
- text-info-600 ml-1 cursor-pointer text-sm
62
- hover:text-info-800
63
- "
64
- @click="toggleExpanded(item.label)"
65
- >
66
- เพิ่มเติม
67
- </button>
68
- </span>
69
- <span
70
- v-if="shouldTruncateText(item)"
71
- v-show="expandedItems[item.label]"
72
- >
73
- {{ item.value || '-' }}
74
- <button
75
- class="
76
- text-info-600 ml-1 cursor-pointer text-sm
77
- hover:text-info-800
78
- "
79
- @click="toggleExpanded(item.label)"
80
- >
81
- แสดงน้อยลง
82
- </button>
83
- </span>
84
- <!-- <span >
85
- <Icon
86
- class="size-5"
87
- :class="item.value ? 'text-success' : 'text-error'"
88
- :name="item.value
89
- ? 'material-symbols:check-circle-outline-rounded'
90
- : 'material-symbols:cancel-outline-rounded'"
91
- />
92
- </span> -->
93
- <Checkbox
94
- v-if="item.type === TYPE_INFO_ITEM.BOOLEAN"
95
- v-model="item.value"
96
- :label="item.label"
97
- class="pointer-events-none"
98
- />
99
- <span
100
- v-else-if="!shouldTruncateText(item)"
101
- :class="[
102
- {
103
- 'font-bold': item.label,
104
- },
105
- ]"
106
- >
107
- {{ getValue(item) }}
108
- </span>
109
- </div>
110
- </template>
111
- </div>
112
- </div>
113
- </template>
114
-
115
- <script lang="ts" setup>
116
- import { reactive } from 'vue'
117
-
118
- enum TYPE_INFO_ITEM {
119
- TEXT = 'text',
120
- NUMBER = 'number',
121
- CURRENCY = 'currency',
122
- DATE = 'date',
123
- DATE_TIME = 'date_time',
124
- BOOLEAN = 'boolean',
125
- }
126
-
127
- defineProps<{
128
- items: Array<{
129
- label: string
130
- value?: any
131
- component?: any
132
- props?: Record<string, any>
133
- max?: number
134
- key?: string
135
- type?: TYPE_INFO_ITEM
136
- customClass?: string
137
- hide?: boolean
138
- }>
139
- vertical?: boolean
140
- inline?: boolean
141
- customClass?: string
142
-
143
- }>()
144
-
145
- const expandedItems = reactive<Record<string, boolean>>({})
146
-
147
- const shouldTruncateText = (item: any): boolean => {
148
- return item.max && item.value && item.value.length > item.max
149
- }
150
-
151
- const truncateText = (text: string, maxLength: number): string => {
152
- if (!maxLength || text.length <= maxLength) return text
153
-
154
- return text.slice(0, maxLength) + '...'
155
- }
156
-
157
- const toggleExpanded = (label: string): void => {
158
- expandedItems[label] = !expandedItems[label]
159
- }
160
-
161
- const getValue = (item: any): string => {
162
- if (item.type === TYPE_INFO_ITEM.DATE) {
163
- return item.value
164
- ? TimeHelper.displayDate(item.value) ?? '-'
165
- : '-'
166
- }
167
-
168
- if (item.type === TYPE_INFO_ITEM.DATE_TIME) {
169
- return item.value
170
- ? TimeHelper.displayDateTime(item.value) ?? '-'
171
- : '-'
172
- }
173
-
174
- if (item.value === undefined || item.value === null) {
175
- return '-'
176
- }
177
-
178
- if (typeof item.value === 'number') {
179
- if (item.type === 'currency') {
180
- return NumberHelper.toCurrency(item.value)
181
- }
182
-
183
- return NumberHelper.withFixed(item.value)
184
- }
185
-
186
- if (item.type === 'currency') {
187
- return NumberHelper.toCurrency(item.value)
188
- }
189
-
190
- if (item.type === 'number') {
191
- return NumberHelper.withFixed(item.value)
192
- }
193
-
194
- return item.value || '-'
195
- }
196
- </script>
1
+ <template>
2
+ <div
3
+ :class="[
4
+ 'grid w-full gap-4',
5
+ {
6
+ 'lg:grid-cols-2': !vertical,
7
+ },
8
+ customClass,
9
+ ]"
10
+ >
11
+ <div
12
+ v-for="item in items"
13
+ :key="item.label"
14
+ :class="[
15
+ 'lg:flex',
16
+ {
17
+ 'flex-col': !inline,
18
+ 'flex-row': inline,
19
+ },
20
+ item.customClass,
21
+
22
+ ]"
23
+ >
24
+ <template v-if="!item.hide">
25
+ <p
26
+ v-if="item.type !== TYPE_INFO_ITEM.BOOLEAN"
27
+ :class="[
28
+ 'mb-1',
29
+ {
30
+ 'mb-1 block text-sm text-gray-500': !inline,
31
+ 'mr-2 text-gray-500': inline,
32
+ },
33
+ ]"
34
+ >
35
+ {{ item.label }}
36
+ </p>
37
+ <component
38
+ :is="item.component"
39
+ v-if="item.component"
40
+ v-bind="item.props"
41
+ />
42
+ <slot
43
+ v-else-if="item.key && $slots[`${item.key}-item`]"
44
+ :name="`${item.key}-item`"
45
+ :row="item"
46
+ :item="item"
47
+ :value="item.value"
48
+ :label="item.label"
49
+ />
50
+ <div
51
+ v-else
52
+ class="break-words whitespace-pre-line text-gray-900"
53
+ >
54
+ <span
55
+ v-if="shouldTruncateText(item)"
56
+ v-show="!expandedItems[item.label]"
57
+ >
58
+ {{ truncateText(item.value || '-', item.max || 0) }}
59
+ <button
60
+ class="
61
+ text-info-600 ml-1 cursor-pointer text-sm
62
+ hover:text-info-800
63
+ "
64
+ @click="toggleExpanded(item.label)"
65
+ >
66
+ เพิ่มเติม
67
+ </button>
68
+ </span>
69
+ <span
70
+ v-if="shouldTruncateText(item)"
71
+ v-show="expandedItems[item.label]"
72
+ >
73
+ {{ item.value || '-' }}
74
+ <button
75
+ class="
76
+ text-info-600 ml-1 cursor-pointer text-sm
77
+ hover:text-info-800
78
+ "
79
+ @click="toggleExpanded(item.label)"
80
+ >
81
+ แสดงน้อยลง
82
+ </button>
83
+ </span>
84
+ <!-- <span >
85
+ <Icon
86
+ class="size-5"
87
+ :class="item.value ? 'text-success' : 'text-error'"
88
+ :name="item.value
89
+ ? 'material-symbols:check-circle-outline-rounded'
90
+ : 'material-symbols:cancel-outline-rounded'"
91
+ />
92
+ </span> -->
93
+ <Checkbox
94
+ v-if="item.type === TYPE_INFO_ITEM.BOOLEAN"
95
+ v-model="item.value"
96
+ :label="item.label"
97
+ class="pointer-events-none"
98
+ />
99
+ <span
100
+ v-else-if="!shouldTruncateText(item)"
101
+ :class="[
102
+ {
103
+ 'font-bold': item.label,
104
+ },
105
+ ]"
106
+ >
107
+ {{ getValue(item) }}
108
+ </span>
109
+ </div>
110
+ </template>
111
+ </div>
112
+ </div>
113
+ </template>
114
+
115
+ <script lang="ts" setup>
116
+ import { reactive } from 'vue'
117
+
118
+ enum TYPE_INFO_ITEM {
119
+ TEXT = 'text',
120
+ NUMBER = 'number',
121
+ CURRENCY = 'currency',
122
+ DATE = 'date',
123
+ DATE_TIME = 'date_time',
124
+ BOOLEAN = 'boolean',
125
+ }
126
+
127
+ defineProps<{
128
+ items: Array<{
129
+ label: string
130
+ value?: any
131
+ component?: any
132
+ props?: Record<string, any>
133
+ max?: number
134
+ key?: string
135
+ type?: TYPE_INFO_ITEM
136
+ customClass?: string
137
+ hide?: boolean
138
+ }>
139
+ vertical?: boolean
140
+ inline?: boolean
141
+ customClass?: string
142
+
143
+ }>()
144
+
145
+ const expandedItems = reactive<Record<string, boolean>>({})
146
+
147
+ const shouldTruncateText = (item: any): boolean => {
148
+ return item.max && item.value && item.value.length > item.max
149
+ }
150
+
151
+ const truncateText = (text: string, maxLength: number): string => {
152
+ if (!maxLength || text.length <= maxLength) return text
153
+
154
+ return text.slice(0, maxLength) + '...'
155
+ }
156
+
157
+ const toggleExpanded = (label: string): void => {
158
+ expandedItems[label] = !expandedItems[label]
159
+ }
160
+
161
+ const getValue = (item: any): string => {
162
+ if (item.type === TYPE_INFO_ITEM.DATE) {
163
+ return item.value
164
+ ? TimeHelper.displayDate(item.value) ?? '-'
165
+ : '-'
166
+ }
167
+
168
+ if (item.type === TYPE_INFO_ITEM.DATE_TIME) {
169
+ return item.value
170
+ ? TimeHelper.displayDateTime(item.value) ?? '-'
171
+ : '-'
172
+ }
173
+
174
+ if (item.value === undefined || item.value === null) {
175
+ return '-'
176
+ }
177
+
178
+ if (typeof item.value === 'number') {
179
+ if (item.type === 'currency') {
180
+ return NumberHelper.toCurrency(item.value)
181
+ }
182
+
183
+ return NumberHelper.withFixed(item.value)
184
+ }
185
+
186
+ if (item.type === 'currency') {
187
+ return NumberHelper.toCurrency(item.value)
188
+ }
189
+
190
+ if (item.type === 'number') {
191
+ return NumberHelper.withFixed(item.value)
192
+ }
193
+
194
+ return item.value || '-'
195
+ }
196
+ </script>