diff --git a/src/utils/profileDataMapper.ts b/src/utils/profileDataMapper.ts index 61906a3..0e1ee5f 100644 --- a/src/utils/profileDataMapper.ts +++ b/src/utils/profileDataMapper.ts @@ -291,21 +291,21 @@ export function mapFieldValuesToAvailability(fieldValues: FieldValueResponse[]): return trimmed; }; - // Use backend-resolved labels only. Drop stale/orphan values whose option is no longer - // defined in the profile field (backend returns no label for them) — prevents old - // selections from lingering when admin renames/removes options. + // Drop stale/orphan options. Backend returns `valueLabel[i]` equal to the raw `value[i]` + // when no option match is found (admin renamed/removed the option). Those are orphans — + // skip them entirely so old selections don't linger on the public profile. const resolvedLabels: string[] = []; rawValues.forEach((val, i) => { - const fromBackend = labelValues[i]; - if (fromBackend && typeof fromBackend === 'string' && fromBackend.trim()) { - resolvedLabels.push(formatIfSnakeCase(fromBackend)); - return; - } - // No backend label → likely a stale option; skip unless it's already a human-readable string const raw = String(val || '').trim(); if (!raw) return; - if (/^[a-z0-9]+(_[a-z0-9]+)+$/i.test(raw)) return; // skip snake_case orphans - resolvedLabels.push(formatIfSnakeCase(raw)); + const fromBackend = labelValues[i]; + const hasResolvedLabel = + fromBackend && + typeof fromBackend === 'string' && + fromBackend.trim() && + fromBackend !== raw; // backend-resolved label must DIFFER from raw value + if (!hasResolvedLabel) return; // orphan — drop + resolvedLabels.push(formatIfSnakeCase(fromBackend)); }); // Deduplicate by normalized comparison (case/whitespace insensitive) @@ -319,15 +319,18 @@ export function mapFieldValuesToAvailability(fieldValues: FieldValueResponse[]): } } - // Determine availability type based on values OR resolved labels - const allValues = [...rawValues.map(v => String(v)), ...resolvedLabels]; - let type = 'Available'; - if (allValues.some(v => matchesKeyword(v, fullTimeKeywords))) { - type = 'Full-time'; - } else if (allValues.some(v => matchesKeyword(v, partTimeKeywords))) { - type = 'Part-time'; - } else if (allValues.some(v => matchesKeyword(v, flexibleKeywords))) { - type = 'Flexible'; + // Determine availability type from resolved labels only (orphan values were already dropped). + // If nothing valid remains, don't fabricate a type. + let type = ''; + if (resolvedLabels.length > 0) { + type = 'Available'; + if (resolvedLabels.some(v => matchesKeyword(v, fullTimeKeywords))) { + type = 'Full-time'; + } else if (resolvedLabels.some(v => matchesKeyword(v, partTimeKeywords))) { + type = 'Part-time'; + } else if (resolvedLabels.some(v => matchesKeyword(v, flexibleKeywords))) { + type = 'Flexible'; + } } return { type, schedule, label };