import React from "react";
import { Box, Button, Chip, IconButton, Stack, TextField, Typography } from "@mui/material";
import AddIcon from "@mui/icons-material/Add";
import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline";
import RichTextField from "../../components/RichTextField";
import { useI18n } from "../../i18n/I18nProvider";
import {
joinLines,
splitLines,
StructuredCvContact,
StructuredCvEducation,
StructuredCvFieldMetadata,
StructuredCvJob,
StructuredCvLanguage,
StructuredCvOtherSection,
} from "../../profileCv";
// Career Profile editing sections, extracted from CareerProfilePage (Phase 1 increment 2).
//
// Each section is presentational: it receives its slice of the profile as `value` and reports edits
// through `onChange(next)`. The parent still owns `structuredCv`, all loading, the save
// (PUT /career/profile { profile, cvText }), and every extraction/import action. No section makes an
// API call or holds profile state — so save payloads and extraction behaviour are unchanged.
//
// `getMetadata` is the parent's field-review lookup (getStructuredCvFieldMetadata over the whole
// profile), passed as a callback so sections stay decoupled from the full profile shape.
type MetadataLookup = (path: string) => StructuredCvFieldMetadata | undefined;
// Field-review chip + tone, moved verbatim from CareerProfilePage so the sections and the parent
// share one definition. Behaviour (thresholds, labels, source snippet) is unchanged.
function confidenceTone(confidence?: number) {
if (typeof confidence !== "number") return { label: "Review", color: "default" as const };
if (confidence >= 0.8) return { label: `High ${Math.round(confidence * 100)}%`, color: "success" as const };
if (confidence >= 0.65) return { label: `Medium ${Math.round(confidence * 100)}%`, color: "warning" as const };
return { label: `Low ${Math.round(confidence * 100)}%`, color: "error" as const };
}
export function FieldReviewNote({ metadata }: { metadata?: StructuredCvFieldMetadata }) {
if (!metadata) return null;
const tone = confidenceTone(metadata.confidence);
return (
{metadata.method ? : null}
{metadata.sourceBlockId ? : null}
{metadata.reviewState ? : null}
{metadata.sourceSnippet ? (
{metadata.sourceSnippet}
) : null}
);
}
export function PersonalInformationSection({
value,
onChange,
getMetadata,
}: {
value: StructuredCvContact;
onChange: (next: StructuredCvContact) => void;
getMetadata: MetadataLookup;
}) {
const { t } = useI18n();
const set = (patch: Partial) => onChange({ ...value, ...patch });
return (
set({ fullName: e.target.value || undefined })} fullWidth />
set({ headline: e.target.value || undefined })} fullWidth />
set({ email: e.target.value || undefined })} fullWidth />
set({ phone: e.target.value || undefined })} fullWidth />
set({ location: e.target.value || undefined })} fullWidth />
set({ website: e.target.value || undefined })} fullWidth />
set({ linkedIn: e.target.value || undefined })} fullWidth sx={{ gridColumn: { xs: "1 / -1", md: "1 / -1" } }} />
set({ gitHub: e.target.value || undefined })} fullWidth sx={{ gridColumn: { xs: "1 / -1", md: "1 / -1" } }} />
Other links} onClick={() => set({ links: [...(value.links ?? []), { label: "", url: "" }] })}>Add link
{(value.links ?? []).map((link, index) => set({ links: (value.links ?? []).map((item, itemIndex) => itemIndex === index ? { ...item, label: event.target.value || undefined } : item) })} sx={{ flex: "0 1 180px" }} /> set({ links: (value.links ?? []).map((item, itemIndex) => itemIndex === index ? { ...item, url: event.target.value || undefined } : item) })} fullWidth /> set({ links: (value.links ?? []).filter((_, itemIndex) => itemIndex !== index) })}>)}
);
}
// Shared free-text list editor (one item per line) for Summary / Skills / Interests.
function LinesField({ label, value, onChange, metadata, minRows }: { label: string; value: string[]; onChange: (next: string[]) => void; metadata?: StructuredCvFieldMetadata; minRows: number }) {
const { t } = useI18n();
return (
onChange(splitLines(e.target.value))} helperText={t("profileCvStructuredListHelp")} multiline minRows={minRows} fullWidth />
);
}
export function ProfessionalSummarySection({ value, onChange, getMetadata }: { value: string[]; onChange: (next: string[]) => void; getMetadata: MetadataLookup }) {
const { t } = useI18n();
return (
onChange(splitLines(text))} minRows={5} />
);
}
export function SkillsSection({ value, onChange, getMetadata }: { value: string[]; onChange: (next: string[]) => void; getMetadata: MetadataLookup }) {
const { t } = useI18n();
return ;
}
export function InterestsSection({ value, onChange, getMetadata }: { value: string[]; onChange: (next: string[]) => void; getMetadata: MetadataLookup }) {
const { t } = useI18n();
return ;
}
export function LongTailSections({ values, onChange }: { values: Record<"awards" | "publications" | "organisations" | "references", string[]>; onChange: (key: keyof typeof values, next: string[]) => void }) {
const { t } = useI18n();
const labels = { awards: t("profileCvStructuredAwards"), publications: t("profileCvStructuredPublications"), organisations: t("profileCvStructuredOrganisations"), references: t("profileCvStructuredReferences") };
return {(Object.keys(values) as (keyof typeof values)[]).map((key) => onChange(key, next)} minRows={3} />)};
}
export function LanguagesSection({ value, onChange, getMetadata }: { value: StructuredCvLanguage[]; onChange: (next: StructuredCvLanguage[]) => void; getMetadata: MetadataLookup }) {
const { t } = useI18n();
const update = (index: number, patch: Partial) => onChange(value.map((entry, i) => (i === index ? { ...entry, ...patch } : entry)));
return (
{t("profileCvStructuredLanguages")}
{value.length === 0 ? {t("profileCvStructuredEmpty")} : null}
{value.map((language, index) => (
update(index, { name: e.target.value || undefined })} fullWidth />
update(index, { level: e.target.value || undefined })} fullWidth />
update(index, { notes: e.target.value || undefined })} fullWidth />
))}
);
}
export function WorkExperienceSection({ value, onChange }: { value: StructuredCvJob[]; onChange: (next: StructuredCvJob[]) => void }) {
const { t } = useI18n();
const update = (index: number, patch: Partial) => onChange(value.map((entry, i) => (i === index ? { ...entry, ...patch } : entry)));
return (
{t("profileCvStructuredJobs")}
{value.length === 0 ? {t("profileCvStructuredEmpty")} : null}
{value.map((job, index) => (
update(index, { title: e.target.value || undefined })} fullWidth />
update(index, { company: e.target.value || undefined })} fullWidth />
update(index, { location: e.target.value || undefined })} fullWidth />
update(index, { start: e.target.value || undefined })} fullWidth />
update(index, { end: e.target.value || undefined, isCurrent: /present|current/i.test(e.target.value) || job.isCurrent })} fullWidth />
update(index, { bullets: splitLines(text) })} minRows={5} />
update(index, { skills: splitLines(e.target.value) })} helperText={t("profileCvStructuredListHelp")} multiline minRows={3} fullWidth sx={{ gridColumn: { xs: "1 / -1", md: "1 / -1" } }} />
))}
);
}
export function EducationSection({ value, onChange }: { value: StructuredCvEducation[]; onChange: (next: StructuredCvEducation[]) => void }) {
const { t } = useI18n();
const update = (index: number, patch: Partial) => onChange(value.map((entry, i) => (i === index ? { ...entry, ...patch } : entry)));
return (
{t("profileCvStructuredEducation")}
{value.length === 0 ? {t("profileCvStructuredEmpty")} : null}
{value.map((education, index) => (
update(index, { qualification: e.target.value || undefined })} fullWidth />
update(index, { institution: e.target.value || undefined })} fullWidth />
update(index, { location: e.target.value || undefined })} fullWidth />
update(index, { start: e.target.value || undefined })} fullWidth />
update(index, { end: e.target.value || undefined })} fullWidth />
update(index, { details: splitLines(text) })} minRows={4} />
))}
);
}
export function OtherSectionsSection({ value, onChange }: { value: StructuredCvOtherSection[]; onChange: (next: StructuredCvOtherSection[]) => void }) {
const { t } = useI18n();
const update = (index: number, patch: Partial) => onChange(value.map((entry, i) => (i === index ? { ...entry, ...patch } : entry)));
return (
{t("profileCvStructuredOtherSections")}
{value.length === 0 ? {t("profileCvStructuredEmpty")} : null}
{value.map((section, index) => (
update(index, { title: e.target.value || undefined })} fullWidth />
update(index, { items: splitLines(text) })} minRows={4} />
))}
);
}