Jobber/orchestrator/src/client/components/tailoring-utils.ts
Shaheer Sarfaraz 16dd17ebea
Fix Tailor CV adding new skills and restore original skills on revert (#190)
* Initial commit

* refactor slightly

* refactor and fix bugs
2026-02-18 23:23:33 +00:00

137 lines
3.7 KiB
TypeScript

import type { ResumeProfile } from "@shared/types";
export interface TailoredSkillGroup {
name: string;
keywords: string[];
}
export interface EditableSkillGroup {
id: string;
name: string;
keywordsText: string;
}
let skillDraftCounter = 0;
export function createTailoredSkillDraftId(): string {
skillDraftCounter += 1;
return `skill-group-${skillDraftCounter}`;
}
export function parseTailoredSkills(
raw: string | null | undefined,
): TailoredSkillGroup[] {
if (!raw || raw.trim().length === 0) return [];
try {
const parsed = JSON.parse(raw) as unknown;
if (!Array.isArray(parsed)) return [];
const groups: TailoredSkillGroup[] = [];
const legacyKeywords: string[] = [];
for (const item of parsed) {
if (typeof item === "string") {
const keyword = item.trim();
if (keyword.length > 0) legacyKeywords.push(keyword);
continue;
}
if (!item || typeof item !== "object") continue;
const record = item as Record<string, unknown>;
const name = typeof record.name === "string" ? record.name.trim() : "";
const keywordsRaw = Array.isArray(record.keywords)
? record.keywords
: typeof record.keywords === "string"
? record.keywords.split(",")
: [];
const keywords = keywordsRaw
.filter((value): value is string => typeof value === "string")
.map((value) => value.trim())
.filter(Boolean);
if (!name && keywords.length === 0) continue;
groups.push({ name, keywords });
}
if (legacyKeywords.length > 0) {
groups.push({ name: "Skills", keywords: legacyKeywords });
}
return groups;
} catch {
return [];
}
}
export function serializeTailoredSkills(groups: TailoredSkillGroup[]): string {
if (groups.length === 0) return "";
return JSON.stringify(groups);
}
export function toEditableSkillGroups(
groups: TailoredSkillGroup[],
): EditableSkillGroup[] {
return groups.map((group) => ({
id: createTailoredSkillDraftId(),
name: group.name,
keywordsText: group.keywords.join(", "),
}));
}
export function fromEditableSkillGroups(
groups: EditableSkillGroup[],
): TailoredSkillGroup[] {
const normalized: TailoredSkillGroup[] = [];
for (const group of groups) {
const name = group.name.trim();
const keywords = group.keywordsText
.split(",")
.map((value) => value.trim())
.filter(Boolean);
if (!name && keywords.length === 0) continue;
normalized.push({ name, keywords });
}
return normalized;
}
export function getOriginalSummary(profile: ResumeProfile | null): string {
if (!profile) return "";
return profile.basics?.summary?.trim() ?? "";
}
export function getOriginalHeadline(profile: ResumeProfile | null): string {
if (!profile) return "";
return profile.basics?.label?.trim() ?? "";
}
export function getOriginalSkills(
profile: ResumeProfile | null,
): TailoredSkillGroup[] {
if (!profile) return [];
const items = profile.sections?.skills?.items;
if (!Array.isArray(items)) return [];
const groups: TailoredSkillGroup[] = [];
for (const item of items) {
if (!item || typeof item !== "object") continue;
const name =
typeof item.name === "string"
? item.name.trim()
: typeof item.description === "string"
? item.description.trim()
: "";
const keywordsRaw = Array.isArray(item.keywords) ? item.keywords : [];
const keywords = keywordsRaw
.filter((value: unknown): value is string => typeof value === "string")
.map((value: string) => value.trim())
.filter(Boolean);
if (!name && keywords.length === 0) continue;
groups.push({ name, keywords });
}
return groups;
}