@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,207 +1,207 @@
1
- export enum Permission {
2
- USER = 'USER',
3
- ADMIN = 'ADMIN',
4
- SUPER = 'SUPER',
5
- NONE = 'NONE',
6
- }
7
-
8
- export const PERMISSION_LABEL = {
9
- [Permission.ADMIN]: 'Admin',
10
- [Permission.USER]: 'User',
11
- [Permission.SUPER]: 'Settings',
12
- [Permission.NONE]: 'No Access',
13
- }
14
-
15
- export interface ITeam {
16
- id: string
17
- name: string
18
- code: string
19
- color: string
20
- description?: string
21
- working_start_at: string
22
- working_end_at: string
23
- users: IUser[] | null
24
- created_at: string
25
- updated_at: string
26
- }
27
-
28
- export interface IUser {
29
- id: string
30
- email: string
31
- full_name: string
32
- display_name: string
33
- position: string
34
- team: ITeam
35
- team_code: string
36
- avatar_url: string
37
- company: string
38
- sig: string
39
- access_level: IUserAccessLevel
40
- is_active: boolean
41
- joined_date: string
42
- slack_id: string
43
- created_at: string
44
- updated_at: string
45
- }
46
-
47
- export enum UserModule {
48
- CHECKIN = 'clockin',
49
- PMO = 'pmo',
50
- TIMESHEET = 'timesheet',
51
- SETTING = 'setting',
52
- }
53
-
54
- export interface IUserAccessLevel {
55
- used_id: string
56
- pmo: Permission.USER | Permission.ADMIN | Permission.SUPER
57
- clockin: Permission.USER | Permission.ADMIN
58
- timesheet: Permission.USER | Permission.ADMIN
59
- setting: Permission.USER | Permission.SUPER
60
- }
61
-
62
- export const useAuth = () => {
63
- const {
64
- auth,
65
- } = useRequestOptions()
66
-
67
- const token = useCookie('token', {
68
- path: '/',
69
- maxAge: 60 * 60 * 24 * 365,
70
- })
71
-
72
- const isAuthenticated = computed(() => !!token.value)
73
- const me = defineStore('auth.me', () => {
74
- const value = ref<IUser | null>(null)
75
- const timestamp = ref<number | null>(null)
76
-
77
- const set = (user: IUser | null) => {
78
- value.value = user
79
- }
80
-
81
- const setTimestamp = (t: number | null) => {
82
- timestamp.value = t
83
- }
84
-
85
- const isShouldFetch = () => {
86
- if (!value.value || !timestamp.value) return true
87
-
88
- // expire after 1 min - fetch if timestamp is older than 1 minute ago
89
- return Date.now() - timestamp.value > 1000 * 60 * 1
90
- }
91
-
92
- return {
93
- value,
94
- set,
95
- timestamp,
96
- setTimestamp,
97
- isShouldFetch,
98
- }
99
- })()
100
-
101
- const fetchMe = (() => {
102
- return useObjectLoader<IUser>({
103
- method: 'GET',
104
- url: '/me',
105
- getRequestOptions: auth,
106
- })
107
- })()
108
-
109
- const updateMe = (() => {
110
- return useObjectLoader<IUser>({
111
- method: 'PUT',
112
- url: '/me',
113
- getRequestOptions: auth,
114
- })
115
- })()
116
-
117
- const loginSlack = async () => {
118
- const config = useRuntimeConfig()
119
-
120
- window.location.href = config.public.baseAPI + '/auth/slack-login'
121
- }
122
-
123
- const loginMs = async () => {
124
- const config = useRuntimeConfig()
125
-
126
- window.location.href = config.public.baseAPI + '/auth/ms-login'
127
- }
128
-
129
- useWatchTrue(() => fetchMe.status.value.isSuccess, () => {
130
- me.setTimestamp(Date.now())
131
- me.set(fetchMe.data.value)
132
- })
133
-
134
- useWatchTrue(() => fetchMe.status.value.isError, () => {
135
- token.value = undefined
136
- me.setTimestamp(null)
137
- me.set(null)
138
- })
139
-
140
- const hasPermission = (module: UserModule, ...permission: Permission[]) => {
141
- if (!me.value?.access_level) {
142
- return false
143
- }
144
-
145
- return permission.includes(me.value.access_level[module])
146
- }
147
-
148
- const isSuperAdmin = computed(() => {
149
- return me.value?.access_level?.setting === Permission.SUPER
150
- })
151
-
152
- const menusNavbar = computed(() => [
153
- {
154
- label: 'Clock-In',
155
- icon: '/admin/clock-in-logo.png',
156
- to: routes.clockin.home.to,
157
- },
158
- {
159
- label: 'Timesheet',
160
- icon: '/admin/timesheet-logo.png',
161
- to: routes.timesheet.home.to,
162
- },
163
- ...(hasPermission(UserModule.PMO, Permission.ADMIN, Permission.USER, Permission.SUPER)
164
- ? [{
165
- label: 'PMO',
166
- icon: '/admin/pmo-logo.png',
167
- to: routes.pmo.project.projects.to,
168
- }]
169
- : []),
170
- ...(hasPermission(UserModule.CHECKIN, Permission.ADMIN)
171
- ? [{
172
- label: 'Clock-In Admin',
173
- icon: '/admin/clock-in-admin-logo.png',
174
- to: routes.adminClockin.checkinDashboard.to,
175
- }]
176
- : []),
177
- ...(hasPermission(UserModule.TIMESHEET, Permission.ADMIN)
178
- ? [{
179
- label: 'Timesheet Admin',
180
- icon: '/admin/timesheet-admin-logo.png',
181
- to: routes.adminTimesheet.summaryReport.to,
182
- }]
183
- : []),
184
-
185
- ...(hasPermission(UserModule.SETTING, Permission.SUPER)
186
- ? [{
187
- label: 'Settings',
188
- icon: '/admin/super-admin-logo.png',
189
- to: routes.admin.users.to,
190
- }]
191
- : []),
192
- ])
193
-
194
- return {
195
- token,
196
- loginSlack,
197
- loginMs,
198
- isAuthenticated,
199
- me,
200
- fetchMe,
201
- updateMe,
202
- hasPermission,
203
- isSuperAdmin,
204
- menusNavbar,
205
-
206
- }
207
- }
1
+ export enum Permission {
2
+ USER = 'USER',
3
+ ADMIN = 'ADMIN',
4
+ SUPER = 'SUPER',
5
+ NONE = 'NONE',
6
+ }
7
+
8
+ export const PERMISSION_LABEL = {
9
+ [Permission.ADMIN]: 'Admin',
10
+ [Permission.USER]: 'User',
11
+ [Permission.SUPER]: 'Settings',
12
+ [Permission.NONE]: 'No Access',
13
+ }
14
+
15
+ export interface ITeam {
16
+ id: string
17
+ name: string
18
+ code: string
19
+ color: string
20
+ description?: string
21
+ working_start_at: string
22
+ working_end_at: string
23
+ users: IUser[] | null
24
+ created_at: string
25
+ updated_at: string
26
+ }
27
+
28
+ export interface IUser {
29
+ id: string
30
+ email: string
31
+ full_name: string
32
+ display_name: string
33
+ position: string
34
+ team: ITeam
35
+ team_code: string
36
+ avatar_url: string
37
+ company: string
38
+ sig: string
39
+ access_level: IUserAccessLevel
40
+ is_active: boolean
41
+ joined_date: string
42
+ slack_id: string
43
+ created_at: string
44
+ updated_at: string
45
+ }
46
+
47
+ export enum UserModule {
48
+ CHECKIN = 'clockin',
49
+ PMO = 'pmo',
50
+ TIMESHEET = 'timesheet',
51
+ SETTING = 'setting',
52
+ }
53
+
54
+ export interface IUserAccessLevel {
55
+ used_id: string
56
+ pmo: Permission.USER | Permission.ADMIN | Permission.SUPER
57
+ clockin: Permission.USER | Permission.ADMIN
58
+ timesheet: Permission.USER | Permission.ADMIN
59
+ setting: Permission.USER | Permission.SUPER
60
+ }
61
+
62
+ export const useAuth = () => {
63
+ const {
64
+ auth,
65
+ } = useRequestOptions()
66
+
67
+ const token = useCookie('token', {
68
+ path: '/',
69
+ maxAge: 60 * 60 * 24 * 365,
70
+ })
71
+
72
+ const isAuthenticated = computed(() => !!token.value)
73
+ const me = defineStore('auth.me', () => {
74
+ const value = ref<IUser | null>(null)
75
+ const timestamp = ref<number | null>(null)
76
+
77
+ const set = (user: IUser | null) => {
78
+ value.value = user
79
+ }
80
+
81
+ const setTimestamp = (t: number | null) => {
82
+ timestamp.value = t
83
+ }
84
+
85
+ const isShouldFetch = () => {
86
+ if (!value.value || !timestamp.value) return true
87
+
88
+ // expire after 1 min - fetch if timestamp is older than 1 minute ago
89
+ return Date.now() - timestamp.value > 1000 * 60 * 1
90
+ }
91
+
92
+ return {
93
+ value,
94
+ set,
95
+ timestamp,
96
+ setTimestamp,
97
+ isShouldFetch,
98
+ }
99
+ })()
100
+
101
+ const fetchMe = (() => {
102
+ return useObjectLoader<IUser>({
103
+ method: 'GET',
104
+ url: '/me',
105
+ getRequestOptions: auth,
106
+ })
107
+ })()
108
+
109
+ const updateMe = (() => {
110
+ return useObjectLoader<IUser>({
111
+ method: 'PUT',
112
+ url: '/me',
113
+ getRequestOptions: auth,
114
+ })
115
+ })()
116
+
117
+ const loginSlack = async () => {
118
+ const config = useRuntimeConfig()
119
+
120
+ window.location.href = config.public.baseAPI + '/auth/slack-login'
121
+ }
122
+
123
+ const loginMs = async () => {
124
+ const config = useRuntimeConfig()
125
+
126
+ window.location.href = config.public.baseAPI + '/auth/ms-login'
127
+ }
128
+
129
+ useWatchTrue(() => fetchMe.status.value.isSuccess, () => {
130
+ me.setTimestamp(Date.now())
131
+ me.set(fetchMe.data.value)
132
+ })
133
+
134
+ useWatchTrue(() => fetchMe.status.value.isError, () => {
135
+ token.value = undefined
136
+ me.setTimestamp(null)
137
+ me.set(null)
138
+ })
139
+
140
+ const hasPermission = (module: UserModule, ...permission: Permission[]) => {
141
+ if (!me.value?.access_level) {
142
+ return false
143
+ }
144
+
145
+ return permission.includes(me.value.access_level[module])
146
+ }
147
+
148
+ const isSuperAdmin = computed(() => {
149
+ return me.value?.access_level?.setting === Permission.SUPER
150
+ })
151
+
152
+ const menusNavbar = computed(() => [
153
+ {
154
+ label: 'Clock-In',
155
+ icon: '/admin/clock-in-logo.png',
156
+ to: routes.clockin.home.to,
157
+ },
158
+ {
159
+ label: 'Timesheet',
160
+ icon: '/admin/timesheet-logo.png',
161
+ to: routes.timesheet.home.to,
162
+ },
163
+ ...(hasPermission(UserModule.PMO, Permission.ADMIN, Permission.USER, Permission.SUPER)
164
+ ? [{
165
+ label: 'PMO',
166
+ icon: '/admin/pmo-logo.png',
167
+ to: routes.pmo.project.projects.to,
168
+ }]
169
+ : []),
170
+ ...(hasPermission(UserModule.CHECKIN, Permission.ADMIN)
171
+ ? [{
172
+ label: 'Clock-In Admin',
173
+ icon: '/admin/clock-in-admin-logo.png',
174
+ to: routes.adminClockin.checkinDashboard.to,
175
+ }]
176
+ : []),
177
+ ...(hasPermission(UserModule.TIMESHEET, Permission.ADMIN)
178
+ ? [{
179
+ label: 'Timesheet Admin',
180
+ icon: '/admin/timesheet-admin-logo.png',
181
+ to: routes.adminTimesheet.summaryReport.to,
182
+ }]
183
+ : []),
184
+
185
+ ...(hasPermission(UserModule.SETTING, Permission.SUPER)
186
+ ? [{
187
+ label: 'Settings',
188
+ icon: '/admin/super-admin-logo.png',
189
+ to: routes.admin.users.to,
190
+ }]
191
+ : []),
192
+ ])
193
+
194
+ return {
195
+ token,
196
+ loginSlack,
197
+ loginMs,
198
+ isAuthenticated,
199
+ me,
200
+ fetchMe,
201
+ updateMe,
202
+ hasPermission,
203
+ isSuperAdmin,
204
+ menusNavbar,
205
+
206
+ }
207
+ }
@@ -0,0 +1,76 @@
1
+ export const NotificationType = {
2
+ INFO: 'INFO',
3
+ SUCCESS: 'SUCCESS',
4
+ WARNING: 'WARNING',
5
+ ERROR: 'ERROR',
6
+ MENTION: 'MENTION',
7
+ COMMENT: 'COMMENT',
8
+ ASSIGNMENT: 'ASSIGNMENT',
9
+ DEADLINE: 'DEADLINE',
10
+ APPROVAL: 'APPROVAL',
11
+ REMINDER: 'REMINDER',
12
+ SYSTEM: 'SYSTEM',
13
+ }
14
+
15
+ export const NotificationPMOActionType = {
16
+ PROJECT_CREATE: 'PROJECT_CREATE',
17
+ PROJECT_STATUS_CHANGED: 'PROJECT_STATUS_CHANGED',
18
+ PROJECT_OWNER_CHANGED: 'PROJECT_OWNER_CHANGED',
19
+ PROJECT_UPDATE: 'PROJECT_UPDATE',
20
+
21
+ COLLABORATOR_ADD: 'COLLABORATOR_ADD',
22
+ COLLABORATOR_UPDATE: 'COLLABORATOR_UPDATE',
23
+ COLLABORATOR_REMOVE: 'COLLABORATOR_REMOVE',
24
+ }
25
+
26
+ export interface INotificationItem {
27
+ id: string
28
+ created_at: string
29
+ updated_at: string
30
+ user_id: string
31
+ type: typeof NotificationType[keyof typeof NotificationType]
32
+ title: string
33
+ message: string
34
+ data?: Record<string, any>
35
+ source_type?: 'PMO_PROJECT' | string
36
+ source_id?: string
37
+ actor_id?: string
38
+ action_type?: typeof NotificationPMOActionType[keyof typeof NotificationPMOActionType] & string
39
+ is_read: boolean
40
+ read_at: string
41
+ channels: string[]
42
+ sent_at: string
43
+ user: IUser
44
+ actor?: IUser
45
+ }
46
+
47
+ export const useNotificationLoader = defineStore('notification', () => {
48
+ const options = useRequestOptions()
49
+
50
+ return usePageLoader<INotificationItem>({
51
+ baseURL: '/notifications?limit=30',
52
+ getBaseRequestOptions: options.auth,
53
+ })
54
+ })
55
+
56
+ export const useNotificationUnreadCount = defineStore('notification-unread-count', () => {
57
+ const options = useRequestOptions()
58
+
59
+ return useObjectLoader<{
60
+ count: number
61
+ }>({
62
+ url: '/notifications/unread-count',
63
+ method: 'get',
64
+ getRequestOptions: options.auth,
65
+ })
66
+ })
67
+
68
+ export const useNotificationMarkAllRead = () => {
69
+ const options = useRequestOptions()
70
+
71
+ return useObjectLoader({
72
+ url: '/notifications/mark-all-read',
73
+ method: 'post',
74
+ getRequestOptions: options.auth,
75
+ })
76
+ }
@@ -1,86 +1,86 @@
1
- import type { AxiosRequestConfig } from 'axios'
2
-
3
- export enum FileAppType {
4
- COMMON = 'COMMON',
5
- PMO = 'PMO',
6
- HR = 'HR',
7
- FINANCE = 'FINANCE',
8
- QUOTATION = 'QUOTATION',
9
- TODO = 'TODO',
10
- }
11
-
12
- export const useRequestOptions = () => {
13
- const config = useRuntimeConfig()
14
-
15
- const mock = (): Omit<AxiosRequestConfig, 'baseURL'> & {
16
- baseURL: string
17
- } => {
18
- return {
19
- baseURL: config.public.baseAPIMock || 'http://localhost:3000/api/mock',
20
- }
21
- }
22
-
23
- const base = (): Omit<AxiosRequestConfig, 'baseURL'> & {
24
- baseURL: string
25
- } => {
26
- return {
27
- baseURL: config.public.baseAPI,
28
- }
29
- }
30
-
31
- const auth = (): Omit<AxiosRequestConfig, 'baseURL'> & {
32
- baseURL: string
33
- } => {
34
- return {
35
- baseURL: config.public.baseAPI,
36
- headers: {
37
- Authorization: `Bearer ${useAuth().token.value}`,
38
- },
39
- }
40
- }
41
-
42
- const file = (fileAppType: FileAppType = FileAppType.COMMON): Omit<AxiosRequestConfig, 'baseURL'> & {
43
- baseURL: string
44
- } => {
45
- return {
46
- baseURL: config.public.baseAPI + '/uploads',
47
- headers: {
48
- Authorization: `Bearer ${useAuth().token.value}`,
49
- },
50
- transformRequest: (data) => {
51
- if (data instanceof FormData) {
52
- data.append('app', fileAppType)
53
- }
54
-
55
- return data
56
- },
57
- }
58
- }
59
-
60
- const filePublic = (fileAppType: FileAppType = FileAppType.COMMON): Omit<AxiosRequestConfig, 'baseURL'> & {
61
- baseURL: string
62
- } => {
63
- return {
64
- baseURL: config.public.baseAPI + '/uploads',
65
- headers: {
66
- Authorization: `Bearer ${useAuth().token.value}`,
67
- },
68
- transformRequest: (data) => {
69
- if (data instanceof FormData) {
70
- data.append('is_public', 'true')
71
- data.append('app', fileAppType)
72
- }
73
-
74
- return data
75
- },
76
- }
77
- }
78
-
79
- return {
80
- base,
81
- mock,
82
- auth,
83
- file,
84
- filePublic,
85
- }
86
- }
1
+ import type { AxiosRequestConfig } from 'axios'
2
+
3
+ export enum FileAppType {
4
+ COMMON = 'COMMON',
5
+ PMO = 'PMO',
6
+ HR = 'HR',
7
+ FINANCE = 'FINANCE',
8
+ QUOTATION = 'QUOTATION',
9
+ TODO = 'TODO',
10
+ }
11
+
12
+ export const useRequestOptions = () => {
13
+ const config = useRuntimeConfig()
14
+
15
+ const mock = (): Omit<AxiosRequestConfig, 'baseURL'> & {
16
+ baseURL: string
17
+ } => {
18
+ return {
19
+ baseURL: config.public.baseAPIMock || 'http://localhost:3000/api/mock',
20
+ }
21
+ }
22
+
23
+ const base = (): Omit<AxiosRequestConfig, 'baseURL'> & {
24
+ baseURL: string
25
+ } => {
26
+ return {
27
+ baseURL: config.public.baseAPI,
28
+ }
29
+ }
30
+
31
+ const auth = (): Omit<AxiosRequestConfig, 'baseURL'> & {
32
+ baseURL: string
33
+ } => {
34
+ return {
35
+ baseURL: config.public.baseAPI,
36
+ headers: {
37
+ Authorization: `Bearer ${useAuth().token.value}`,
38
+ },
39
+ }
40
+ }
41
+
42
+ const file = (fileAppType: FileAppType = FileAppType.COMMON): Omit<AxiosRequestConfig, 'baseURL'> & {
43
+ baseURL: string
44
+ } => {
45
+ return {
46
+ baseURL: config.public.baseAPI + '/uploads',
47
+ headers: {
48
+ Authorization: `Bearer ${useAuth().token.value}`,
49
+ },
50
+ transformRequest: (data) => {
51
+ if (data instanceof FormData) {
52
+ data.append('app', fileAppType)
53
+ }
54
+
55
+ return data
56
+ },
57
+ }
58
+ }
59
+
60
+ const filePublic = (fileAppType: FileAppType = FileAppType.COMMON): Omit<AxiosRequestConfig, 'baseURL'> & {
61
+ baseURL: string
62
+ } => {
63
+ return {
64
+ baseURL: config.public.baseAPI + '/uploads',
65
+ headers: {
66
+ Authorization: `Bearer ${useAuth().token.value}`,
67
+ },
68
+ transformRequest: (data) => {
69
+ if (data instanceof FormData) {
70
+ data.append('is_public', 'true')
71
+ data.append('app', fileAppType)
72
+ }
73
+
74
+ return data
75
+ },
76
+ }
77
+ }
78
+
79
+ return {
80
+ base,
81
+ mock,
82
+ auth,
83
+ file,
84
+ filePublic,
85
+ }
86
+ }