Files
CapaKraken/packages/api/src/router/resource-read-models.ts
T
Hartmut 1df208dbcc feat(timeline): add pulse animation for in-flight drag mutations
Allocation bars that have active optimistic overrides (post-drag,
awaiting server confirmation) now pulse subtly via animate-pulse.
The pending set is derived from the existing optimisticAllocations
map keys, requiring no additional state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 13:28:46 +02:00

114 lines
3.9 KiB
TypeScript

import { fmtEur } from "../lib/format-utils.js";
export {
RESOURCE_DIRECTORY_SELECT,
RESOURCE_IDENTIFIER_DETAIL_SELECT,
RESOURCE_IDENTIFIER_SELECT,
RESOURCE_SUMMARY_DETAIL_SELECT,
RESOURCE_SUMMARY_SELECT,
} from "../db/selects.js";
export function mapResourceSummary(resource: {
id: string;
eid: string;
displayName: string;
chapter: string | null;
isActive: boolean;
areaRole: { name: string } | null;
country: { code: string; name: string } | null;
metroCity: { name: string } | null;
orgUnit: { name: string } | null;
}) {
return {
id: resource.id,
eid: resource.eid,
name: resource.displayName,
chapter: resource.chapter,
role: resource.areaRole?.name ?? null,
country: resource.country?.name ?? resource.country?.code ?? null,
countryCode: resource.country?.code ?? null,
metroCity: resource.metroCity?.name ?? null,
orgUnit: resource.orgUnit?.name ?? null,
active: resource.isActive,
};
}
export function mapResourceSummaryDetail(resource: {
id: string;
eid: string;
displayName: string;
chapter: string | null;
fte: number | null;
lcrCents: number | null;
chargeabilityTarget: number | null;
isActive: boolean;
areaRole: { name: string } | null;
country: { code: string; name: string } | null;
metroCity: { name: string } | null;
orgUnit: { name: string } | null;
}) {
return {
id: resource.id,
eid: resource.eid,
name: resource.displayName,
chapter: resource.chapter,
role: resource.areaRole?.name ?? null,
country: resource.country?.name ?? resource.country?.code ?? null,
countryCode: resource.country?.code ?? null,
metroCity: resource.metroCity?.name ?? null,
orgUnit: resource.orgUnit?.name ?? null,
fte: resource.fte,
lcr: resource.lcrCents != null ? fmtEur(resource.lcrCents) : null,
chargeabilityTarget: resource.chargeabilityTarget != null ? `${resource.chargeabilityTarget}%` : null,
active: resource.isActive,
};
}
export function mapResourceDetail(resource: {
id: string;
eid: string;
displayName: string;
email: string | null;
chapter: string | null;
fte: number | null;
lcrCents: number | null;
ucrCents: number | null;
chargeabilityTarget: number | null;
isActive: boolean;
skills: unknown;
postalCode: string | null;
federalState: string | null;
areaRole: { name: string; color: string | null } | null;
country: { code: string; name: string; dailyWorkingHours: number | null } | null;
metroCity: { name: string } | null;
managementLevelGroup: { name: string; targetPercentage: number | null } | null;
orgUnit: { name: string; level: number } | null;
_count: { assignments: number; vacations: number };
}) {
const skills = Array.isArray(resource.skills) ? resource.skills as { name?: string; level?: number }[] : [];
return {
id: resource.id,
eid: resource.eid,
name: resource.displayName,
email: resource.email,
chapter: resource.chapter,
role: resource.areaRole?.name ?? null,
country: resource.country?.name ?? resource.country?.code ?? null,
countryCode: resource.country?.code ?? null,
countryHours: resource.country?.dailyWorkingHours ?? 8,
metroCity: resource.metroCity?.name ?? null,
fte: resource.fte,
lcr: resource.lcrCents != null ? fmtEur(resource.lcrCents) : null,
ucr: resource.ucrCents != null ? fmtEur(resource.ucrCents) : null,
chargeabilityTarget: resource.chargeabilityTarget != null ? `${resource.chargeabilityTarget}%` : null,
managementLevel: resource.managementLevelGroup?.name ?? null,
orgUnit: resource.orgUnit?.name ?? null,
postalCode: resource.postalCode,
federalState: resource.federalState,
active: resource.isActive,
totalAssignments: resource._count.assignments,
totalVacations: resource._count.vacations,
skillCount: skills.length,
topSkills: skills.slice(0, 10).map((skill) => `${skill.name ?? "?"} (${skill.level ?? "?"})`),
};
}