Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/apps/web/app/utils/integrations/hrM05Forms.ts
T

222 lines
6.5 KiB
TypeScript

import {
FormDefinitionSchema,
FormSubmissionPayloadSchema,
type FormDefinition,
type FormSubmissionPayload,
} from "@hivegrid/form-widget";
export type HrM05Process = "leave" | "sick" | "request";
export type HrM05Values = FormSubmissionPayload["values"];
export type HrM05Translate = (key: string) => string;
export type HrM05MemberOption = {
userId: string;
displayName: string;
team: string | null;
roleNames: readonly string[];
};
const identity: HrM05Translate = (key) => key;
/**
* `FormFieldSchema.options` accepts at most 50 entries. A tenant with more
* active members used to overflow that cap, which made `FormDefinitionSchema`
* throw inside the loader and turned /hr/leave, /hr/sick and /hr/requests into
* 500s. Capping keeps the routes alive; the picker itself stays server-validated
* (`requireActiveHrMember`), so a member beyond the cap is still a legal target.
*/
const MAX_PICKER_OPTIONS = 50;
function cappedMembers(
members: readonly HrM05MemberOption[],
reservedOptions = 0,
): readonly HrM05MemberOption[] {
return members.slice(0, Math.max(0, MAX_PICKER_OPTIONS - reservedOptions));
}
export function parseHrM05Values(raw: string): HrM05Values | null {
try {
const parsed = FormSubmissionPayloadSchema.safeParse({
values: JSON.parse(raw) as unknown,
});
return parsed.success ? parsed.data.values : null;
} catch {
return null;
}
}
export function buildHrLeaveFormDefinition(
t: HrM05Translate = identity,
members: readonly HrM05MemberOption[] = [],
): FormDefinition {
return FormDefinitionSchema.parse({
title: t("hr.leave.action.new"),
description: t("hr.m05.leaveDescription"),
submitLabel: t("hr.leave.action.submit"),
successMessage: t("hr.m05.success"),
gdprConsentLabel: t("hr.m05.internalConsent"),
captchaEnabled: false,
fields: [
{
key: "requestKind",
type: "select",
label: t("hr.leave.field.kind"),
required: true,
options: [
"vacation",
"special_leave",
"unpaid_leave",
"overtime_compensation",
].map((value) => ({ value, label: t(`hr.leave.kind.${value}`) })),
},
{
key: "startDate",
type: "date",
label: t("hr.leave.field.start"),
required: true,
},
{
key: "endDate",
type: "date",
label: t("hr.leave.field.end"),
required: true,
},
{
key: "note",
type: "textarea",
label: t("hr.leave.field.note"),
required: false,
validationRules: { maxLength: 2000 },
},
...(members.length > 0
? [
{
key: "approverUserId",
type: "select" as const,
label: t("hr.leave.field.approver"),
required: false,
options: [
{ value: "__none__", label: t("hr.picker.noApprover") },
...cappedMembers(members, 1).map((member) => ({
value: member.userId,
label: `${member.displayName} · ${member.team ?? t("hr.picker.noTeam")} · ${
member.roleNames.join(", ") || t("hr.picker.noRole")
}`,
})),
],
},
]
: []),
],
});
}
export function buildHrSickFormDefinition(
t: HrM05Translate = identity,
employeeOptions: readonly HrM05MemberOption[] = [],
): FormDefinition {
return FormDefinitionSchema.parse({
title: t("hr.sick.action.report"),
description: t("hr.m05.sickDescription"),
submitLabel: t("hr.sick.action.report"),
successMessage: t("hr.m05.success"),
gdprConsentLabel: t("hr.m05.internalConsent"),
captchaEnabled: false,
fields: [
{
key: "startDate",
type: "date",
label: t("hr.sick.field.start"),
required: true,
},
{
key: "expectedReturnDate",
type: "date",
label: t("hr.sick.field.expectedReturn"),
required: false,
},
{
key: "note",
type: "textarea",
label: t("hr.leave.field.note"),
required: false,
validationRules: { maxLength: 2000 },
},
...(employeeOptions.length > 0
? [
{
key: "employeeUserId",
type: "select" as const,
label: t("hr.sick.field.employee"),
required: true,
options: cappedMembers(employeeOptions).map((member) => ({
value: member.userId,
label: `${member.displayName} · ${member.team ?? t("hr.picker.noTeam")} · ${
member.roleNames.join(", ") || t("hr.picker.noRole")
}`,
})),
},
]
: []),
],
});
}
export function buildHrRequestFormDefinition(
t: HrM05Translate = identity,
members: readonly HrM05MemberOption[] = [],
): FormDefinition {
return FormDefinitionSchema.parse({
title: t("hr.requests.action.new"),
description: t("hr.m05.requestDescription"),
submitLabel: t("hr.requests.action.submit"),
successMessage: t("hr.m05.success"),
gdprConsentLabel: t("hr.m05.internalConsent"),
captchaEnabled: false,
fields: [
{
key: "requestKind",
type: "select",
label: t("hr.requests.field.kind"),
required: true,
options: ["expense", "hardware", "training", "reference"].map((value) => ({
value,
label: t(`hr.requests.kind.${value}`),
})),
},
{
key: "title",
type: "text",
label: t("hr.requests.field.title"),
required: true,
validationRules: { maxLength: 255 },
},
{
key: "details",
type: "textarea",
label: t("hr.requests.field.details"),
required: false,
validationRules: { maxLength: 8000 },
},
...(members.length > 0
? [
{
key: "approverUserId",
type: "select" as const,
label: t("hr.requests.field.approver"),
required: false,
options: [
{ value: "__none__", label: t("hr.picker.noApprover") },
...cappedMembers(members, 1).map((member) => ({
value: member.userId,
label: `${member.displayName} · ${member.team ?? t("hr.picker.noTeam")} · ${
member.roleNames.join(", ") || t("hr.picker.noRole")
}`,
})),
],
},
]
: []),
],
});
}