@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,243 +1,35 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { makeCrudRoute } from "@open-mercato/shared/lib/crud/factory";
|
|
3
|
-
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
4
|
-
import { resolveCrudRecordId, parseScopedCommandInput } from "@open-mercato/shared/lib/api/scoped";
|
|
5
|
-
import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
1
|
+
import { makeActivityRoute } from "@open-mercato/core/modules/entities/lib/makeActivityRoute";
|
|
6
2
|
import { StaffTeamMemberActivity } from "../data/entities.js";
|
|
7
3
|
import {
|
|
8
4
|
staffTeamMemberActivityCreateSchema,
|
|
9
5
|
staffTeamMemberActivityUpdateSchema
|
|
10
6
|
} from "../data/validators.js";
|
|
11
|
-
import { User } from "@open-mercato/core/modules/auth/data/entities";
|
|
12
7
|
import { E } from "../../../generated/entities.ids.generated.js";
|
|
13
|
-
import { createStaffCrudOpenApi
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
metadata: routeMetadata,
|
|
31
|
-
orm: {
|
|
32
|
-
entity: StaffTeamMemberActivity,
|
|
33
|
-
idField: "id",
|
|
34
|
-
orgField: "organizationId",
|
|
35
|
-
tenantField: "tenantId"
|
|
36
|
-
},
|
|
37
|
-
indexer: {
|
|
38
|
-
entityType: E.staff.staff_team_member_activity
|
|
39
|
-
},
|
|
40
|
-
list: {
|
|
41
|
-
schema: listSchema,
|
|
42
|
-
entityId: E.staff.staff_team_member_activity,
|
|
43
|
-
fields: [
|
|
44
|
-
"id",
|
|
45
|
-
"member_id",
|
|
46
|
-
"activity_type",
|
|
47
|
-
"subject",
|
|
48
|
-
"body",
|
|
49
|
-
"occurred_at",
|
|
50
|
-
"author_user_id",
|
|
51
|
-
"appearance_icon",
|
|
52
|
-
"appearance_color",
|
|
53
|
-
"organization_id",
|
|
54
|
-
"tenant_id",
|
|
55
|
-
"created_at",
|
|
56
|
-
"updated_at"
|
|
57
|
-
],
|
|
58
|
-
decorateCustomFields: {
|
|
59
|
-
entityIds: E.staff.staff_team_member_activity
|
|
60
|
-
},
|
|
61
|
-
sortFieldMap: {
|
|
62
|
-
occurredAt: "occurred_at",
|
|
63
|
-
createdAt: "created_at",
|
|
64
|
-
updatedAt: "updated_at"
|
|
65
|
-
},
|
|
66
|
-
buildFilters: async (query) => {
|
|
67
|
-
const filters = {};
|
|
68
|
-
if (query.entityId) filters.member_id = { $eq: query.entityId };
|
|
69
|
-
return filters;
|
|
70
|
-
},
|
|
71
|
-
transformItem: (item) => {
|
|
72
|
-
const record = item ?? {};
|
|
73
|
-
const toIsoString = (value) => {
|
|
74
|
-
if (value == null) return null;
|
|
75
|
-
if (value instanceof Date) return value.toISOString();
|
|
76
|
-
if (typeof value === "string") {
|
|
77
|
-
const trimmed = value.trim();
|
|
78
|
-
if (!trimmed.length) return null;
|
|
79
|
-
const date = new Date(trimmed);
|
|
80
|
-
return Number.isNaN(date.getTime()) ? trimmed : date.toISOString();
|
|
81
|
-
}
|
|
82
|
-
return null;
|
|
83
|
-
};
|
|
84
|
-
const readString = (value) => typeof value === "string" ? value : null;
|
|
85
|
-
const idValue = readString(record.id) ?? (record.id != null ? String(record.id) : "");
|
|
86
|
-
const memberId = readString(record["member_id"]) ?? readString(record["memberId"]) ?? null;
|
|
87
|
-
const activityType = readString(record["activity_type"]) ?? readString(record["activityType"]) ?? "";
|
|
88
|
-
const subject = readString(record.subject) ?? (record.subject == null ? null : String(record.subject));
|
|
89
|
-
const body = readString(record.body) ?? (record.body == null ? null : String(record.body));
|
|
90
|
-
const authorUserId = readString(record["author_user_id"]) ?? readString(record["authorUserId"]) ?? null;
|
|
91
|
-
const appearanceIconRaw = readString(record["appearance_icon"]) ?? readString(record["appearanceIcon"]);
|
|
92
|
-
const appearanceColorRaw = readString(record["appearance_color"]) ?? readString(record["appearanceColor"]);
|
|
93
|
-
const organizationId = readString(record["organization_id"]) ?? readString(record["organizationId"]);
|
|
94
|
-
const tenantId = readString(record["tenant_id"]) ?? readString(record["tenantId"]);
|
|
95
|
-
const output = {
|
|
96
|
-
id: idValue,
|
|
97
|
-
entityId: memberId,
|
|
98
|
-
memberId,
|
|
99
|
-
activityType,
|
|
100
|
-
subject,
|
|
101
|
-
body,
|
|
102
|
-
occurredAt: toIsoString(record["occurred_at"] ?? record["occurredAt"]),
|
|
103
|
-
createdAt: toIsoString(record["created_at"] ?? record["createdAt"]),
|
|
104
|
-
authorUserId,
|
|
105
|
-
organizationId,
|
|
106
|
-
tenantId,
|
|
107
|
-
appearanceIcon: appearanceIconRaw && appearanceIconRaw.trim().length ? appearanceIconRaw : null,
|
|
108
|
-
appearanceColor: appearanceColorRaw && appearanceColorRaw.trim().length ? appearanceColorRaw : null,
|
|
109
|
-
customFields: Array.isArray(record.customFields) ? record.customFields : void 0,
|
|
110
|
-
customValues: record.customValues ?? void 0
|
|
111
|
-
};
|
|
112
|
-
for (const [key, value] of Object.entries(record)) {
|
|
113
|
-
if (key.startsWith("cf_") || key.startsWith("cf:")) {
|
|
114
|
-
output[key] = value;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
return output;
|
|
118
|
-
}
|
|
119
|
-
},
|
|
120
|
-
actions: {
|
|
121
|
-
create: {
|
|
122
|
-
commandId: "staff.team-member-activities.create",
|
|
123
|
-
schema: rawBodySchema,
|
|
124
|
-
mapInput: async ({ raw, ctx }) => {
|
|
125
|
-
const { translate } = await resolveTranslations();
|
|
126
|
-
return parseScopedCommandInput(staffTeamMemberActivityCreateSchema, raw ?? {}, ctx, translate);
|
|
127
|
-
},
|
|
128
|
-
response: ({ result }) => ({
|
|
129
|
-
id: result?.activityId ?? result?.id ?? null,
|
|
130
|
-
authorUserId: result?.authorUserId ?? null
|
|
131
|
-
}),
|
|
132
|
-
status: 201
|
|
133
|
-
},
|
|
134
|
-
update: {
|
|
135
|
-
commandId: "staff.team-member-activities.update",
|
|
136
|
-
schema: rawBodySchema,
|
|
137
|
-
mapInput: async ({ raw, ctx }) => {
|
|
138
|
-
const { translate } = await resolveTranslations();
|
|
139
|
-
return parseScopedCommandInput(staffTeamMemberActivityUpdateSchema, raw ?? {}, ctx, translate);
|
|
140
|
-
},
|
|
141
|
-
response: () => ({ ok: true })
|
|
142
|
-
},
|
|
143
|
-
delete: {
|
|
144
|
-
commandId: "staff.team-member-activities.delete",
|
|
145
|
-
schema: rawBodySchema,
|
|
146
|
-
mapInput: async ({ parsed, ctx }) => {
|
|
147
|
-
const { translate } = await resolveTranslations();
|
|
148
|
-
const id = resolveCrudRecordId(parsed, ctx, translate);
|
|
149
|
-
return { id };
|
|
150
|
-
},
|
|
151
|
-
response: () => ({ ok: true })
|
|
152
|
-
}
|
|
153
|
-
},
|
|
154
|
-
hooks: {
|
|
155
|
-
afterList: async (payload, ctx) => {
|
|
156
|
-
const items = Array.isArray(payload.items) ? payload.items : [];
|
|
157
|
-
if (!items.length) return;
|
|
158
|
-
const userIds = /* @__PURE__ */ new Set();
|
|
159
|
-
items.forEach((item) => {
|
|
160
|
-
if (!item || typeof item !== "object") return;
|
|
161
|
-
const record = item;
|
|
162
|
-
const userId = typeof record.author_user_id === "string" ? record.author_user_id : typeof record.authorUserId === "string" ? record.authorUserId : null;
|
|
163
|
-
if (userId) userIds.add(userId);
|
|
164
|
-
});
|
|
165
|
-
if (!userIds.size) return;
|
|
166
|
-
try {
|
|
167
|
-
const em = ctx.container.resolve("em").fork();
|
|
168
|
-
const users = await findWithDecryption(
|
|
169
|
-
em,
|
|
170
|
-
User,
|
|
171
|
-
{ id: { $in: Array.from(userIds) } },
|
|
172
|
-
void 0,
|
|
173
|
-
{ tenantId: ctx.auth?.tenantId ?? null, organizationId: ctx.selectedOrganizationId ?? null }
|
|
174
|
-
);
|
|
175
|
-
const map = /* @__PURE__ */ new Map();
|
|
176
|
-
users.forEach((user) => {
|
|
177
|
-
const name = typeof user.name === "string" && user.name.trim().length ? user.name.trim() : null;
|
|
178
|
-
map.set(user.id, { name, email: user.email ?? null });
|
|
179
|
-
});
|
|
180
|
-
items.forEach((item) => {
|
|
181
|
-
if (!item || typeof item !== "object") return;
|
|
182
|
-
const record = item;
|
|
183
|
-
const userId = typeof record.author_user_id === "string" ? record.author_user_id : typeof record.authorUserId === "string" ? record.authorUserId : null;
|
|
184
|
-
if (!userId) return;
|
|
185
|
-
const meta = map.get(userId);
|
|
186
|
-
if (!meta) return;
|
|
187
|
-
record.authorName = meta.name;
|
|
188
|
-
record.authorEmail = meta.email;
|
|
189
|
-
if (!("author_name" in record)) record.author_name = meta.name;
|
|
190
|
-
if (!("author_email" in record)) record.author_email = meta.email;
|
|
191
|
-
});
|
|
192
|
-
} catch (err) {
|
|
193
|
-
console.warn("[staff.activities] failed to enrich author metadata", err);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
});
|
|
198
|
-
const GET = crud.GET;
|
|
199
|
-
const POST = crud.POST;
|
|
200
|
-
const PUT = crud.PUT;
|
|
201
|
-
const DELETE = crud.DELETE;
|
|
202
|
-
const activityListItemSchema = z.object({
|
|
203
|
-
id: z.string().uuid(),
|
|
204
|
-
member_id: z.string().uuid().nullable().optional(),
|
|
205
|
-
activity_type: z.string().nullable().optional(),
|
|
206
|
-
subject: z.string().nullable().optional(),
|
|
207
|
-
body: z.string().nullable().optional(),
|
|
208
|
-
occurred_at: z.string().nullable().optional(),
|
|
209
|
-
author_user_id: z.string().uuid().nullable(),
|
|
210
|
-
appearance_icon: z.string().nullable().optional(),
|
|
211
|
-
appearance_color: z.string().nullable().optional(),
|
|
212
|
-
organization_id: z.string().uuid().nullable().optional(),
|
|
213
|
-
tenant_id: z.string().uuid().nullable().optional(),
|
|
214
|
-
created_at: z.string().nullable(),
|
|
215
|
-
updated_at: z.string().nullable().optional()
|
|
216
|
-
}).passthrough();
|
|
217
|
-
const activityCreateResponseSchema = z.object({
|
|
218
|
-
id: z.string().uuid().nullable(),
|
|
219
|
-
authorUserId: z.string().uuid().nullable()
|
|
220
|
-
});
|
|
221
|
-
const openApi = createStaffCrudOpenApi({
|
|
222
|
-
resourceName: "TeamMemberActivity",
|
|
223
|
-
querySchema: listSchema,
|
|
224
|
-
listResponseSchema: createPagedListResponseSchema(activityListItemSchema),
|
|
225
|
-
create: {
|
|
226
|
-
schema: staffTeamMemberActivityCreateSchema,
|
|
227
|
-
responseSchema: activityCreateResponseSchema,
|
|
228
|
-
description: "Adds an activity to a team member timeline."
|
|
229
|
-
},
|
|
230
|
-
update: {
|
|
231
|
-
schema: staffTeamMemberActivityUpdateSchema,
|
|
232
|
-
responseSchema: defaultOkResponseSchema,
|
|
233
|
-
description: "Updates a team member activity."
|
|
234
|
-
},
|
|
235
|
-
del: {
|
|
236
|
-
schema: z.object({ id: z.string().uuid() }),
|
|
237
|
-
responseSchema: defaultOkResponseSchema,
|
|
238
|
-
description: "Deletes a team member activity."
|
|
8
|
+
import { createStaffCrudOpenApi } from "./openapi.js";
|
|
9
|
+
const route = makeActivityRoute({
|
|
10
|
+
entity: StaffTeamMemberActivity,
|
|
11
|
+
entityId: E.staff.staff_team_member_activity,
|
|
12
|
+
parentFkColumn: "member_id",
|
|
13
|
+
parentFkParam: "memberId",
|
|
14
|
+
features: { view: "staff.view", manage: "staff.manage_team" },
|
|
15
|
+
createSchema: staffTeamMemberActivityCreateSchema,
|
|
16
|
+
updateSchema: staffTeamMemberActivityUpdateSchema,
|
|
17
|
+
commandPrefix: "staff.team-member-activities",
|
|
18
|
+
logPrefix: "[staff.activities]",
|
|
19
|
+
openApiFactory: createStaffCrudOpenApi,
|
|
20
|
+
openApi: {
|
|
21
|
+
resourceName: "TeamMemberActivity",
|
|
22
|
+
createDescription: "Adds an activity to a team member timeline.",
|
|
23
|
+
updateDescription: "Updates a team member activity.",
|
|
24
|
+
deleteDescription: "Deletes a team member activity."
|
|
239
25
|
}
|
|
240
26
|
});
|
|
27
|
+
const metadata = route.metadata;
|
|
28
|
+
const openApi = route.openApi;
|
|
29
|
+
const GET = route.GET;
|
|
30
|
+
const POST = route.POST;
|
|
31
|
+
const PUT = route.PUT;
|
|
32
|
+
const DELETE = route.DELETE;
|
|
241
33
|
export {
|
|
242
34
|
DELETE,
|
|
243
35
|
GET,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/staff/api/activities.ts"],
|
|
4
|
-
"sourcesContent": ["import { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { resolveCrudRecordId, parseScopedCommandInput } from '@open-mercato/shared/lib/api/scoped'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { StaffTeamMemberActivity } from '../data/entities'\nimport {\n staffTeamMemberActivityCreateSchema,\n staffTeamMemberActivityUpdateSchema,\n} from '../data/validators'\nimport { User } from '@open-mercato/core/modules/auth/data/entities'\nimport { E } from '#generated/entities.ids.generated'\nimport { createStaffCrudOpenApi, createPagedListResponseSchema, defaultOkResponseSchema } from './openapi'\n\nconst rawBodySchema = z.object({}).passthrough()\n\nconst listSchema = z\n .object({\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(100).default(50),\n entityId: z.string().uuid().optional(),\n sortField: z.string().optional(),\n sortDir: z.enum(['asc', 'desc']).optional(),\n })\n .passthrough()\n\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: ['staff.view'] },\n POST: { requireAuth: true, requireFeatures: ['staff.manage_team'] },\n PUT: { requireAuth: true, requireFeatures: ['staff.manage_team'] },\n DELETE: { requireAuth: true, requireFeatures: ['staff.manage_team'] },\n}\n\nexport const metadata = routeMetadata\n\nconst crud = makeCrudRoute({\n metadata: routeMetadata,\n orm: {\n entity: StaffTeamMemberActivity,\n idField: 'id',\n orgField: 'organizationId',\n tenantField: 'tenantId',\n },\n indexer: {\n entityType: E.staff.staff_team_member_activity,\n },\n list: {\n schema: listSchema,\n entityId: E.staff.staff_team_member_activity,\n fields: [\n 'id',\n 'member_id',\n 'activity_type',\n 'subject',\n 'body',\n 'occurred_at',\n 'author_user_id',\n 'appearance_icon',\n 'appearance_color',\n 'organization_id',\n 'tenant_id',\n 'created_at',\n 'updated_at',\n ],\n decorateCustomFields: {\n entityIds: E.staff.staff_team_member_activity,\n },\n sortFieldMap: {\n occurredAt: 'occurred_at',\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n },\n buildFilters: async (query) => {\n const filters: Record<string, unknown> = {}\n if (query.entityId) filters.member_id = { $eq: query.entityId }\n return filters\n },\n transformItem: (item: Record<string, unknown>) => {\n const record = (item ?? {}) as Record<string, unknown>\n const toIsoString = (value: unknown): string | null => {\n if (value == null) return null\n if (value instanceof Date) return value.toISOString()\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (!trimmed.length) return null\n const date = new Date(trimmed)\n return Number.isNaN(date.getTime()) ? trimmed : date.toISOString()\n }\n return null\n }\n const readString = (value: unknown): string | null => (typeof value === 'string' ? value : null)\n const idValue = readString(record.id) ?? (record.id != null ? String(record.id) : '')\n const memberId = readString(record['member_id']) ?? readString(record['memberId']) ?? null\n const activityType =\n readString(record['activity_type']) ??\n readString(record['activityType']) ??\n ''\n const subject =\n readString(record.subject) ??\n (record.subject == null ? null : String(record.subject))\n const body =\n readString(record.body) ??\n (record.body == null ? null : String(record.body))\n const authorUserId =\n readString(record['author_user_id']) ?? readString(record['authorUserId']) ?? null\n const appearanceIconRaw =\n readString(record['appearance_icon']) ?? readString(record['appearanceIcon'])\n const appearanceColorRaw =\n readString(record['appearance_color']) ?? readString(record['appearanceColor'])\n const organizationId =\n readString(record['organization_id']) ?? readString(record['organizationId'])\n const tenantId =\n readString(record['tenant_id']) ?? readString(record['tenantId'])\n const output: Record<string, unknown> = {\n id: idValue,\n entityId: memberId,\n memberId,\n activityType,\n subject,\n body,\n occurredAt: toIsoString(record['occurred_at'] ?? record['occurredAt']),\n createdAt: toIsoString(record['created_at'] ?? record['createdAt']),\n authorUserId,\n organizationId,\n tenantId,\n appearanceIcon: appearanceIconRaw && appearanceIconRaw.trim().length ? appearanceIconRaw : null,\n appearanceColor: appearanceColorRaw && appearanceColorRaw.trim().length ? appearanceColorRaw : null,\n customFields: Array.isArray(record.customFields) ? record.customFields : undefined,\n customValues: record.customValues ?? undefined,\n }\n for (const [key, value] of Object.entries(record)) {\n if (key.startsWith('cf_') || key.startsWith('cf:')) {\n output[key] = value\n }\n }\n return output\n },\n },\n actions: {\n create: {\n commandId: 'staff.team-member-activities.create',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n return parseScopedCommandInput(staffTeamMemberActivityCreateSchema, raw ?? {}, ctx, translate)\n },\n response: ({ result }) => ({\n id: result?.activityId ?? result?.id ?? null,\n authorUserId: result?.authorUserId ?? null,\n }),\n status: 201,\n },\n update: {\n commandId: 'staff.team-member-activities.update',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n return parseScopedCommandInput(staffTeamMemberActivityUpdateSchema, raw ?? {}, ctx, translate)\n },\n response: () => ({ ok: true }),\n },\n delete: {\n commandId: 'staff.team-member-activities.delete',\n schema: rawBodySchema,\n mapInput: async ({ parsed, ctx }) => {\n const { translate } = await resolveTranslations()\n const id = resolveCrudRecordId(parsed, ctx, translate)\n return { id }\n },\n response: () => ({ ok: true }),\n },\n },\n hooks: {\n afterList: async (payload, ctx) => {\n const items = Array.isArray(payload.items) ? payload.items : []\n if (!items.length) return\n const userIds = new Set<string>()\n items.forEach((item: unknown) => {\n if (!item || typeof item !== 'object') return\n const record = item as Record<string, unknown>\n const userId =\n typeof record.author_user_id === 'string'\n ? record.author_user_id\n : typeof record.authorUserId === 'string'\n ? record.authorUserId\n : null\n if (userId) userIds.add(userId)\n })\n if (!userIds.size) return\n try {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const users = await findWithDecryption(\n em,\n User,\n { id: { $in: Array.from(userIds) } },\n undefined,\n { tenantId: ctx.auth?.tenantId ?? null, organizationId: ctx.selectedOrganizationId ?? null },\n )\n const map = new Map<string, { name: string | null; email: string | null }>()\n users.forEach((user) => {\n const name = typeof user.name === 'string' && user.name.trim().length\n ? user.name.trim()\n : null\n map.set(user.id, { name, email: user.email ?? null })\n })\n items.forEach((item: unknown) => {\n if (!item || typeof item !== 'object') return\n const record = item as Record<string, unknown>\n const userId =\n typeof record.author_user_id === 'string'\n ? record.author_user_id\n : typeof record.authorUserId === 'string'\n ? record.authorUserId\n : null\n if (!userId) return\n const meta = map.get(userId)\n if (!meta) return\n record.authorName = meta.name\n record.authorEmail = meta.email\n if (!('author_name' in record)) record.author_name = meta.name\n if (!('author_email' in record)) record.author_email = meta.email\n })\n } catch (err) {\n console.warn('[staff.activities] failed to enrich author metadata', err)\n }\n },\n },\n})\n\nexport const GET = crud.GET\nexport const POST = crud.POST\nexport const PUT = crud.PUT\nexport const DELETE = crud.DELETE\n\nconst activityListItemSchema = z\n .object({\n id: z.string().uuid(),\n member_id: z.string().uuid().nullable().optional(),\n activity_type: z.string().nullable().optional(),\n subject: z.string().nullable().optional(),\n body: z.string().nullable().optional(),\n occurred_at: z.string().nullable().optional(),\n author_user_id: z.string().uuid().nullable(),\n appearance_icon: z.string().nullable().optional(),\n appearance_color: z.string().nullable().optional(),\n organization_id: z.string().uuid().nullable().optional(),\n tenant_id: z.string().uuid().nullable().optional(),\n created_at: z.string().nullable(),\n updated_at: z.string().nullable().optional(),\n })\n .passthrough()\n\nconst activityCreateResponseSchema = z.object({\n id: z.string().uuid().nullable(),\n authorUserId: z.string().uuid().nullable(),\n})\n\nexport const openApi = createStaffCrudOpenApi({\n resourceName: 'TeamMemberActivity',\n querySchema: listSchema,\n listResponseSchema: createPagedListResponseSchema(activityListItemSchema),\n create: {\n schema: staffTeamMemberActivityCreateSchema,\n responseSchema: activityCreateResponseSchema,\n description: 'Adds an activity to a team member timeline.',\n },\n update: {\n schema: staffTeamMemberActivityUpdateSchema,\n responseSchema: defaultOkResponseSchema,\n description: 'Updates a team member activity.',\n },\n del: {\n schema: z.object({ id: z.string().uuid() }),\n responseSchema: defaultOkResponseSchema,\n description: 'Deletes a team member activity.',\n },\n})\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,
|
|
4
|
+
"sourcesContent": ["import { makeActivityRoute } from '@open-mercato/core/modules/entities/lib/makeActivityRoute'\nimport { StaffTeamMemberActivity } from '../data/entities'\nimport {\n staffTeamMemberActivityCreateSchema,\n staffTeamMemberActivityUpdateSchema,\n} from '../data/validators'\nimport { E } from '#generated/entities.ids.generated'\nimport { createStaffCrudOpenApi } from './openapi'\n\nconst route = makeActivityRoute({\n entity: StaffTeamMemberActivity,\n entityId: E.staff.staff_team_member_activity,\n parentFkColumn: 'member_id',\n parentFkParam: 'memberId',\n features: { view: 'staff.view', manage: 'staff.manage_team' },\n createSchema: staffTeamMemberActivityCreateSchema,\n updateSchema: staffTeamMemberActivityUpdateSchema,\n commandPrefix: 'staff.team-member-activities',\n logPrefix: '[staff.activities]',\n openApiFactory: createStaffCrudOpenApi,\n openApi: {\n resourceName: 'TeamMemberActivity',\n createDescription: 'Adds an activity to a team member timeline.',\n updateDescription: 'Updates a team member activity.',\n deleteDescription: 'Deletes a team member activity.',\n },\n})\n\nexport const metadata = route.metadata\nexport const openApi = route.openApi\nexport const GET = route.GET\nexport const POST = route.POST\nexport const PUT = route.PUT\nexport const DELETE = route.DELETE\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,yBAAyB;AAClC,SAAS,+BAA+B;AACxC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,SAAS;AAClB,SAAS,8BAA8B;AAEvC,MAAM,QAAQ,kBAAkB;AAAA,EAC9B,QAAQ;AAAA,EACR,UAAU,EAAE,MAAM;AAAA,EAClB,gBAAgB;AAAA,EAChB,eAAe;AAAA,EACf,UAAU,EAAE,MAAM,cAAc,QAAQ,oBAAoB;AAAA,EAC5D,cAAc;AAAA,EACd,cAAc;AAAA,EACd,eAAe;AAAA,EACf,WAAW;AAAA,EACX,gBAAgB;AAAA,EAChB,SAAS;AAAA,IACP,cAAc;AAAA,IACd,mBAAmB;AAAA,IACnB,mBAAmB;AAAA,IACnB,mBAAmB;AAAA,EACrB;AACF,CAAC;AAEM,MAAM,WAAW,MAAM;AACvB,MAAM,UAAU,MAAM;AACtB,MAAM,MAAM,MAAM;AAClB,MAAM,OAAO,MAAM;AACnB,MAAM,MAAM,MAAM;AAClB,MAAM,SAAS,MAAM;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -13,6 +13,7 @@ import { updateCrud } from "@open-mercato/ui/backend/utils/crud";
|
|
|
13
13
|
import { flash } from "@open-mercato/ui/backend/FlashMessages";
|
|
14
14
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
15
15
|
import { LeaveRequestForm, buildLeaveRequestPayload } from "@open-mercato/core/modules/staff/components/LeaveRequestForm";
|
|
16
|
+
import { normalizeLeaveRequest, resolveStatusVariant, formatDateLabel, formatDateRange } from "../../../../lib/leaveRequestHelpers.js";
|
|
16
17
|
function StaffLeaveRequestDetailPage({ params }) {
|
|
17
18
|
const id = params?.id;
|
|
18
19
|
const t = useT();
|
|
@@ -42,10 +43,9 @@ function StaffLeaveRequestDetailPage({ params }) {
|
|
|
42
43
|
const entry = Array.isArray(payload.items) ? payload.items[0] : null;
|
|
43
44
|
if (!entry) throw new Error(t("staff.leaveRequests.errors.notFound", "Leave request not found."));
|
|
44
45
|
if (!cancelled) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
);
|
|
46
|
+
const normalized = normalizeLeaveRequest(entry);
|
|
47
|
+
setRecord(normalized);
|
|
48
|
+
setDecisionComment(normalized.decisionComment ?? "");
|
|
49
49
|
}
|
|
50
50
|
} catch (err) {
|
|
51
51
|
if (!cancelled) {
|
|
@@ -64,19 +64,16 @@ function StaffLeaveRequestDetailPage({ params }) {
|
|
|
64
64
|
}, [id, t]);
|
|
65
65
|
const status = record?.status ?? "pending";
|
|
66
66
|
const memberLabel = record?.member?.displayName ?? null;
|
|
67
|
-
const dateSummary = formatDateRange(
|
|
68
|
-
record?.startDate ?? record?.start_date ?? null,
|
|
69
|
-
record?.endDate ?? record?.end_date ?? null
|
|
70
|
-
);
|
|
67
|
+
const dateSummary = formatDateRange(record?.startDate, record?.endDate);
|
|
71
68
|
const initialValues = React.useMemo(() => ({
|
|
72
69
|
id: record?.id,
|
|
73
|
-
memberId: record?.memberId ??
|
|
70
|
+
memberId: record?.memberId ?? null,
|
|
74
71
|
memberLabel,
|
|
75
|
-
startDate: record?.startDate ??
|
|
76
|
-
endDate: record?.endDate ??
|
|
72
|
+
startDate: record?.startDate ?? null,
|
|
73
|
+
endDate: record?.endDate ?? null,
|
|
77
74
|
timezone: record?.timezone ?? null,
|
|
78
|
-
unavailabilityReasonEntryId: record?.unavailabilityReasonEntryId ??
|
|
79
|
-
unavailabilityReasonValue: record?.unavailabilityReasonValue ??
|
|
75
|
+
unavailabilityReasonEntryId: record?.unavailabilityReasonEntryId ?? null,
|
|
76
|
+
unavailabilityReasonValue: record?.unavailabilityReasonValue ?? null,
|
|
80
77
|
note: record?.note ?? null
|
|
81
78
|
}), [record, memberLabel]);
|
|
82
79
|
const handleSubmit = React.useCallback(async (values) => {
|
|
@@ -112,10 +109,10 @@ function StaffLeaveRequestDetailPage({ params }) {
|
|
|
112
109
|
/* @__PURE__ */ jsxs("div", { className: "mb-6 space-y-2 rounded-lg border bg-card p-4", children: [
|
|
113
110
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [
|
|
114
111
|
/* @__PURE__ */ jsx(Badge, { variant: resolveStatusVariant(status), children: t(`staff.leaveRequests.status.${status}`, status) }),
|
|
115
|
-
record.
|
|
112
|
+
record.decidedAt ? /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
|
|
116
113
|
t("staff.leaveRequests.decision.at", "Decision at"),
|
|
117
114
|
" ",
|
|
118
|
-
formatDateLabel(record.decidedAt
|
|
115
|
+
formatDateLabel(record.decidedAt)
|
|
119
116
|
] }) : null
|
|
120
117
|
] }),
|
|
121
118
|
memberLabel ? /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground", children: [
|
|
@@ -144,9 +141,9 @@ function StaffLeaveRequestDetailPage({ params }) {
|
|
|
144
141
|
/* @__PURE__ */ jsx(Button, { onClick: () => handleDecision("accept"), children: t("staff.leaveRequests.actions.accept", "Approve") }),
|
|
145
142
|
/* @__PURE__ */ jsx(Button, { variant: "destructive", onClick: () => handleDecision("reject"), children: t("staff.leaveRequests.actions.reject", "Reject") })
|
|
146
143
|
] })
|
|
147
|
-
] }) : record.decisionComment
|
|
144
|
+
] }) : record.decisionComment ? /* @__PURE__ */ jsxs("div", { className: "mb-6 rounded-lg border bg-card p-4 text-sm text-muted-foreground", children: [
|
|
148
145
|
/* @__PURE__ */ jsx("div", { className: "mb-1 font-medium text-foreground", children: t("staff.leaveRequests.decision.comment", "Decision comment") }),
|
|
149
|
-
/* @__PURE__ */ jsx("p", { children: record.decisionComment
|
|
146
|
+
/* @__PURE__ */ jsx("p", { children: record.decisionComment })
|
|
150
147
|
] }) : null,
|
|
151
148
|
/* @__PURE__ */ jsx(
|
|
152
149
|
LeaveRequestForm,
|
|
@@ -194,23 +191,6 @@ function StaffLeaveRequestDetailPage({ params }) {
|
|
|
194
191
|
)
|
|
195
192
|
] }) });
|
|
196
193
|
}
|
|
197
|
-
function resolveStatusVariant(status) {
|
|
198
|
-
if (status === "approved") return "default";
|
|
199
|
-
if (status === "rejected") return "destructive";
|
|
200
|
-
return "secondary";
|
|
201
|
-
}
|
|
202
|
-
function formatDateLabel(value) {
|
|
203
|
-
if (!value) return "";
|
|
204
|
-
const date = new Date(value);
|
|
205
|
-
if (Number.isNaN(date.getTime())) return value;
|
|
206
|
-
return date.toLocaleDateString();
|
|
207
|
-
}
|
|
208
|
-
function formatDateRange(start, end) {
|
|
209
|
-
const startLabel = formatDateLabel(start);
|
|
210
|
-
const endLabel = formatDateLabel(end);
|
|
211
|
-
if (startLabel && endLabel) return `${startLabel} -> ${endLabel}`;
|
|
212
|
-
return startLabel || endLabel || "-";
|
|
213
|
-
}
|
|
214
194
|
export {
|
|
215
195
|
StaffLeaveRequestDetailPage as default
|
|
216
196
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/staff/backend/staff/leave-requests/%5Bid%5D/page.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport { Send } from 'lucide-react'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Textarea } from '@open-mercato/ui/primitives/textarea'\nimport { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'\nimport { SendObjectMessageDialog } from '@open-mercato/ui/backend/messages'\nimport { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { updateCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { LeaveRequestForm, buildLeaveRequestPayload, type LeaveRequestFormValues } from '@open-mercato/core/modules/staff/components/LeaveRequestForm'\n\ntype LeaveRequestRecord = {\n id: string\n member?: { id?: string; displayName?: string }\n memberId?: string | null\n member_id?: string | null\n startDate?: string | null\n start_date?: string | null\n endDate?: string | null\n end_date?: string | null\n timezone?: string | null\n status?: 'pending' | 'approved' | 'rejected'\n unavailabilityReasonEntryId?: string | null\n unavailability_reason_entry_id?: string | null\n unavailabilityReasonValue?: string | null\n unavailability_reason_value?: string | null\n note?: string | null\n decisionComment?: string | null\n decision_comment?: string | null\n decidedAt?: string | null\n decided_at?: string | null\n} & Record<string, unknown>\n\ntype LeaveRequestsResponse = {\n items?: LeaveRequestRecord[]\n}\n\nexport default function StaffLeaveRequestDetailPage({ params }: { params?: { id?: string } }) {\n const id = params?.id\n const t = useT()\n const router = useRouter()\n const [isLoading, setIsLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [record, setRecord] = React.useState<LeaveRequestRecord | null>(null)\n const [decisionComment, setDecisionComment] = React.useState('')\n\n React.useEffect(() => {\n if (!id) {\n setError(t('staff.leaveRequests.errors.notFound', 'Leave request not found.'))\n setIsLoading(false)\n return\n }\n const requestId = id\n let cancelled = false\n async function load() {\n setIsLoading(true)\n setError(null)\n try {\n const params = new URLSearchParams({ page: '1', pageSize: '1', ids: requestId })\n const payload = await readApiResultOrThrow<LeaveRequestsResponse>(\n `/api/staff/leave-requests?${params.toString()}`,\n undefined,\n { errorMessage: t('staff.leaveRequests.errors.load', 'Failed to load leave request.') },\n )\n const entry = Array.isArray(payload.items) ? payload.items[0] : null\n if (!entry) throw new Error(t('staff.leaveRequests.errors.notFound', 'Leave request not found.'))\n if (!cancelled) {\n setRecord(entry)\n setDecisionComment(\n typeof entry.decisionComment === 'string'\n ? entry.decisionComment\n : typeof entry.decision_comment === 'string'\n ? entry.decision_comment\n : ''\n )\n }\n } catch (err) {\n if (!cancelled) {\n const message = err instanceof Error ? err.message : t('staff.leaveRequests.errors.load', 'Failed to load leave request.')\n setError(message)\n setRecord(null)\n }\n } finally {\n if (!cancelled) setIsLoading(false)\n }\n }\n void load()\n return () => { cancelled = true }\n }, [id, t])\n\n const status = record?.status ?? 'pending'\n const memberLabel = record?.member?.displayName ?? null\n const dateSummary = formatDateRange(\n record?.startDate ?? record?.start_date ?? null,\n record?.endDate ?? record?.end_date ?? null,\n )\n const initialValues = React.useMemo<LeaveRequestFormValues>(() => ({\n id: record?.id,\n memberId: record?.memberId ?? record?.member_id ?? null,\n memberLabel,\n startDate: record?.startDate ?? record?.start_date ?? null,\n endDate: record?.endDate ?? record?.end_date ?? null,\n timezone: record?.timezone ?? null,\n unavailabilityReasonEntryId: record?.unavailabilityReasonEntryId ?? record?.unavailability_reason_entry_id ?? null,\n unavailabilityReasonValue: record?.unavailabilityReasonValue ?? record?.unavailability_reason_value ?? null,\n note: record?.note ?? null,\n }), [record, memberLabel])\n\nconst handleSubmit = React.useCallback(async (values: LeaveRequestFormValues) => {\n if (!record?.id) return\n const payload = buildLeaveRequestPayload(values, { id: record.id })\n await updateCrud('staff/leave-requests', payload, {\n errorMessage: t('staff.leaveRequests.form.errors.update', 'Failed to update leave request.'),\n })\n flash(t('staff.leaveRequests.form.flash.updated', 'Leave request updated.'), 'success')\n router.push('/backend/staff/leave-requests')\n }, [record?.id, router, t])\n\n const handleDecision = React.useCallback(async (action: 'accept' | 'reject') => {\n if (!record?.id) return\n const endpoint = action === 'accept' ? '/api/staff/leave-requests/accept' : '/api/staff/leave-requests/reject'\n await apiCallOrThrow(endpoint, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ id: record.id, decisionComment: decisionComment || null }),\n })\n flash(\n action === 'accept'\n ? t('staff.leaveRequests.messages.accepted', 'Leave request approved.')\n : t('staff.leaveRequests.messages.rejected', 'Leave request rejected.'),\n 'success',\n )\n router.refresh()\n }, [decisionComment, record?.id, router, t])\n\n if (isLoading) {\n return (\n <Page>\n <PageBody>\n <LoadingMessage label={t('staff.leaveRequests.form.loading', 'Loading leave request...')} />\n </PageBody>\n </Page>\n )\n }\n\n if (error || !record) {\n return (\n <Page>\n <PageBody>\n <ErrorMessage label={error ?? t('staff.leaveRequests.errors.load', 'Failed to load leave request.')} />\n </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody>\n <div className=\"mb-6 space-y-2 rounded-lg border bg-card p-4\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <Badge variant={resolveStatusVariant(status)}>\n {t(`staff.leaveRequests.status.${status}`, status)}\n </Badge>\n {record.decided_at || record.decidedAt ? (\n <span className=\"text-xs text-muted-foreground\">\n {t('staff.leaveRequests.decision.at', 'Decision at')} {formatDateLabel(record.decidedAt ?? record.decided_at ?? null)}\n </span>\n ) : null}\n </div>\n {memberLabel ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('staff.leaveRequests.detail.member', 'Team member')}: {memberLabel}\n </p>\n ) : null}\n <p className=\"text-sm text-muted-foreground\">\n {t('staff.leaveRequests.detail.dates', 'Dates')}: {dateSummary}\n </p>\n </div>\n\n {status === 'pending' ? (\n <div className=\"mb-6 rounded-lg border bg-card p-4\">\n <div className=\"mb-3 text-sm font-medium\">{t('staff.leaveRequests.decision.title', 'Decision')}</div>\n <Textarea\n value={decisionComment}\n onChange={(event) => setDecisionComment(event.target.value)}\n placeholder={t('staff.leaveRequests.decision.placeholder', 'Add a comment (optional)')}\n className=\"mb-3\"\n />\n <div className=\"flex flex-wrap gap-2\">\n <Button onClick={() => handleDecision('accept')}>\n {t('staff.leaveRequests.actions.accept', 'Approve')}\n </Button>\n <Button variant=\"destructive\" onClick={() => handleDecision('reject')}>\n {t('staff.leaveRequests.actions.reject', 'Reject')}\n </Button>\n </div>\n </div>\n ) : record.decisionComment || record.decision_comment ? (\n <div className=\"mb-6 rounded-lg border bg-card p-4 text-sm text-muted-foreground\">\n <div className=\"mb-1 font-medium text-foreground\">{t('staff.leaveRequests.decision.comment', 'Decision comment')}</div>\n <p>{record.decisionComment ?? record.decision_comment}</p>\n </div>\n ) : null}\n\n <LeaveRequestForm\n title={t('staff.leaveRequests.form.editTitle', 'Leave request')}\n submitLabel={t('staff.leaveRequests.form.actions.save', 'Save')}\n backHref=\"/backend/staff/leave-requests\"\n cancelHref=\"/backend/staff/leave-requests\"\n initialValues={initialValues}\n onSubmit={handleSubmit}\n allowMemberSelect\n memberLabel={memberLabel}\n extraActions={record.id ? (\n <SendObjectMessageDialog\n object={{\n entityModule: 'staff',\n entityType: 'leave_request',\n entityId: record.id,\n sourceEntityType: 'staff:leave_request',\n sourceEntityId: record.id,\n previewData: {\n title: memberLabel || t('staff.leaveRequests.messages.contextTitle', 'Linked leave request'),\n subtitle: dateSummary || undefined,\n status: record?.status ?? undefined,\n },\n }}\n viewHref={`/backend/staff/leave-requests/${record.id}`}\n lockedType=\"staff.leave_request_approval\"\n requiredActionConfig={{\n mode: 'required',\n options: [\n { id: 'approve', label: t('staff.notifications.leaveRequest.actions.approve', 'Approve') },\n { id: 'reject', label: t('staff.notifications.leaveRequest.actions.reject', 'Reject') },\n ],\n }}\n defaultValues={{\n type: 'staff.leave_request_approval',\n subject: t('staff.leaveRequests.messages.compose.subject', 'Leave request approval needed'),\n body: t('staff.leaveRequests.messages.compose.body', 'Please review this leave request and take action.'),\n }}\n />\n ) : null}\n />\n </PageBody>\n </Page>\n )\n}\n\nfunction resolveStatusVariant(status: 'pending' | 'approved' | 'rejected') {\n if (status === 'approved') return 'default'\n if (status === 'rejected') return 'destructive'\n return 'secondary'\n}\n\nfunction formatDateLabel(value?: string | null): string {\n if (!value) return ''\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return value\n return date.toLocaleDateString()\n}\n\nfunction formatDateRange(start?: string | null, end?: string | null): string {\n const startLabel = formatDateLabel(start)\n const endLabel = formatDateLabel(end)\n if (startLabel && endLabel) return `${startLabel} -> ${endLabel}`\n return startLabel || endLabel || '-'\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Textarea } from '@open-mercato/ui/primitives/textarea'\nimport { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'\nimport { SendObjectMessageDialog } from '@open-mercato/ui/backend/messages'\nimport { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { updateCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { LeaveRequestForm, buildLeaveRequestPayload, type LeaveRequestFormValues } from '@open-mercato/core/modules/staff/components/LeaveRequestForm'\nimport { type LeaveRequestRecord, type LeaveRequestsResponse, type NormalizedLeaveRequest, normalizeLeaveRequest, resolveStatusVariant, formatDateLabel, formatDateRange } from '../../../../lib/leaveRequestHelpers'\n\nexport default function StaffLeaveRequestDetailPage({ params }: { params?: { id?: string } }) {\n const id = params?.id\n const t = useT()\n const router = useRouter()\n const [isLoading, setIsLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [record, setRecord] = React.useState<NormalizedLeaveRequest | null>(null)\n const [decisionComment, setDecisionComment] = React.useState('')\n\n React.useEffect(() => {\n if (!id) {\n setError(t('staff.leaveRequests.errors.notFound', 'Leave request not found.'))\n setIsLoading(false)\n return\n }\n const requestId = id\n let cancelled = false\n async function load() {\n setIsLoading(true)\n setError(null)\n try {\n const params = new URLSearchParams({ page: '1', pageSize: '1', ids: requestId })\n const payload = await readApiResultOrThrow<LeaveRequestsResponse>(\n `/api/staff/leave-requests?${params.toString()}`,\n undefined,\n { errorMessage: t('staff.leaveRequests.errors.load', 'Failed to load leave request.') },\n )\n const entry = Array.isArray(payload.items) ? payload.items[0] : null\n if (!entry) throw new Error(t('staff.leaveRequests.errors.notFound', 'Leave request not found.'))\n if (!cancelled) {\n const normalized = normalizeLeaveRequest(entry)\n setRecord(normalized)\n setDecisionComment(normalized.decisionComment ?? '')\n }\n } catch (err) {\n if (!cancelled) {\n const message = err instanceof Error ? err.message : t('staff.leaveRequests.errors.load', 'Failed to load leave request.')\n setError(message)\n setRecord(null)\n }\n } finally {\n if (!cancelled) setIsLoading(false)\n }\n }\n void load()\n return () => { cancelled = true }\n }, [id, t])\n\n const status = record?.status ?? 'pending'\n const memberLabel = record?.member?.displayName ?? null\n const dateSummary = formatDateRange(record?.startDate, record?.endDate)\n const initialValues = React.useMemo<LeaveRequestFormValues>(() => ({\n id: record?.id,\n memberId: record?.memberId ?? null,\n memberLabel,\n startDate: record?.startDate ?? null,\n endDate: record?.endDate ?? null,\n timezone: record?.timezone ?? null,\n unavailabilityReasonEntryId: record?.unavailabilityReasonEntryId ?? null,\n unavailabilityReasonValue: record?.unavailabilityReasonValue ?? null,\n note: record?.note ?? null,\n }), [record, memberLabel])\n\nconst handleSubmit = React.useCallback(async (values: LeaveRequestFormValues) => {\n if (!record?.id) return\n const payload = buildLeaveRequestPayload(values, { id: record.id })\n await updateCrud('staff/leave-requests', payload, {\n errorMessage: t('staff.leaveRequests.form.errors.update', 'Failed to update leave request.'),\n })\n flash(t('staff.leaveRequests.form.flash.updated', 'Leave request updated.'), 'success')\n router.push('/backend/staff/leave-requests')\n }, [record?.id, router, t])\n\n const handleDecision = React.useCallback(async (action: 'accept' | 'reject') => {\n if (!record?.id) return\n const endpoint = action === 'accept' ? '/api/staff/leave-requests/accept' : '/api/staff/leave-requests/reject'\n await apiCallOrThrow(endpoint, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ id: record.id, decisionComment: decisionComment || null }),\n })\n flash(\n action === 'accept'\n ? t('staff.leaveRequests.messages.accepted', 'Leave request approved.')\n : t('staff.leaveRequests.messages.rejected', 'Leave request rejected.'),\n 'success',\n )\n router.refresh()\n }, [decisionComment, record?.id, router, t])\n\n if (isLoading) {\n return (\n <Page>\n <PageBody>\n <LoadingMessage label={t('staff.leaveRequests.form.loading', 'Loading leave request...')} />\n </PageBody>\n </Page>\n )\n }\n\n if (error || !record) {\n return (\n <Page>\n <PageBody>\n <ErrorMessage label={error ?? t('staff.leaveRequests.errors.load', 'Failed to load leave request.')} />\n </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody>\n <div className=\"mb-6 space-y-2 rounded-lg border bg-card p-4\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <Badge variant={resolveStatusVariant(status)}>\n {t(`staff.leaveRequests.status.${status}`, status)}\n </Badge>\n {record.decidedAt ? (\n <span className=\"text-xs text-muted-foreground\">\n {t('staff.leaveRequests.decision.at', 'Decision at')} {formatDateLabel(record.decidedAt)}\n </span>\n ) : null}\n </div>\n {memberLabel ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('staff.leaveRequests.detail.member', 'Team member')}: {memberLabel}\n </p>\n ) : null}\n <p className=\"text-sm text-muted-foreground\">\n {t('staff.leaveRequests.detail.dates', 'Dates')}: {dateSummary}\n </p>\n </div>\n\n {status === 'pending' ? (\n <div className=\"mb-6 rounded-lg border bg-card p-4\">\n <div className=\"mb-3 text-sm font-medium\">{t('staff.leaveRequests.decision.title', 'Decision')}</div>\n <Textarea\n value={decisionComment}\n onChange={(event) => setDecisionComment(event.target.value)}\n placeholder={t('staff.leaveRequests.decision.placeholder', 'Add a comment (optional)')}\n className=\"mb-3\"\n />\n <div className=\"flex flex-wrap gap-2\">\n <Button onClick={() => handleDecision('accept')}>\n {t('staff.leaveRequests.actions.accept', 'Approve')}\n </Button>\n <Button variant=\"destructive\" onClick={() => handleDecision('reject')}>\n {t('staff.leaveRequests.actions.reject', 'Reject')}\n </Button>\n </div>\n </div>\n ) : record.decisionComment ? (\n <div className=\"mb-6 rounded-lg border bg-card p-4 text-sm text-muted-foreground\">\n <div className=\"mb-1 font-medium text-foreground\">{t('staff.leaveRequests.decision.comment', 'Decision comment')}</div>\n <p>{record.decisionComment}</p>\n </div>\n ) : null}\n\n <LeaveRequestForm\n title={t('staff.leaveRequests.form.editTitle', 'Leave request')}\n submitLabel={t('staff.leaveRequests.form.actions.save', 'Save')}\n backHref=\"/backend/staff/leave-requests\"\n cancelHref=\"/backend/staff/leave-requests\"\n initialValues={initialValues}\n onSubmit={handleSubmit}\n allowMemberSelect\n memberLabel={memberLabel}\n extraActions={record.id ? (\n <SendObjectMessageDialog\n object={{\n entityModule: 'staff',\n entityType: 'leave_request',\n entityId: record.id,\n sourceEntityType: 'staff:leave_request',\n sourceEntityId: record.id,\n previewData: {\n title: memberLabel || t('staff.leaveRequests.messages.contextTitle', 'Linked leave request'),\n subtitle: dateSummary || undefined,\n status: record?.status ?? undefined,\n },\n }}\n viewHref={`/backend/staff/leave-requests/${record.id}`}\n lockedType=\"staff.leave_request_approval\"\n requiredActionConfig={{\n mode: 'required',\n options: [\n { id: 'approve', label: t('staff.notifications.leaveRequest.actions.approve', 'Approve') },\n { id: 'reject', label: t('staff.notifications.leaveRequest.actions.reject', 'Reject') },\n ],\n }}\n defaultValues={{\n type: 'staff.leave_request_approval',\n subject: t('staff.leaveRequests.messages.compose.subject', 'Leave request approval needed'),\n body: t('staff.leaveRequests.messages.compose.body', 'Please review this leave request and take action.'),\n }}\n />\n ) : null}\n />\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["params"]
|
|
7
7
|
}
|
|
@@ -11,6 +11,7 @@ import { updateCrud } from "@open-mercato/ui/backend/utils/crud";
|
|
|
11
11
|
import { flash } from "@open-mercato/ui/backend/FlashMessages";
|
|
12
12
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
13
13
|
import { LeaveRequestForm, buildLeaveRequestPayload } from "@open-mercato/core/modules/staff/components/LeaveRequestForm";
|
|
14
|
+
import { normalizeLeaveRequest, resolveStatusVariant, formatDateLabel, formatDateRange } from "../../../../lib/leaveRequestHelpers.js";
|
|
14
15
|
function StaffMyLeaveRequestDetailPage({ params }) {
|
|
15
16
|
const id = params?.id;
|
|
16
17
|
const t = useT();
|
|
@@ -39,7 +40,7 @@ function StaffMyLeaveRequestDetailPage({ params }) {
|
|
|
39
40
|
const entry = Array.isArray(payload.items) ? payload.items[0] : null;
|
|
40
41
|
if (!entry) throw new Error(t("staff.leaveRequests.errors.notFound", "Leave request not found."));
|
|
41
42
|
if (!cancelled) {
|
|
42
|
-
setRecord(entry);
|
|
43
|
+
setRecord(normalizeLeaveRequest(entry));
|
|
43
44
|
}
|
|
44
45
|
} catch (err) {
|
|
45
46
|
if (!cancelled) {
|
|
@@ -60,19 +61,16 @@ function StaffMyLeaveRequestDetailPage({ params }) {
|
|
|
60
61
|
const memberLabel = record?.member?.displayName ?? null;
|
|
61
62
|
const initialValues = React.useMemo(() => ({
|
|
62
63
|
id: record?.id,
|
|
63
|
-
memberId: record?.memberId ??
|
|
64
|
+
memberId: record?.memberId ?? null,
|
|
64
65
|
memberLabel,
|
|
65
|
-
startDate: record?.startDate ??
|
|
66
|
-
endDate: record?.endDate ??
|
|
66
|
+
startDate: record?.startDate ?? null,
|
|
67
|
+
endDate: record?.endDate ?? null,
|
|
67
68
|
timezone: record?.timezone ?? null,
|
|
68
|
-
unavailabilityReasonEntryId: record?.unavailabilityReasonEntryId ??
|
|
69
|
-
unavailabilityReasonValue: record?.unavailabilityReasonValue ??
|
|
69
|
+
unavailabilityReasonEntryId: record?.unavailabilityReasonEntryId ?? null,
|
|
70
|
+
unavailabilityReasonValue: record?.unavailabilityReasonValue ?? null,
|
|
70
71
|
note: record?.note ?? null
|
|
71
72
|
}), [record, memberLabel]);
|
|
72
|
-
const dateSummary = formatDateRange(
|
|
73
|
-
record?.startDate ?? record?.start_date ?? null,
|
|
74
|
-
record?.endDate ?? record?.end_date ?? null
|
|
75
|
-
);
|
|
73
|
+
const dateSummary = formatDateRange(record?.startDate, record?.endDate);
|
|
76
74
|
const handleSubmit = React.useCallback(async (values) => {
|
|
77
75
|
if (!record?.id) return;
|
|
78
76
|
const payload = buildLeaveRequestPayload(values, { id: record.id });
|
|
@@ -92,15 +90,15 @@ function StaffMyLeaveRequestDetailPage({ params }) {
|
|
|
92
90
|
/* @__PURE__ */ jsxs("div", { className: "mb-6 space-y-2 rounded-lg border bg-card p-4", children: [
|
|
93
91
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [
|
|
94
92
|
/* @__PURE__ */ jsx(Badge, { variant: resolveStatusVariant(status), children: t(`staff.leaveRequests.status.${status}`, status) }),
|
|
95
|
-
record.
|
|
93
|
+
record.decidedAt ? /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
|
|
96
94
|
t("staff.leaveRequests.decision.at", "Decision at"),
|
|
97
95
|
" ",
|
|
98
|
-
formatDateLabel(record.decidedAt
|
|
96
|
+
formatDateLabel(record.decidedAt)
|
|
99
97
|
] }) : null
|
|
100
98
|
] }),
|
|
101
|
-
record.decisionComment
|
|
99
|
+
record.decisionComment ? /* @__PURE__ */ jsxs("div", { className: "text-sm text-muted-foreground", children: [
|
|
102
100
|
/* @__PURE__ */ jsx("div", { className: "font-medium text-foreground", children: t("staff.leaveRequests.decision.comment", "Decision comment") }),
|
|
103
|
-
/* @__PURE__ */ jsx("p", { children: record.decisionComment
|
|
101
|
+
/* @__PURE__ */ jsx("p", { children: record.decisionComment })
|
|
104
102
|
] }) : null
|
|
105
103
|
] }),
|
|
106
104
|
status === "pending" ? /* @__PURE__ */ jsx(
|
|
@@ -152,12 +150,12 @@ function StaffMyLeaveRequestDetailPage({ params }) {
|
|
|
152
150
|
/* @__PURE__ */ jsxs("p", { children: [
|
|
153
151
|
t("staff.leaveRequests.detail.dates", "Dates"),
|
|
154
152
|
": ",
|
|
155
|
-
formatDateRange(record.startDate
|
|
153
|
+
formatDateRange(record.startDate, record.endDate)
|
|
156
154
|
] }),
|
|
157
|
-
record.unavailabilityReasonValue
|
|
155
|
+
record.unavailabilityReasonValue ? /* @__PURE__ */ jsxs("p", { children: [
|
|
158
156
|
t("staff.leaveRequests.detail.reason", "Reason"),
|
|
159
157
|
": ",
|
|
160
|
-
record.unavailabilityReasonValue
|
|
158
|
+
record.unavailabilityReasonValue
|
|
161
159
|
] }) : null,
|
|
162
160
|
record.note ? /* @__PURE__ */ jsxs("p", { children: [
|
|
163
161
|
t("staff.leaveRequests.detail.note", "Note"),
|
|
@@ -167,23 +165,6 @@ function StaffMyLeaveRequestDetailPage({ params }) {
|
|
|
167
165
|
] })
|
|
168
166
|
] }) });
|
|
169
167
|
}
|
|
170
|
-
function resolveStatusVariant(status) {
|
|
171
|
-
if (status === "approved") return "default";
|
|
172
|
-
if (status === "rejected") return "destructive";
|
|
173
|
-
return "secondary";
|
|
174
|
-
}
|
|
175
|
-
function formatDateLabel(value) {
|
|
176
|
-
if (!value) return "";
|
|
177
|
-
const date = new Date(value);
|
|
178
|
-
if (Number.isNaN(date.getTime())) return value;
|
|
179
|
-
return date.toLocaleDateString();
|
|
180
|
-
}
|
|
181
|
-
function formatDateRange(start, end) {
|
|
182
|
-
const startLabel = formatDateLabel(start);
|
|
183
|
-
const endLabel = formatDateLabel(end);
|
|
184
|
-
if (startLabel && endLabel) return `${startLabel} -> ${endLabel}`;
|
|
185
|
-
return startLabel || endLabel || "-";
|
|
186
|
-
}
|
|
187
168
|
export {
|
|
188
169
|
StaffMyLeaveRequestDetailPage as default
|
|
189
170
|
};
|