@finema/finework-layer 0.2.139 → 0.2.141

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.141](https://gitlab.finema.co/finema/finework/finework-frontend-layer/compare/0.2.140...0.2.141) (2026-02-19)
4
+
5
+ ### Bug Fixes
6
+
7
+ * improve noti ([cb22978](https://gitlab.finema.co/finema/finework/finework-frontend-layer/commit/cb22978810ab73bdabb21bf0f49c35aaa19ed282))
8
+
9
+ ## [0.2.140](https://gitlab.finema.co/finema/finework/finework-frontend-layer/compare/0.2.139...0.2.140) (2026-02-17)
10
+
11
+ ### Features
12
+
13
+ * refactor TimeFromNow component and add RelativeTimeFromNow component for enhanced date formatting ([b7c9d76](https://gitlab.finema.co/finema/finework/finework-frontend-layer/commit/b7c9d76dfdc8f5ec901daa582fec4e16db1ccd20))
14
+
3
15
  ## [0.2.139](https://gitlab.finema.co/finema/finework/finework-frontend-layer/compare/0.2.138...0.2.139) (2026-02-16)
4
16
 
5
17
  ### Bug Fixes
@@ -0,0 +1,106 @@
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 { differenceInDays, differenceInHours, differenceInMinutes, differenceInMonths, differenceInWeeks, startOfDay, addDays } from 'date-fns'
12
+
13
+ const props = defineProps<{
14
+ value: string | Date
15
+ as?: string
16
+ }>()
17
+
18
+ const config = useAppConfig()
19
+ const localeInject = inject('locale')
20
+
21
+ const isThaiLocale = computed(() => {
22
+ return (localeInject ?? config.core.locale) === 'th'
23
+ })
24
+
25
+ const getValue = computed(() => {
26
+ const targetDate = new Date(props.value)
27
+ const now = new Date()
28
+
29
+ const nowDate = startOfDay(now)
30
+ const dueDateOnly = startOfDay(targetDate)
31
+
32
+ if (nowDate > dueDateOnly) {
33
+ const dueDateEnd = addDays(dueDateOnly, 1)
34
+
35
+ const months = differenceInMonths(now, dueDateEnd)
36
+
37
+ if (months > 0) {
38
+ if (isThaiLocale.value) {
39
+ return months === 1 ? '1 เดือนที่แล้ว' : `${months} เดือนที่แล้ว`
40
+ }
41
+
42
+ return months === 1 ? '1 month ago' : `${months} months ago`
43
+ }
44
+
45
+ const weeks = differenceInWeeks(now, dueDateEnd)
46
+
47
+ if (weeks > 0) {
48
+ if (isThaiLocale.value) {
49
+ return weeks === 1 ? '1 สัปดาห์ที่แล้ว' : `${weeks} สัปดาห์ที่แล้ว`
50
+ }
51
+
52
+ return weeks === 1 ? '1 week ago' : `${weeks} weeks ago`
53
+ }
54
+
55
+ const days = differenceInDays(now, dueDateEnd)
56
+
57
+ if (days > 0) {
58
+ if (isThaiLocale.value) {
59
+ return days === 1 ? '1 วันที่แล้ว' : `${days} วันที่แล้ว`
60
+ }
61
+
62
+ return days === 1 ? '1 day ago' : `${days} days ago`
63
+ }
64
+
65
+ const hours = differenceInHours(now, dueDateEnd)
66
+
67
+ if (hours > 0) {
68
+ if (isThaiLocale.value) {
69
+ return hours === 1 ? '1 ชม.ที่แล้ว' : `${hours} ชม.ที่แล้ว`
70
+ }
71
+
72
+ return hours === 1 ? '1hr ago' : `${hours}hr ago`
73
+ }
74
+
75
+ const minutes = differenceInMinutes(now, dueDateEnd)
76
+
77
+ if (minutes > 0) {
78
+ if (isThaiLocale.value) {
79
+ return minutes === 1 ? '1 นาทีที่แล้ว' : `${minutes} นาทีที่แล้ว`
80
+ }
81
+
82
+ return minutes === 1 ? '1m ago' : `${minutes}m ago`
83
+ }
84
+
85
+ return isThaiLocale.value ? 'เมื่อสักครู่' : 'just now'
86
+ }
87
+
88
+ if (nowDate.getTime() === dueDateOnly.getTime()) {
89
+ return isThaiLocale.value ? 'วันนี้' : 'Today'
90
+ }
91
+
92
+ const tomorrow = addDays(nowDate, 1)
93
+
94
+ if (tomorrow.getTime() === dueDateOnly.getTime()) {
95
+ return isThaiLocale.value ? 'พรุ่งนี้' : 'Tomorrow'
96
+ }
97
+
98
+ const days = differenceInDays(dueDateOnly, nowDate)
99
+
100
+ if (isThaiLocale.value) {
101
+ return `อีก ${days} วัน`
102
+ }
103
+
104
+ return `in ${days}d`
105
+ })
106
+ </script>
@@ -8,72 +8,32 @@
8
8
  </template>
9
9
 
10
10
  <script lang="ts" setup>
11
- import { differenceInDays, differenceInHours, differenceInMinutes, differenceInMonths, differenceInWeeks, startOfDay, addDays } from 'date-fns'
11
+ import { formatDistanceToNow, differenceInDays } from 'date-fns'
12
+ import * as locales from 'date-fns/locale'
12
13
 
13
14
  const props = defineProps<{
14
15
  value: string | Date
15
16
  as?: string
16
17
  }>()
17
18
 
18
- const getValue = computed(() => {
19
- const targetDate = new Date(props.value)
20
- const now = new Date()
21
-
22
- // Compare only the date part
23
- const nowDate = startOfDay(now)
24
- const dueDateOnly = startOfDay(targetDate)
25
-
26
- if (nowDate > dueDateOnly) {
27
- const dueDateEnd = addDays(dueDateOnly, 1)
28
-
29
- const months = differenceInMonths(now, dueDateEnd)
30
-
31
- if (months > 0) {
32
- return months === 1 ? '1 month ago' : `${months} months ago`
33
- }
34
-
35
- const weeks = differenceInWeeks(now, dueDateEnd)
36
-
37
- if (weeks > 0) {
38
- return weeks === 1 ? '1 week ago' : `${weeks} weeks ago`
39
- }
40
-
41
- const days = differenceInDays(now, dueDateEnd)
42
-
43
- if (days > 0) {
44
- return days === 1 ? '1 day ago' : `${days} days ago`
45
- }
19
+ const config = useAppConfig()
20
+ const localeInject = inject('locale')
46
21
 
47
- const hours = differenceInHours(now, dueDateEnd)
48
-
49
- if (hours > 0) {
50
- return hours === 1 ? '1hr ago' : `${hours}hr ago`
51
- }
52
-
53
- const minutes = differenceInMinutes(now, dueDateEnd)
54
-
55
- if (minutes > 0) {
56
- return minutes === 1 ? '1m ago' : `${minutes}m ago`
57
- }
58
-
59
- return 'just now'
60
- }
61
-
62
- // Today
63
- if (nowDate.getTime() === dueDateOnly.getTime()) {
64
- return 'Today'
65
- }
22
+ const getValue = computed(() => {
23
+ const locale = (localeInject ?? config.core.locale) === 'th'
24
+ ? locales.th
25
+ : locales.enUS
66
26
 
67
- // Tomorrow
68
- const tomorrow = addDays(nowDate, 1)
27
+ const targetDate = new Date(props.value)
28
+ const daysDiff = differenceInDays(new Date(), targetDate)
69
29
 
70
- if (tomorrow.getTime() === dueDateOnly.getTime()) {
71
- return 'Tomorrow'
30
+ if (daysDiff <= 6) {
31
+ return formatDistanceToNow(targetDate, {
32
+ addSuffix: true,
33
+ locale,
34
+ })
72
35
  }
73
36
 
74
- // In X days (future)
75
- const days = differenceInDays(dueDateOnly, nowDate)
76
-
77
- return `in ${days}d`
37
+ return TimeHelper.displayDateTime(props.value)
78
38
  })
79
39
  </script>
@@ -39,42 +39,36 @@
39
39
  <NuxtLink
40
40
  :to="getLink(item)"
41
41
  :target="getLink(item) === '#' ? '_self' : '_blank'"
42
- :class="['mb-2 flex cursor-pointer items-start gap-2 rounded-lg p-4 hover:bg-gray-100']"
42
+ :class="['flex cursor-pointer items-start gap-2 rounded-lg p-4 hover:bg-gray-100']"
43
43
  >
44
- <Chip
45
- v-if="getImage(item)"
44
+ <!-- <Chip
46
45
  position="bottom-right"
47
46
  size="3xl"
48
47
  inset
49
48
  :color="getNotificationColor(item)"
50
- >
49
+ > -->
50
+ <div class="relative">
51
+ <Avatar
52
+ class="min-h-12 min-w-12"
53
+ :src="item.actor.avatar_url"
54
+ :alt="item.actor.display_name"
55
+ />
51
56
  <img
52
-
53
57
  :src="getImage(item)!"
54
- class="w-12"
58
+ class="absolute -right-1 -bottom-1 w-5 rounded-full bg-white p-0.25"
55
59
  />
56
- </Chip>
57
-
58
- <div class="flex-1">
59
- <div class="flex items-start justify-between gap-2">
60
- <p
61
- class="line-clamp-1 text-sm font-medium text-gray-700"
62
- :title="item.title"
63
- >
64
- {{ item.title }}
65
- </p>
66
- <FormatTimeFromNow
67
- class="text-muted flex-1 text-right text-xs whitespace-nowrap"
68
- :value="item.sent_at"
69
- />
70
- </div>
60
+ </div>
61
+ <!-- </Chip> -->
71
62
 
63
+ <div class="flex flex-1 flex-col gap-0">
72
64
  <p
73
- class="text-sm text-neutral-500"
74
- :title="getMessage(item)"
75
- >
76
- {{ getMessage(item) }}
77
- </p>
65
+ class="text-gray line-clamp-2 text-sm"
66
+ v-html="getMessage(item)"
67
+ />
68
+ <FormatTimeFromNow
69
+ class="text-xs font-medium whitespace-nowrap text-gray-600"
70
+ :value="item.sent_at"
71
+ />
78
72
  </div>
79
73
  </NuxtLink>
80
74
  </div>
@@ -101,24 +95,41 @@ onMounted(() => {
101
95
  })
102
96
 
103
97
  const getLink = (item: INotificationItem) => {
104
- if (item.data?.url) {
105
- return item.data.url
106
- }
98
+ if (item.data?.url) return item.data.url
107
99
 
108
- if (item.source_type === 'PMO_PROJECT') {
109
- return `/pmo/projects/${item.source_id}`
110
- } else if (item.source_type === 'NEWSLETTER') {
111
- return `/announcement/${item.source_id}`
112
- } else if (item.source_type === 'TODO' && (item.action_type === 'TODO_ASSIGN_CARD' || item.action_type === 'TODO_COMMENT_ADD' || item.action_type === 'TODO_DUE_DATE_CHANGE' || item.action_type === 'TODO_STATUS_CHANGE')) {
113
- return `/todo/boards/${item.data?.todo_board_slug}/cards/${item.data?.todo_slug}`
114
- } else if (item.source_type === 'TODO' && (item.action_type === 'TODO_REMOVE_CARD' || item.action_type === 'TODO_ASSIGN_BOARD')) {
115
- return `/todo/boards/${item.data?.todo_board_slug}`
116
- } else if (item.source_type === 'TODO' && item.action_type === 'TODO_REMOVE_BOARD') {
117
- return `/todo/cards`
118
- }
100
+ switch (item.source_type) {
101
+ case 'PMO_PROJECT':
102
+ return `/pmo/projects/${item.source_id}`
103
+
104
+ case 'NEWSLETTER':
105
+ return `/announcement/${item.source_id}`
106
+
107
+ case 'TODO': {
108
+ const boardSlug = item.data?.todo_board_slug
109
+ const cardSlug = item.data?.todo_slug
110
+
111
+ switch (item.action_type) {
112
+ case 'TODO_ASSIGN_CARD':
113
+ case 'TODO_COMMENT_ADD':
114
+ case 'TODO_DUE_DATE_CHANGE':
115
+ case 'TODO_STATUS_CHANGE':
116
+ return `/todo/boards/${boardSlug}/cards/${cardSlug}`
119
117
 
120
- // todo/boards/MEE/cards/MEE-1
121
- return '#'
118
+ case 'TODO_REMOVE_CARD':
119
+ case 'TODO_ASSIGN_BOARD':
120
+ return `/todo/boards/${boardSlug}`
121
+
122
+ case 'TODO_REMOVE_BOARD':
123
+ return `/todo/cards`
124
+
125
+ default:
126
+ return '#'
127
+ }
128
+ }
129
+
130
+ default:
131
+ return '#'
132
+ }
122
133
  }
123
134
 
124
135
  const getImage = (item: INotificationItem): string | null => {
@@ -159,7 +170,42 @@ const getNotificationColor = (item: INotificationItem): string => {
159
170
  }
160
171
 
161
172
  const getMessage = (item: INotificationItem): string => {
162
- return item.message
173
+ let message = item.message
174
+
175
+ // ตรวจหาวันที่ในรูปแบบ yyyy-mm-dd และแปลงเป็น text
176
+ const datePattern = /\b(\d{4})-(\d{2})-(\d{2})\b/g
177
+
178
+ message = message.replace(datePattern, (match) => {
179
+ try {
180
+ const date = new Date(match)
181
+
182
+ return TimeHelper.displayDate(date)
183
+ ? `<span class="text-primary font-medium"> ${TimeHelper.displayDate(date)}</span>`
184
+ : match
185
+ } catch {
186
+ return match
187
+ }
188
+ })
189
+
190
+ if (item.data) {
191
+ Object.entries(item.data).forEach(([key, value]) => {
192
+ if (key.endsWith('_slug') && typeof value === 'string' && value) {
193
+ // หา slug ในข้อความและครอบด้วย <strong>
194
+ const slugPattern = new RegExp(`\\b${value}\\b`, 'g')
195
+
196
+ message = message.replace(slugPattern, `<span class="text-primary font-medium">${value}</span>`)
197
+ }
198
+ })
199
+ }
200
+
201
+ // ตรวจหาคำที่อยู่ระหว่าง "from" และ "to" และทำให้เป็น bold + text-primary font-semibold
202
+ const fromToPattern = /\bfrom\s+(\S+)\s+to\s+(\S+)/gi
203
+
204
+ message = message.replace(fromToPattern, (_match, from, to) => {
205
+ return `from <span class="text-primary font-medium">${from}</span> to <span class="text-primary font-semibold">${to}</span>`
206
+ })
207
+
208
+ return message
163
209
  }
164
210
 
165
211
  useWatchTrue(() => markAllRead.status.value.isSuccess, () => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@finema/finework-layer",
3
3
  "type": "module",
4
- "version": "0.2.139",
4
+ "version": "0.2.141",
5
5
  "main": "./nuxt.config.ts",
6
6
  "scripts": {
7
7
  "dev": "nuxi dev .playground -o",
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file