141 lines
4.4 KiB
TypeScript
141 lines
4.4 KiB
TypeScript
"use client";
|
|
|
|
import { useState, type FormEvent } from "react";
|
|
import { Plus, Save, X } from "lucide-react";
|
|
import { Button } from "@/components/ui/Button";
|
|
import { Textarea } from "@/components/ui/Textarea";
|
|
import { ExperienceItem } from "@/components/cv/ExperienceItem";
|
|
import type { Experience } from "@/lib/types/cv";
|
|
|
|
type ExperienceEditor =
|
|
| { mode: "closed" }
|
|
| { mode: "new" }
|
|
| { mode: "edit"; id: Experience["id"] };
|
|
|
|
interface ExperienceManagerProps {
|
|
experiences: Experience[];
|
|
onAdd: (titre: string, contenu: string) => void;
|
|
onUpdate: (id: string, titre: string, contenu: string) => void;
|
|
onRemove: (id: string) => void;
|
|
}
|
|
|
|
export function ExperienceManager({
|
|
experiences,
|
|
onAdd,
|
|
onUpdate,
|
|
onRemove,
|
|
}: ExperienceManagerProps) {
|
|
const [editor, setEditor] = useState<ExperienceEditor>({ mode: "closed" });
|
|
const [titre, setTitre] = useState("");
|
|
const [contenu, setContenu] = useState("");
|
|
|
|
const isEditing = editor.mode !== "closed";
|
|
const editingId = editor.mode === "edit" ? editor.id : null;
|
|
|
|
function startNew() {
|
|
setEditor({ mode: "new" });
|
|
setTitre("");
|
|
setContenu("");
|
|
}
|
|
|
|
function startEdit(id: string) {
|
|
const experience = experiences.find((exp) => exp.id === id);
|
|
if (!experience) return;
|
|
setEditor({ mode: "edit", id });
|
|
setTitre(experience.titre);
|
|
setContenu(experience.contenu);
|
|
}
|
|
|
|
function close() {
|
|
setEditor({ mode: "closed" });
|
|
setTitre("");
|
|
setContenu("");
|
|
}
|
|
|
|
function handleSubmit(event: FormEvent) {
|
|
event.preventDefault();
|
|
const trimmedTitre = titre.trim();
|
|
const title = trimmedTitre || "Expérience";
|
|
if (editor.mode === "new") {
|
|
onAdd(title, contenu);
|
|
} else if (editor.mode === "edit") {
|
|
onUpdate(editor.id, title, contenu);
|
|
}
|
|
close();
|
|
}
|
|
|
|
return (
|
|
<section aria-labelledby="experiences-title" className="mt-6 border-t border-border pt-4">
|
|
<div className="flex items-center justify-between gap-2">
|
|
<h2 id="experiences-title" className="text-sm font-semibold text-ink">
|
|
Expériences professionnelles
|
|
</h2>
|
|
<Button size="sm" onClick={startNew}>
|
|
<Plus size={14} aria-hidden />
|
|
Ajouter
|
|
</Button>
|
|
</div>
|
|
|
|
{isEditing && (
|
|
<form
|
|
onSubmit={handleSubmit}
|
|
className="mt-3 flex flex-col gap-3 rounded-md border border-accent bg-white p-3"
|
|
>
|
|
<div className="flex flex-col gap-1">
|
|
<label htmlFor="experience-titre" className="text-xs font-medium text-ink">
|
|
Titre
|
|
</label>
|
|
<input
|
|
id="experience-titre"
|
|
type="text"
|
|
value={titre}
|
|
onChange={(event) => setTitre(event.target.value)}
|
|
placeholder="Développeur Full-Stack"
|
|
autoFocus
|
|
className="rounded-md border border-border px-3 py-2 text-sm text-ink placeholder:text-muted focus:outline-2 focus:outline-offset-2 focus:outline-accent"
|
|
/>
|
|
</div>
|
|
<div className="flex flex-col gap-1">
|
|
<label htmlFor="experience-contenu" className="text-xs font-medium text-ink">
|
|
Contenu (Markdown)
|
|
</label>
|
|
<Textarea
|
|
id="experience-contenu"
|
|
value={contenu}
|
|
onChange={(event) => setContenu(event.target.value)}
|
|
placeholder="- Stack : React, Next.js - Missions : …"
|
|
className="min-h-[120px]"
|
|
/>
|
|
</div>
|
|
<div className="flex gap-2">
|
|
<Button type="submit" size="sm">
|
|
<Save size={14} aria-hidden />
|
|
{editingId ? "Enregistrer" : "Ajouter"}
|
|
</Button>
|
|
<Button variant="ghost" size="sm" type="button" onClick={close}>
|
|
<X size={14} aria-hidden />
|
|
Annuler
|
|
</Button>
|
|
</div>
|
|
</form>
|
|
)}
|
|
|
|
<ol className="mt-3 flex flex-col gap-1.5">
|
|
{experiences.map((experience, index) => (
|
|
<ExperienceItem
|
|
key={experience.id}
|
|
index={index}
|
|
experience={experience}
|
|
onEdit={() => startEdit(experience.id)}
|
|
onRemove={() => onRemove(experience.id)}
|
|
/>
|
|
))}
|
|
{experiences.length === 0 && (
|
|
<p className="text-sm text-muted">
|
|
Aucune expérience. Utilisez le bouton « Ajouter » pour commencer.
|
|
</p>
|
|
)}
|
|
</ol>
|
|
</section>
|
|
);
|
|
} |