@open-mercato/core 0.4.6-develop-f7d3079656 → 0.4.6-develop-0861f05ea9
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/modules/currencies/backend/exchange-rates/[id]/page.js +17 -154
- package/dist/modules/currencies/backend/exchange-rates/[id]/page.js.map +3 -3
- package/dist/modules/currencies/backend/exchange-rates/create/page.js +14 -152
- package/dist/modules/currencies/backend/exchange-rates/create/page.js.map +2 -2
- package/dist/modules/currencies/lib/exchangeRateFormConfig.js +167 -0
- package/dist/modules/currencies/lib/exchangeRateFormConfig.js.map +7 -0
- package/dist/modules/customers/api/dashboard/widgets/utils.js +1 -34
- package/dist/modules/customers/api/dashboard/widgets/utils.js.map +2 -2
- package/dist/modules/customers/commands/activities.js +3 -8
- package/dist/modules/customers/commands/activities.js.map +2 -2
- package/dist/modules/customers/commands/comments.js +2 -8
- package/dist/modules/customers/commands/comments.js.map +2 -2
- package/dist/modules/dashboards/lib/widgetScope.js +38 -0
- package/dist/modules/dashboards/lib/widgetScope.js.map +7 -0
- package/dist/modules/entities/lib/makeActivityRoute.js +265 -0
- package/dist/modules/entities/lib/makeActivityRoute.js.map +7 -0
- package/dist/modules/resources/api/activities.js +24 -232
- package/dist/modules/resources/api/activities.js.map +2 -2
- package/dist/modules/resources/commands/activities.js +3 -8
- package/dist/modules/resources/commands/activities.js.map +2 -2
- package/dist/modules/resources/commands/comments.js +2 -8
- package/dist/modules/resources/commands/comments.js.map +2 -2
- package/dist/modules/sales/api/dashboard/widgets/new-orders/route.js +27 -182
- package/dist/modules/sales/api/dashboard/widgets/new-orders/route.js.map +2 -2
- package/dist/modules/sales/api/dashboard/widgets/new-quotes/route.js +28 -183
- package/dist/modules/sales/api/dashboard/widgets/new-quotes/route.js.map +2 -2
- package/dist/modules/sales/api/order-line-statuses/route.js +15 -194
- package/dist/modules/sales/api/order-line-statuses/route.js.map +2 -2
- package/dist/modules/sales/api/order-lines/route.js +15 -281
- package/dist/modules/sales/api/order-lines/route.js.map +2 -2
- package/dist/modules/sales/api/order-statuses/route.js +15 -194
- package/dist/modules/sales/api/order-statuses/route.js.map +2 -2
- package/dist/modules/sales/api/payment-statuses/route.js +15 -194
- package/dist/modules/sales/api/payment-statuses/route.js.map +2 -2
- package/dist/modules/sales/api/quote-lines/route.js +15 -279
- package/dist/modules/sales/api/quote-lines/route.js.map +2 -2
- package/dist/modules/sales/api/shipment-statuses/route.js +15 -194
- package/dist/modules/sales/api/shipment-statuses/route.js.map +2 -2
- package/dist/modules/sales/components/PaymentMethodsSettings.js +3 -84
- package/dist/modules/sales/components/PaymentMethodsSettings.js.map +2 -2
- package/dist/modules/sales/components/ProviderFieldInput.js +86 -0
- package/dist/modules/sales/components/ProviderFieldInput.js.map +7 -0
- package/dist/modules/sales/components/ShippingMethodsSettings.js +3 -82
- package/dist/modules/sales/components/ShippingMethodsSettings.js.map +2 -2
- package/dist/modules/sales/lib/makeSalesLineRoute.js +308 -0
- package/dist/modules/sales/lib/makeSalesLineRoute.js.map +7 -0
- package/dist/modules/sales/lib/makeStatusDictionaryRoute.js +206 -0
- package/dist/modules/sales/lib/makeStatusDictionaryRoute.js.map +7 -0
- package/dist/modules/sales/widgets/dashboard/makeDashboardWidgetRoute.js +178 -0
- package/dist/modules/sales/widgets/dashboard/makeDashboardWidgetRoute.js.map +7 -0
- package/dist/modules/sales/widgets/dashboard/new-orders/widget.client.js +1 -39
- package/dist/modules/sales/widgets/dashboard/new-orders/widget.client.js.map +2 -2
- package/dist/modules/sales/widgets/dashboard/new-quotes/widget.client.js +1 -39
- package/dist/modules/sales/widgets/dashboard/new-quotes/widget.client.js.map +2 -2
- package/dist/modules/sales/widgets/dashboard/shared.js +46 -0
- package/dist/modules/sales/widgets/dashboard/shared.js.map +7 -0
- package/dist/modules/staff/api/activities.js +24 -232
- package/dist/modules/staff/api/activities.js.map +2 -2
- package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js +14 -34
- package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/my-leave-requests/[id]/page.js +15 -34
- package/dist/modules/staff/backend/staff/my-leave-requests/[id]/page.js.map +2 -2
- package/dist/modules/staff/commands/activities.js +3 -8
- package/dist/modules/staff/commands/activities.js.map +2 -2
- package/dist/modules/staff/commands/comments.js +2 -8
- package/dist/modules/staff/commands/comments.js.map +2 -2
- package/dist/modules/staff/lib/leaveRequestHelpers.js +41 -0
- package/dist/modules/staff/lib/leaveRequestHelpers.js.map +7 -0
- package/package.json +2 -2
- package/src/modules/currencies/backend/exchange-rates/[id]/page.tsx +20 -180
- package/src/modules/currencies/backend/exchange-rates/create/page.tsx +16 -175
- package/src/modules/currencies/lib/exchangeRateFormConfig.ts +200 -0
- package/src/modules/customers/api/dashboard/widgets/utils.ts +1 -53
- package/src/modules/customers/commands/activities.ts +2 -8
- package/src/modules/customers/commands/comments.ts +2 -8
- package/src/modules/dashboards/i18n/de.json +3 -0
- package/src/modules/dashboards/i18n/en.json +3 -0
- package/src/modules/dashboards/i18n/es.json +3 -0
- package/src/modules/dashboards/i18n/pl.json +3 -0
- package/src/modules/dashboards/lib/widgetScope.ts +53 -0
- package/src/modules/entities/lib/makeActivityRoute.ts +327 -0
- package/src/modules/resources/api/activities.ts +25 -269
- package/src/modules/resources/commands/activities.ts +2 -7
- package/src/modules/resources/commands/comments.ts +2 -8
- package/src/modules/sales/api/dashboard/widgets/new-orders/route.ts +29 -244
- package/src/modules/sales/api/dashboard/widgets/new-quotes/route.ts +30 -245
- package/src/modules/sales/api/order-line-statuses/route.ts +16 -209
- package/src/modules/sales/api/order-lines/route.ts +16 -300
- package/src/modules/sales/api/order-statuses/route.ts +16 -209
- package/src/modules/sales/api/payment-statuses/route.ts +16 -209
- package/src/modules/sales/api/quote-lines/route.ts +16 -298
- package/src/modules/sales/api/shipment-statuses/route.ts +16 -209
- package/src/modules/sales/components/PaymentMethodsSettings.tsx +3 -88
- package/src/modules/sales/components/ProviderFieldInput.tsx +85 -0
- package/src/modules/sales/components/ShippingMethodsSettings.tsx +3 -86
- package/src/modules/sales/lib/makeSalesLineRoute.ts +345 -0
- package/src/modules/sales/lib/makeStatusDictionaryRoute.ts +229 -0
- package/src/modules/sales/widgets/dashboard/makeDashboardWidgetRoute.ts +247 -0
- package/src/modules/sales/widgets/dashboard/new-orders/widget.client.tsx +7 -50
- package/src/modules/sales/widgets/dashboard/new-quotes/widget.client.tsx +7 -49
- package/src/modules/sales/widgets/dashboard/shared.ts +44 -0
- package/src/modules/staff/api/activities.ts +25 -269
- package/src/modules/staff/backend/staff/leave-requests/[id]/page.tsx +15 -69
- package/src/modules/staff/backend/staff/my-leave-requests/[id]/page.tsx +16 -65
- package/src/modules/staff/commands/activities.ts +2 -7
- package/src/modules/staff/commands/comments.ts +2 -8
- package/src/modules/staff/lib/leaveRequestHelpers.ts +78 -0
|
@@ -1,278 +1,34 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
3
|
-
import { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'
|
|
4
|
-
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
5
|
-
import { resolveCrudRecordId, parseScopedCommandInput } from '@open-mercato/shared/lib/api/scoped'
|
|
6
|
-
import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
1
|
+
import { makeActivityRoute } from '@open-mercato/core/modules/entities/lib/makeActivityRoute'
|
|
7
2
|
import { StaffTeamMemberActivity } from '../data/entities'
|
|
8
3
|
import {
|
|
9
4
|
staffTeamMemberActivityCreateSchema,
|
|
10
5
|
staffTeamMemberActivityUpdateSchema,
|
|
11
6
|
} from '../data/validators'
|
|
12
|
-
import { User } from '@open-mercato/core/modules/auth/data/entities'
|
|
13
7
|
import { E } from '#generated/entities.ids.generated'
|
|
14
|
-
import { createStaffCrudOpenApi
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
DELETE: { requireAuth: true, requireFeatures: ['staff.manage_team'] },
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export const metadata = routeMetadata
|
|
36
|
-
|
|
37
|
-
const crud = makeCrudRoute({
|
|
38
|
-
metadata: routeMetadata,
|
|
39
|
-
orm: {
|
|
40
|
-
entity: StaffTeamMemberActivity,
|
|
41
|
-
idField: 'id',
|
|
42
|
-
orgField: 'organizationId',
|
|
43
|
-
tenantField: 'tenantId',
|
|
44
|
-
},
|
|
45
|
-
indexer: {
|
|
46
|
-
entityType: E.staff.staff_team_member_activity,
|
|
47
|
-
},
|
|
48
|
-
list: {
|
|
49
|
-
schema: listSchema,
|
|
50
|
-
entityId: E.staff.staff_team_member_activity,
|
|
51
|
-
fields: [
|
|
52
|
-
'id',
|
|
53
|
-
'member_id',
|
|
54
|
-
'activity_type',
|
|
55
|
-
'subject',
|
|
56
|
-
'body',
|
|
57
|
-
'occurred_at',
|
|
58
|
-
'author_user_id',
|
|
59
|
-
'appearance_icon',
|
|
60
|
-
'appearance_color',
|
|
61
|
-
'organization_id',
|
|
62
|
-
'tenant_id',
|
|
63
|
-
'created_at',
|
|
64
|
-
'updated_at',
|
|
65
|
-
],
|
|
66
|
-
decorateCustomFields: {
|
|
67
|
-
entityIds: E.staff.staff_team_member_activity,
|
|
68
|
-
},
|
|
69
|
-
sortFieldMap: {
|
|
70
|
-
occurredAt: 'occurred_at',
|
|
71
|
-
createdAt: 'created_at',
|
|
72
|
-
updatedAt: 'updated_at',
|
|
73
|
-
},
|
|
74
|
-
buildFilters: async (query) => {
|
|
75
|
-
const filters: Record<string, unknown> = {}
|
|
76
|
-
if (query.entityId) filters.member_id = { $eq: query.entityId }
|
|
77
|
-
return filters
|
|
78
|
-
},
|
|
79
|
-
transformItem: (item: Record<string, unknown>) => {
|
|
80
|
-
const record = (item ?? {}) as Record<string, unknown>
|
|
81
|
-
const toIsoString = (value: unknown): string | null => {
|
|
82
|
-
if (value == null) return null
|
|
83
|
-
if (value instanceof Date) return value.toISOString()
|
|
84
|
-
if (typeof value === 'string') {
|
|
85
|
-
const trimmed = value.trim()
|
|
86
|
-
if (!trimmed.length) return null
|
|
87
|
-
const date = new Date(trimmed)
|
|
88
|
-
return Number.isNaN(date.getTime()) ? trimmed : date.toISOString()
|
|
89
|
-
}
|
|
90
|
-
return null
|
|
91
|
-
}
|
|
92
|
-
const readString = (value: unknown): string | null => (typeof value === 'string' ? value : null)
|
|
93
|
-
const idValue = readString(record.id) ?? (record.id != null ? String(record.id) : '')
|
|
94
|
-
const memberId = readString(record['member_id']) ?? readString(record['memberId']) ?? null
|
|
95
|
-
const activityType =
|
|
96
|
-
readString(record['activity_type']) ??
|
|
97
|
-
readString(record['activityType']) ??
|
|
98
|
-
''
|
|
99
|
-
const subject =
|
|
100
|
-
readString(record.subject) ??
|
|
101
|
-
(record.subject == null ? null : String(record.subject))
|
|
102
|
-
const body =
|
|
103
|
-
readString(record.body) ??
|
|
104
|
-
(record.body == null ? null : String(record.body))
|
|
105
|
-
const authorUserId =
|
|
106
|
-
readString(record['author_user_id']) ?? readString(record['authorUserId']) ?? null
|
|
107
|
-
const appearanceIconRaw =
|
|
108
|
-
readString(record['appearance_icon']) ?? readString(record['appearanceIcon'])
|
|
109
|
-
const appearanceColorRaw =
|
|
110
|
-
readString(record['appearance_color']) ?? readString(record['appearanceColor'])
|
|
111
|
-
const organizationId =
|
|
112
|
-
readString(record['organization_id']) ?? readString(record['organizationId'])
|
|
113
|
-
const tenantId =
|
|
114
|
-
readString(record['tenant_id']) ?? readString(record['tenantId'])
|
|
115
|
-
const output: Record<string, unknown> = {
|
|
116
|
-
id: idValue,
|
|
117
|
-
entityId: memberId,
|
|
118
|
-
memberId,
|
|
119
|
-
activityType,
|
|
120
|
-
subject,
|
|
121
|
-
body,
|
|
122
|
-
occurredAt: toIsoString(record['occurred_at'] ?? record['occurredAt']),
|
|
123
|
-
createdAt: toIsoString(record['created_at'] ?? record['createdAt']),
|
|
124
|
-
authorUserId,
|
|
125
|
-
organizationId,
|
|
126
|
-
tenantId,
|
|
127
|
-
appearanceIcon: appearanceIconRaw && appearanceIconRaw.trim().length ? appearanceIconRaw : null,
|
|
128
|
-
appearanceColor: appearanceColorRaw && appearanceColorRaw.trim().length ? appearanceColorRaw : null,
|
|
129
|
-
customFields: Array.isArray(record.customFields) ? record.customFields : undefined,
|
|
130
|
-
customValues: record.customValues ?? undefined,
|
|
131
|
-
}
|
|
132
|
-
for (const [key, value] of Object.entries(record)) {
|
|
133
|
-
if (key.startsWith('cf_') || key.startsWith('cf:')) {
|
|
134
|
-
output[key] = value
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
return output
|
|
138
|
-
},
|
|
139
|
-
},
|
|
140
|
-
actions: {
|
|
141
|
-
create: {
|
|
142
|
-
commandId: 'staff.team-member-activities.create',
|
|
143
|
-
schema: rawBodySchema,
|
|
144
|
-
mapInput: async ({ raw, ctx }) => {
|
|
145
|
-
const { translate } = await resolveTranslations()
|
|
146
|
-
return parseScopedCommandInput(staffTeamMemberActivityCreateSchema, raw ?? {}, ctx, translate)
|
|
147
|
-
},
|
|
148
|
-
response: ({ result }) => ({
|
|
149
|
-
id: result?.activityId ?? result?.id ?? null,
|
|
150
|
-
authorUserId: result?.authorUserId ?? null,
|
|
151
|
-
}),
|
|
152
|
-
status: 201,
|
|
153
|
-
},
|
|
154
|
-
update: {
|
|
155
|
-
commandId: 'staff.team-member-activities.update',
|
|
156
|
-
schema: rawBodySchema,
|
|
157
|
-
mapInput: async ({ raw, ctx }) => {
|
|
158
|
-
const { translate } = await resolveTranslations()
|
|
159
|
-
return parseScopedCommandInput(staffTeamMemberActivityUpdateSchema, raw ?? {}, ctx, translate)
|
|
160
|
-
},
|
|
161
|
-
response: () => ({ ok: true }),
|
|
162
|
-
},
|
|
163
|
-
delete: {
|
|
164
|
-
commandId: 'staff.team-member-activities.delete',
|
|
165
|
-
schema: rawBodySchema,
|
|
166
|
-
mapInput: async ({ parsed, ctx }) => {
|
|
167
|
-
const { translate } = await resolveTranslations()
|
|
168
|
-
const id = resolveCrudRecordId(parsed, ctx, translate)
|
|
169
|
-
return { id }
|
|
170
|
-
},
|
|
171
|
-
response: () => ({ ok: true }),
|
|
172
|
-
},
|
|
173
|
-
},
|
|
174
|
-
hooks: {
|
|
175
|
-
afterList: async (payload, ctx) => {
|
|
176
|
-
const items = Array.isArray(payload.items) ? payload.items : []
|
|
177
|
-
if (!items.length) return
|
|
178
|
-
const userIds = new Set<string>()
|
|
179
|
-
items.forEach((item: unknown) => {
|
|
180
|
-
if (!item || typeof item !== 'object') return
|
|
181
|
-
const record = item as Record<string, unknown>
|
|
182
|
-
const userId =
|
|
183
|
-
typeof record.author_user_id === 'string'
|
|
184
|
-
? record.author_user_id
|
|
185
|
-
: typeof record.authorUserId === 'string'
|
|
186
|
-
? record.authorUserId
|
|
187
|
-
: null
|
|
188
|
-
if (userId) userIds.add(userId)
|
|
189
|
-
})
|
|
190
|
-
if (!userIds.size) return
|
|
191
|
-
try {
|
|
192
|
-
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
193
|
-
const users = await findWithDecryption(
|
|
194
|
-
em,
|
|
195
|
-
User,
|
|
196
|
-
{ id: { $in: Array.from(userIds) } },
|
|
197
|
-
undefined,
|
|
198
|
-
{ tenantId: ctx.auth?.tenantId ?? null, organizationId: ctx.selectedOrganizationId ?? null },
|
|
199
|
-
)
|
|
200
|
-
const map = new Map<string, { name: string | null; email: string | null }>()
|
|
201
|
-
users.forEach((user) => {
|
|
202
|
-
const name = typeof user.name === 'string' && user.name.trim().length
|
|
203
|
-
? user.name.trim()
|
|
204
|
-
: null
|
|
205
|
-
map.set(user.id, { name, email: user.email ?? null })
|
|
206
|
-
})
|
|
207
|
-
items.forEach((item: unknown) => {
|
|
208
|
-
if (!item || typeof item !== 'object') return
|
|
209
|
-
const record = item as Record<string, unknown>
|
|
210
|
-
const userId =
|
|
211
|
-
typeof record.author_user_id === 'string'
|
|
212
|
-
? record.author_user_id
|
|
213
|
-
: typeof record.authorUserId === 'string'
|
|
214
|
-
? record.authorUserId
|
|
215
|
-
: null
|
|
216
|
-
if (!userId) return
|
|
217
|
-
const meta = map.get(userId)
|
|
218
|
-
if (!meta) return
|
|
219
|
-
record.authorName = meta.name
|
|
220
|
-
record.authorEmail = meta.email
|
|
221
|
-
if (!('author_name' in record)) record.author_name = meta.name
|
|
222
|
-
if (!('author_email' in record)) record.author_email = meta.email
|
|
223
|
-
})
|
|
224
|
-
} catch (err) {
|
|
225
|
-
console.warn('[staff.activities] failed to enrich author metadata', err)
|
|
226
|
-
}
|
|
227
|
-
},
|
|
8
|
+
import { createStaffCrudOpenApi } from './openapi'
|
|
9
|
+
|
|
10
|
+
const route = makeActivityRoute({
|
|
11
|
+
entity: StaffTeamMemberActivity,
|
|
12
|
+
entityId: E.staff.staff_team_member_activity,
|
|
13
|
+
parentFkColumn: 'member_id',
|
|
14
|
+
parentFkParam: 'memberId',
|
|
15
|
+
features: { view: 'staff.view', manage: 'staff.manage_team' },
|
|
16
|
+
createSchema: staffTeamMemberActivityCreateSchema,
|
|
17
|
+
updateSchema: staffTeamMemberActivityUpdateSchema,
|
|
18
|
+
commandPrefix: 'staff.team-member-activities',
|
|
19
|
+
logPrefix: '[staff.activities]',
|
|
20
|
+
openApiFactory: createStaffCrudOpenApi,
|
|
21
|
+
openApi: {
|
|
22
|
+
resourceName: 'TeamMemberActivity',
|
|
23
|
+
createDescription: 'Adds an activity to a team member timeline.',
|
|
24
|
+
updateDescription: 'Updates a team member activity.',
|
|
25
|
+
deleteDescription: 'Deletes a team member activity.',
|
|
228
26
|
},
|
|
229
27
|
})
|
|
230
28
|
|
|
231
|
-
export const
|
|
232
|
-
export const
|
|
233
|
-
export const
|
|
234
|
-
export const
|
|
235
|
-
|
|
236
|
-
const
|
|
237
|
-
.object({
|
|
238
|
-
id: z.string().uuid(),
|
|
239
|
-
member_id: z.string().uuid().nullable().optional(),
|
|
240
|
-
activity_type: z.string().nullable().optional(),
|
|
241
|
-
subject: z.string().nullable().optional(),
|
|
242
|
-
body: z.string().nullable().optional(),
|
|
243
|
-
occurred_at: z.string().nullable().optional(),
|
|
244
|
-
author_user_id: z.string().uuid().nullable(),
|
|
245
|
-
appearance_icon: z.string().nullable().optional(),
|
|
246
|
-
appearance_color: z.string().nullable().optional(),
|
|
247
|
-
organization_id: z.string().uuid().nullable().optional(),
|
|
248
|
-
tenant_id: z.string().uuid().nullable().optional(),
|
|
249
|
-
created_at: z.string().nullable(),
|
|
250
|
-
updated_at: z.string().nullable().optional(),
|
|
251
|
-
})
|
|
252
|
-
.passthrough()
|
|
253
|
-
|
|
254
|
-
const activityCreateResponseSchema = z.object({
|
|
255
|
-
id: z.string().uuid().nullable(),
|
|
256
|
-
authorUserId: z.string().uuid().nullable(),
|
|
257
|
-
})
|
|
258
|
-
|
|
259
|
-
export const openApi = createStaffCrudOpenApi({
|
|
260
|
-
resourceName: 'TeamMemberActivity',
|
|
261
|
-
querySchema: listSchema,
|
|
262
|
-
listResponseSchema: createPagedListResponseSchema(activityListItemSchema),
|
|
263
|
-
create: {
|
|
264
|
-
schema: staffTeamMemberActivityCreateSchema,
|
|
265
|
-
responseSchema: activityCreateResponseSchema,
|
|
266
|
-
description: 'Adds an activity to a team member timeline.',
|
|
267
|
-
},
|
|
268
|
-
update: {
|
|
269
|
-
schema: staffTeamMemberActivityUpdateSchema,
|
|
270
|
-
responseSchema: defaultOkResponseSchema,
|
|
271
|
-
description: 'Updates a team member activity.',
|
|
272
|
-
},
|
|
273
|
-
del: {
|
|
274
|
-
schema: z.object({ id: z.string().uuid() }),
|
|
275
|
-
responseSchema: defaultOkResponseSchema,
|
|
276
|
-
description: 'Deletes a team member activity.',
|
|
277
|
-
},
|
|
278
|
-
})
|
|
29
|
+
export const metadata = route.metadata
|
|
30
|
+
export const openApi = route.openApi
|
|
31
|
+
export const GET = route.GET
|
|
32
|
+
export const POST = route.POST
|
|
33
|
+
export const PUT = route.PUT
|
|
34
|
+
export const DELETE = route.DELETE
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
4
|
import { useRouter } from 'next/navigation'
|
|
5
|
-
import { Send } from 'lucide-react'
|
|
6
5
|
import { Page, PageBody } from '@open-mercato/ui/backend/Page'
|
|
7
6
|
import { Badge } from '@open-mercato/ui/primitives/badge'
|
|
8
7
|
import { Button } from '@open-mercato/ui/primitives/button'
|
|
@@ -14,32 +13,7 @@ import { updateCrud } from '@open-mercato/ui/backend/utils/crud'
|
|
|
14
13
|
import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
15
14
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
16
15
|
import { LeaveRequestForm, buildLeaveRequestPayload, type LeaveRequestFormValues } from '@open-mercato/core/modules/staff/components/LeaveRequestForm'
|
|
17
|
-
|
|
18
|
-
type LeaveRequestRecord = {
|
|
19
|
-
id: string
|
|
20
|
-
member?: { id?: string; displayName?: string }
|
|
21
|
-
memberId?: string | null
|
|
22
|
-
member_id?: string | null
|
|
23
|
-
startDate?: string | null
|
|
24
|
-
start_date?: string | null
|
|
25
|
-
endDate?: string | null
|
|
26
|
-
end_date?: string | null
|
|
27
|
-
timezone?: string | null
|
|
28
|
-
status?: 'pending' | 'approved' | 'rejected'
|
|
29
|
-
unavailabilityReasonEntryId?: string | null
|
|
30
|
-
unavailability_reason_entry_id?: string | null
|
|
31
|
-
unavailabilityReasonValue?: string | null
|
|
32
|
-
unavailability_reason_value?: string | null
|
|
33
|
-
note?: string | null
|
|
34
|
-
decisionComment?: string | null
|
|
35
|
-
decision_comment?: string | null
|
|
36
|
-
decidedAt?: string | null
|
|
37
|
-
decided_at?: string | null
|
|
38
|
-
} & Record<string, unknown>
|
|
39
|
-
|
|
40
|
-
type LeaveRequestsResponse = {
|
|
41
|
-
items?: LeaveRequestRecord[]
|
|
42
|
-
}
|
|
16
|
+
import { type LeaveRequestRecord, type LeaveRequestsResponse, type NormalizedLeaveRequest, normalizeLeaveRequest, resolveStatusVariant, formatDateLabel, formatDateRange } from '../../../../lib/leaveRequestHelpers'
|
|
43
17
|
|
|
44
18
|
export default function StaffLeaveRequestDetailPage({ params }: { params?: { id?: string } }) {
|
|
45
19
|
const id = params?.id
|
|
@@ -47,7 +21,7 @@ export default function StaffLeaveRequestDetailPage({ params }: { params?: { id?
|
|
|
47
21
|
const router = useRouter()
|
|
48
22
|
const [isLoading, setIsLoading] = React.useState(true)
|
|
49
23
|
const [error, setError] = React.useState<string | null>(null)
|
|
50
|
-
const [record, setRecord] = React.useState<
|
|
24
|
+
const [record, setRecord] = React.useState<NormalizedLeaveRequest | null>(null)
|
|
51
25
|
const [decisionComment, setDecisionComment] = React.useState('')
|
|
52
26
|
|
|
53
27
|
React.useEffect(() => {
|
|
@@ -71,14 +45,9 @@ export default function StaffLeaveRequestDetailPage({ params }: { params?: { id?
|
|
|
71
45
|
const entry = Array.isArray(payload.items) ? payload.items[0] : null
|
|
72
46
|
if (!entry) throw new Error(t('staff.leaveRequests.errors.notFound', 'Leave request not found.'))
|
|
73
47
|
if (!cancelled) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
? entry.decisionComment
|
|
78
|
-
: typeof entry.decision_comment === 'string'
|
|
79
|
-
? entry.decision_comment
|
|
80
|
-
: ''
|
|
81
|
-
)
|
|
48
|
+
const normalized = normalizeLeaveRequest(entry)
|
|
49
|
+
setRecord(normalized)
|
|
50
|
+
setDecisionComment(normalized.decisionComment ?? '')
|
|
82
51
|
}
|
|
83
52
|
} catch (err) {
|
|
84
53
|
if (!cancelled) {
|
|
@@ -96,19 +65,16 @@ export default function StaffLeaveRequestDetailPage({ params }: { params?: { id?
|
|
|
96
65
|
|
|
97
66
|
const status = record?.status ?? 'pending'
|
|
98
67
|
const memberLabel = record?.member?.displayName ?? null
|
|
99
|
-
const dateSummary = formatDateRange(
|
|
100
|
-
record?.startDate ?? record?.start_date ?? null,
|
|
101
|
-
record?.endDate ?? record?.end_date ?? null,
|
|
102
|
-
)
|
|
68
|
+
const dateSummary = formatDateRange(record?.startDate, record?.endDate)
|
|
103
69
|
const initialValues = React.useMemo<LeaveRequestFormValues>(() => ({
|
|
104
70
|
id: record?.id,
|
|
105
|
-
memberId: record?.memberId ??
|
|
71
|
+
memberId: record?.memberId ?? null,
|
|
106
72
|
memberLabel,
|
|
107
|
-
startDate: record?.startDate ??
|
|
108
|
-
endDate: record?.endDate ??
|
|
73
|
+
startDate: record?.startDate ?? null,
|
|
74
|
+
endDate: record?.endDate ?? null,
|
|
109
75
|
timezone: record?.timezone ?? null,
|
|
110
|
-
unavailabilityReasonEntryId: record?.unavailabilityReasonEntryId ??
|
|
111
|
-
unavailabilityReasonValue: record?.unavailabilityReasonValue ??
|
|
76
|
+
unavailabilityReasonEntryId: record?.unavailabilityReasonEntryId ?? null,
|
|
77
|
+
unavailabilityReasonValue: record?.unavailabilityReasonValue ?? null,
|
|
112
78
|
note: record?.note ?? null,
|
|
113
79
|
}), [record, memberLabel])
|
|
114
80
|
|
|
@@ -167,9 +133,9 @@ const handleSubmit = React.useCallback(async (values: LeaveRequestFormValues) =>
|
|
|
167
133
|
<Badge variant={resolveStatusVariant(status)}>
|
|
168
134
|
{t(`staff.leaveRequests.status.${status}`, status)}
|
|
169
135
|
</Badge>
|
|
170
|
-
{record.
|
|
136
|
+
{record.decidedAt ? (
|
|
171
137
|
<span className="text-xs text-muted-foreground">
|
|
172
|
-
{t('staff.leaveRequests.decision.at', 'Decision at')} {formatDateLabel(record.decidedAt
|
|
138
|
+
{t('staff.leaveRequests.decision.at', 'Decision at')} {formatDateLabel(record.decidedAt)}
|
|
173
139
|
</span>
|
|
174
140
|
) : null}
|
|
175
141
|
</div>
|
|
@@ -201,10 +167,10 @@ const handleSubmit = React.useCallback(async (values: LeaveRequestFormValues) =>
|
|
|
201
167
|
</Button>
|
|
202
168
|
</div>
|
|
203
169
|
</div>
|
|
204
|
-
) : record.decisionComment
|
|
170
|
+
) : record.decisionComment ? (
|
|
205
171
|
<div className="mb-6 rounded-lg border bg-card p-4 text-sm text-muted-foreground">
|
|
206
172
|
<div className="mb-1 font-medium text-foreground">{t('staff.leaveRequests.decision.comment', 'Decision comment')}</div>
|
|
207
|
-
<p>{record.decisionComment
|
|
173
|
+
<p>{record.decisionComment}</p>
|
|
208
174
|
</div>
|
|
209
175
|
) : null}
|
|
210
176
|
|
|
@@ -252,23 +218,3 @@ const handleSubmit = React.useCallback(async (values: LeaveRequestFormValues) =>
|
|
|
252
218
|
</Page>
|
|
253
219
|
)
|
|
254
220
|
}
|
|
255
|
-
|
|
256
|
-
function resolveStatusVariant(status: 'pending' | 'approved' | 'rejected') {
|
|
257
|
-
if (status === 'approved') return 'default'
|
|
258
|
-
if (status === 'rejected') return 'destructive'
|
|
259
|
-
return 'secondary'
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
function formatDateLabel(value?: string | null): string {
|
|
263
|
-
if (!value) return ''
|
|
264
|
-
const date = new Date(value)
|
|
265
|
-
if (Number.isNaN(date.getTime())) return value
|
|
266
|
-
return date.toLocaleDateString()
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
function formatDateRange(start?: string | null, end?: string | null): string {
|
|
270
|
-
const startLabel = formatDateLabel(start)
|
|
271
|
-
const endLabel = formatDateLabel(end)
|
|
272
|
-
if (startLabel && endLabel) return `${startLabel} -> ${endLabel}`
|
|
273
|
-
return startLabel || endLabel || '-'
|
|
274
|
-
}
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
4
|
import { useRouter } from 'next/navigation'
|
|
5
|
-
import { Send } from 'lucide-react'
|
|
6
5
|
import { Page, PageBody } from '@open-mercato/ui/backend/Page'
|
|
7
6
|
import { Badge } from '@open-mercato/ui/primitives/badge'
|
|
8
7
|
import { Button } from '@open-mercato/ui/primitives/button'
|
|
@@ -13,32 +12,7 @@ import { updateCrud } from '@open-mercato/ui/backend/utils/crud'
|
|
|
13
12
|
import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
14
13
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
15
14
|
import { LeaveRequestForm, buildLeaveRequestPayload, type LeaveRequestFormValues } from '@open-mercato/core/modules/staff/components/LeaveRequestForm'
|
|
16
|
-
|
|
17
|
-
type LeaveRequestRecord = {
|
|
18
|
-
id: string
|
|
19
|
-
member?: { id?: string; displayName?: string }
|
|
20
|
-
memberId?: string | null
|
|
21
|
-
member_id?: string | null
|
|
22
|
-
startDate?: string | null
|
|
23
|
-
start_date?: string | null
|
|
24
|
-
endDate?: string | null
|
|
25
|
-
end_date?: string | null
|
|
26
|
-
timezone?: string | null
|
|
27
|
-
status?: 'pending' | 'approved' | 'rejected'
|
|
28
|
-
unavailabilityReasonEntryId?: string | null
|
|
29
|
-
unavailability_reason_entry_id?: string | null
|
|
30
|
-
unavailabilityReasonValue?: string | null
|
|
31
|
-
unavailability_reason_value?: string | null
|
|
32
|
-
note?: string | null
|
|
33
|
-
decisionComment?: string | null
|
|
34
|
-
decision_comment?: string | null
|
|
35
|
-
decidedAt?: string | null
|
|
36
|
-
decided_at?: string | null
|
|
37
|
-
} & Record<string, unknown>
|
|
38
|
-
|
|
39
|
-
type LeaveRequestsResponse = {
|
|
40
|
-
items?: LeaveRequestRecord[]
|
|
41
|
-
}
|
|
15
|
+
import { type LeaveRequestsResponse, type NormalizedLeaveRequest, normalizeLeaveRequest, resolveStatusVariant, formatDateLabel, formatDateRange } from '../../../../lib/leaveRequestHelpers'
|
|
42
16
|
|
|
43
17
|
export default function StaffMyLeaveRequestDetailPage({ params }: { params?: { id?: string } }) {
|
|
44
18
|
const id = params?.id
|
|
@@ -46,7 +20,7 @@ export default function StaffMyLeaveRequestDetailPage({ params }: { params?: { i
|
|
|
46
20
|
const router = useRouter()
|
|
47
21
|
const [isLoading, setIsLoading] = React.useState(true)
|
|
48
22
|
const [error, setError] = React.useState<string | null>(null)
|
|
49
|
-
const [record, setRecord] = React.useState<
|
|
23
|
+
const [record, setRecord] = React.useState<NormalizedLeaveRequest | null>(null)
|
|
50
24
|
|
|
51
25
|
React.useEffect(() => {
|
|
52
26
|
if (!id) {
|
|
@@ -69,7 +43,7 @@ export default function StaffMyLeaveRequestDetailPage({ params }: { params?: { i
|
|
|
69
43
|
const entry = Array.isArray(payload.items) ? payload.items[0] : null
|
|
70
44
|
if (!entry) throw new Error(t('staff.leaveRequests.errors.notFound', 'Leave request not found.'))
|
|
71
45
|
if (!cancelled) {
|
|
72
|
-
setRecord(entry)
|
|
46
|
+
setRecord(normalizeLeaveRequest(entry))
|
|
73
47
|
}
|
|
74
48
|
} catch (err) {
|
|
75
49
|
if (!cancelled) {
|
|
@@ -89,19 +63,16 @@ export default function StaffMyLeaveRequestDetailPage({ params }: { params?: { i
|
|
|
89
63
|
const memberLabel = record?.member?.displayName ?? null
|
|
90
64
|
const initialValues = React.useMemo<LeaveRequestFormValues>(() => ({
|
|
91
65
|
id: record?.id,
|
|
92
|
-
memberId: record?.memberId ??
|
|
66
|
+
memberId: record?.memberId ?? null,
|
|
93
67
|
memberLabel,
|
|
94
|
-
startDate: record?.startDate ??
|
|
95
|
-
endDate: record?.endDate ??
|
|
68
|
+
startDate: record?.startDate ?? null,
|
|
69
|
+
endDate: record?.endDate ?? null,
|
|
96
70
|
timezone: record?.timezone ?? null,
|
|
97
|
-
unavailabilityReasonEntryId: record?.unavailabilityReasonEntryId ??
|
|
98
|
-
unavailabilityReasonValue: record?.unavailabilityReasonValue ??
|
|
71
|
+
unavailabilityReasonEntryId: record?.unavailabilityReasonEntryId ?? null,
|
|
72
|
+
unavailabilityReasonValue: record?.unavailabilityReasonValue ?? null,
|
|
99
73
|
note: record?.note ?? null,
|
|
100
74
|
}), [record, memberLabel])
|
|
101
|
-
const dateSummary = formatDateRange(
|
|
102
|
-
record?.startDate ?? record?.start_date ?? null,
|
|
103
|
-
record?.endDate ?? record?.end_date ?? null,
|
|
104
|
-
)
|
|
75
|
+
const dateSummary = formatDateRange(record?.startDate, record?.endDate)
|
|
105
76
|
const handleSubmit = React.useCallback(async (values: LeaveRequestFormValues) => {
|
|
106
77
|
if (!record?.id) return
|
|
107
78
|
const payload = buildLeaveRequestPayload(values, { id: record.id })
|
|
@@ -140,16 +111,16 @@ const handleSubmit = React.useCallback(async (values: LeaveRequestFormValues) =>
|
|
|
140
111
|
<Badge variant={resolveStatusVariant(status)}>
|
|
141
112
|
{t(`staff.leaveRequests.status.${status}`, status)}
|
|
142
113
|
</Badge>
|
|
143
|
-
{record.
|
|
114
|
+
{record.decidedAt ? (
|
|
144
115
|
<span className="text-xs text-muted-foreground">
|
|
145
|
-
{t('staff.leaveRequests.decision.at', 'Decision at')} {formatDateLabel(record.decidedAt
|
|
116
|
+
{t('staff.leaveRequests.decision.at', 'Decision at')} {formatDateLabel(record.decidedAt)}
|
|
146
117
|
</span>
|
|
147
118
|
) : null}
|
|
148
119
|
</div>
|
|
149
|
-
{record.decisionComment
|
|
120
|
+
{record.decisionComment ? (
|
|
150
121
|
<div className="text-sm text-muted-foreground">
|
|
151
122
|
<div className="font-medium text-foreground">{t('staff.leaveRequests.decision.comment', 'Decision comment')}</div>
|
|
152
|
-
<p>{record.decisionComment
|
|
123
|
+
<p>{record.decisionComment}</p>
|
|
153
124
|
</div>
|
|
154
125
|
) : null}
|
|
155
126
|
</div>
|
|
@@ -199,9 +170,9 @@ const handleSubmit = React.useCallback(async (values: LeaveRequestFormValues) =>
|
|
|
199
170
|
<div className="rounded-lg border bg-card p-4 text-sm text-muted-foreground">
|
|
200
171
|
<div className="font-medium text-foreground">{t('staff.leaveRequests.detail.summary', 'Request details')}</div>
|
|
201
172
|
<p>{memberLabel ? t('staff.leaveRequests.detail.member', 'Team member') + `: ${memberLabel}` : null}</p>
|
|
202
|
-
<p>{t('staff.leaveRequests.detail.dates', 'Dates')}: {formatDateRange(record.startDate
|
|
203
|
-
{record.unavailabilityReasonValue
|
|
204
|
-
<p>{t('staff.leaveRequests.detail.reason', 'Reason')}: {record.unavailabilityReasonValue
|
|
173
|
+
<p>{t('staff.leaveRequests.detail.dates', 'Dates')}: {formatDateRange(record.startDate, record.endDate)}</p>
|
|
174
|
+
{record.unavailabilityReasonValue ? (
|
|
175
|
+
<p>{t('staff.leaveRequests.detail.reason', 'Reason')}: {record.unavailabilityReasonValue}</p>
|
|
205
176
|
) : null}
|
|
206
177
|
{record.note ? <p>{t('staff.leaveRequests.detail.note', 'Note')}: {record.note}</p> : null}
|
|
207
178
|
</div>
|
|
@@ -210,23 +181,3 @@ const handleSubmit = React.useCallback(async (values: LeaveRequestFormValues) =>
|
|
|
210
181
|
</Page>
|
|
211
182
|
)
|
|
212
183
|
}
|
|
213
|
-
|
|
214
|
-
function resolveStatusVariant(status: 'pending' | 'approved' | 'rejected') {
|
|
215
|
-
if (status === 'approved') return 'default'
|
|
216
|
-
if (status === 'rejected') return 'destructive'
|
|
217
|
-
return 'secondary'
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
function formatDateLabel(value?: string | null): string {
|
|
221
|
-
if (!value) return ''
|
|
222
|
-
const date = new Date(value)
|
|
223
|
-
if (Number.isNaN(date.getTime())) return value
|
|
224
|
-
return date.toLocaleDateString()
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
function formatDateRange(start?: string | null, end?: string | null): string {
|
|
228
|
-
const startLabel = formatDateLabel(start)
|
|
229
|
-
const endLabel = formatDateLabel(end)
|
|
230
|
-
if (startLabel && endLabel) return `${startLabel} -> ${endLabel}`
|
|
231
|
-
return startLabel || endLabel || '-'
|
|
232
|
-
}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
requireId,
|
|
8
8
|
parseWithCustomFields,
|
|
9
9
|
setCustomFieldsIfAny,
|
|
10
|
+
normalizeAuthorUserId,
|
|
10
11
|
} from '@open-mercato/shared/lib/commands/helpers'
|
|
11
12
|
import type { DataEngine } from '@open-mercato/shared/lib/data/engine'
|
|
12
13
|
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
@@ -115,13 +116,7 @@ const createActivityCommand: CommandHandler<
|
|
|
115
116
|
const { parsed, custom } = parseWithCustomFields(staffTeamMemberActivityCreateSchema, rawInput)
|
|
116
117
|
ensureTenantScope(ctx, parsed.tenantId)
|
|
117
118
|
ensureOrganizationScope(ctx, parsed.organizationId)
|
|
118
|
-
const
|
|
119
|
-
const normalizedAuthor = (() => {
|
|
120
|
-
if (parsed.authorUserId) return parsed.authorUserId
|
|
121
|
-
if (!authSub) return null
|
|
122
|
-
const uuidRegex = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/
|
|
123
|
-
return uuidRegex.test(authSub) ? authSub : null
|
|
124
|
-
})()
|
|
119
|
+
const normalizedAuthor = normalizeAuthorUserId(parsed.authorUserId, ctx.auth)
|
|
125
120
|
|
|
126
121
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
127
122
|
const member = await requireTeamMember(em, parsed.entityId, 'Team member not found')
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { registerCommand } from '@open-mercato/shared/lib/commands'
|
|
2
2
|
import type { CommandHandler } from '@open-mercato/shared/lib/commands'
|
|
3
|
-
import { emitCrudSideEffects, emitCrudUndoSideEffects, buildChanges, requireId } from '@open-mercato/shared/lib/commands/helpers'
|
|
3
|
+
import { emitCrudSideEffects, emitCrudUndoSideEffects, buildChanges, requireId, normalizeAuthorUserId } from '@open-mercato/shared/lib/commands/helpers'
|
|
4
4
|
import type { DataEngine } from '@open-mercato/shared/lib/data/engine'
|
|
5
5
|
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
6
6
|
import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
@@ -60,13 +60,7 @@ const createCommentCommand: CommandHandler<
|
|
|
60
60
|
const parsed = staffTeamMemberCommentCreateSchema.parse(rawInput)
|
|
61
61
|
ensureTenantScope(ctx, parsed.tenantId)
|
|
62
62
|
ensureOrganizationScope(ctx, parsed.organizationId)
|
|
63
|
-
const
|
|
64
|
-
const normalizedAuthor = (() => {
|
|
65
|
-
if (parsed.authorUserId) return parsed.authorUserId
|
|
66
|
-
if (!authSub) return null
|
|
67
|
-
const uuidRegex = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/
|
|
68
|
-
return uuidRegex.test(authSub) ? authSub : null
|
|
69
|
-
})()
|
|
63
|
+
const normalizedAuthor = normalizeAuthorUserId(parsed.authorUserId, ctx.auth)
|
|
70
64
|
|
|
71
65
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
72
66
|
const member = await requireTeamMember(em, parsed.entityId, 'Team member not found')
|