feat: ajouter la section Compétences (6e bloc)
This commit is contained in:
1 parent
a0d312a884
commit
92aaafb4aa
13 files changed
+75
-47
No files matched your search
@@ -91,13 +91,13 @@ cv-maker/
|
|||||||
│ ├── CvWorkspace.tsx # Assemblage 2 colonnes + toolbar
|
│ ├── CvWorkspace.tsx # Assemblage 2 colonnes + toolbar
|
||||||
│ ├── CvToolbar.tsx # Barre d'app (PDF, JSON, liens)
|
│ ├── CvToolbar.tsx # Barre d'app (PDF, JSON, liens)
|
||||||
│ ├── CvInputPanel.tsx # Panneau gauche (tabs + expériences)
|
│ ├── CvInputPanel.tsx # Panneau gauche (tabs + expériences)
|
||||||
│ ├── CvTabs.tsx # Onglets 1..5
|
│ ├── CvTabs.tsx # Onglets 1..6
|
||||||
│ ├── MarkdownTextarea.tsx # Textarea contrôlé
|
│ ├── MarkdownTextarea.tsx # Textarea contrôlé
|
||||||
│ ├── ExperienceManager.tsx # Formulaire + liste expériences
|
│ ├── ExperienceManager.tsx # Formulaire + liste expériences
|
||||||
│ ├── ExperienceItem.tsx # Ligne « n. titre [Modifier] [×] »
|
│ ├── ExperienceItem.tsx # Ligne « n. titre [Modifier] [×] »
|
||||||
│ ├── CvPreview.tsx # Feuille A4
|
│ ├── CvPreview.tsx # Feuille A4
|
||||||
│ ├── CvHeader.tsx # Accroche (zone 1)
|
│ ├── CvHeader.tsx # Accroche (zone 1)
|
||||||
│ ├── CvLeftColumn.tsx # Sections 2→5 (1/3)
|
│ ├── CvLeftColumn.tsx # Sections 2→6 (1/3)
|
||||||
│ ├── CvSection.tsx # Section générique
|
│ ├── CvSection.tsx # Section générique
|
||||||
│ ├── CvRightColumn.tsx # Expériences pro (2/3)
|
│ ├── CvRightColumn.tsx # Expériences pro (2/3)
|
||||||
│ └── CvExperiencePreview.tsx
|
│ └── CvExperiencePreview.tsx
|
||||||
@@ -131,8 +131,9 @@ interface CVCareerData {
|
|||||||
accroche: string; // onglet 1 — message d'accroche
|
accroche: string; // onglet 1 — message d'accroche
|
||||||
etatCivil: string; // onglet 2 — état civil + contacts + site + GitHub
|
etatCivil: string; // onglet 2 — état civil + contacts + site + GitHub
|
||||||
formations: string; // onglet 3 — formations
|
formations: string; // onglet 3 — formations
|
||||||
softSkills: string; // onglet 4 — soft skills
|
competences: string; // onglet 4 — compétences techniques
|
||||||
centresInteret: string; // onglet 5 — centres d'intérêt
|
softSkills: string; // onglet 5 — soft skills
|
||||||
|
centresInteret: string; // onglet 6 — centres d'intérêt
|
||||||
experiences: Experience[];
|
experiences: Experience[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +146,7 @@ interface Experience {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- Les 5 zones sont du **Markdown brut** (le HTML rendu n'est jamais stocké).
|
- Les 6 zones sont du **Markdown brut** (le HTML rendu n'est jamais stocké).
|
||||||
- Les expériences sont affichées **dans l'ordre de saisie** : la 1ʳᵉ ajoutée en haut de la section « Expériences professionnelles », la suivante en dessous, etc. (la 1ʳᵉ porte le numéro 1).
|
- Les expériences sont affichées **dans l'ordre de saisie** : la 1ʳᵉ ajoutée en haut de la section « Expériences professionnelles », la suivante en dessous, etc. (la 1ʳᵉ porte le numéro 1).
|
||||||
- Sauvegarde invalide/corrompue → état par défaut, sans crash.
|
- Sauvegarde invalide/corrompue → état par défaut, sans crash.
|
||||||
|
|
||||||
|
|||||||
@@ -22,9 +22,10 @@ export default function DocumentationPage() {
|
|||||||
<h2 className="text-xl font-semibold text-ink">Prise en main</h2>
|
<h2 className="text-xl font-semibold text-ink">Prise en main</h2>
|
||||||
<ol className="mt-3 list-decimal space-y-2 pl-5 text-sm leading-relaxed text-ink">
|
<ol className="mt-3 list-decimal space-y-2 pl-5 text-sm leading-relaxed text-ink">
|
||||||
<li>
|
<li>
|
||||||
<strong>Onglets 1 à 5</strong> — sélectionnez une zone de saisie (accroche,
|
<strong>Onglets 1 à 6</strong> — sélectionnez une zone de saisie (accroche,
|
||||||
état civil + contacts, formations, soft skills, centres d'intérêt).
|
état civil + contacts, formations, compétences, soft skills, centres
|
||||||
Un seul onglet est actif à la fois ; chaque zone conserve son texte.
|
d'intérêt). Un seul onglet est actif à la fois ; chaque zone conserve son
|
||||||
|
texte.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<strong>Saisie Markdown</strong> — le texte est écrit en Markdown dans le
|
<strong>Saisie Markdown</strong> — le texte est écrit en Markdown dans le
|
||||||
@@ -64,7 +65,7 @@ export default function DocumentationPage() {
|
|||||||
<section className="mt-8">
|
<section className="mt-8">
|
||||||
<h2 className="text-xl font-semibold text-ink">Syntaxe Markdown supportée</h2>
|
<h2 className="text-xl font-semibold text-ink">Syntaxe Markdown supportée</h2>
|
||||||
<p className="mt-2 text-sm leading-relaxed text-muted">
|
<p className="mt-2 text-sm leading-relaxed text-muted">
|
||||||
Les 5 zones et le contenu des expériences sont rendus en Markdown (GFM).
|
Les 6 zones et le contenu des expériences sont rendus en Markdown (GFM).
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-4 overflow-x-auto rounded-md border border-border bg-white">
|
<div className="mt-4 overflow-x-auto rounded-md border border-border bg-white">
|
||||||
<table className="w-full text-sm text-ink">
|
<table className="w-full text-sm text-ink">
|
||||||
@@ -105,9 +106,9 @@ export default function DocumentationPage() {
|
|||||||
<p className="mt-2 text-sm leading-relaxed text-ink">
|
<p className="mt-2 text-sm leading-relaxed text-ink">
|
||||||
Utilisez l'onglet 1 pour le nom et le poste visé, l'onglet 2 pour vos
|
Utilisez l'onglet 1 pour le nom et le poste visé, l'onglet 2 pour vos
|
||||||
coordonnées (e-mail, téléphone, site web, GitHub), l'onglet 3 pour vos
|
coordonnées (e-mail, téléphone, site web, GitHub), l'onglet 3 pour vos
|
||||||
formations, l'onglet 4 pour vos soft skills et l'onglet 5 pour vos
|
formations, l'onglet 4 pour vos compétences techniques, l'onglet 5 pour
|
||||||
centres d'intérêt. Complétez avec vos expériences professionnelles, de la plus
|
vos soft skills et l'onglet 6 pour vos centres d'intérêt. Complétez avec
|
||||||
récente à la plus ancienne.
|
vos expériences professionnelles, de la plus récente à la plus ancienne.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ const PLACEHOLDERS: Record<CVTabId, string> = {
|
|||||||
"**Jean Dupont**\n- jean.dupont@email.fr\n- 06 12 34 56 78\n- [Site web](https://mon-site.fr)\n- [GitHub](https://github.com/jean-dupont)",
|
"**Jean Dupont**\n- jean.dupont@email.fr\n- 06 12 34 56 78\n- [Site web](https://mon-site.fr)\n- [GitHub](https://github.com/jean-dupont)",
|
||||||
formations:
|
formations:
|
||||||
"### Master Informatique\nUniversité — 2020\n\n### Licence Informatique\nUniversité — 2018",
|
"### Master Informatique\nUniversité — 2020\n\n### Licence Informatique\nUniversité — 2018",
|
||||||
|
competences:
|
||||||
|
"- JavaScript / TypeScript\n- React / Next.js\n- Node.js\n- SQL",
|
||||||
softSkills:
|
softSkills:
|
||||||
"- Esprit d'équipe\n- Autonomie\n- Curiosité technique",
|
"- Esprit d'équipe\n- Autonomie\n- Curiosité technique",
|
||||||
centresInteret:
|
centresInteret:
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { CvSection } from "@/components/cv/CvSection";
|
|||||||
interface CvLeftColumnProps {
|
interface CvLeftColumnProps {
|
||||||
etatCivilHtml: string;
|
etatCivilHtml: string;
|
||||||
formationsHtml: string;
|
formationsHtml: string;
|
||||||
|
competencesHtml: string;
|
||||||
softSkillsHtml: string;
|
softSkillsHtml: string;
|
||||||
centresInteretHtml: string;
|
centresInteretHtml: string;
|
||||||
}
|
}
|
||||||
@@ -10,6 +11,7 @@ interface CvLeftColumnProps {
|
|||||||
export function CvLeftColumn({
|
export function CvLeftColumn({
|
||||||
etatCivilHtml,
|
etatCivilHtml,
|
||||||
formationsHtml,
|
formationsHtml,
|
||||||
|
competencesHtml,
|
||||||
softSkillsHtml,
|
softSkillsHtml,
|
||||||
centresInteretHtml,
|
centresInteretHtml,
|
||||||
}: CvLeftColumnProps) {
|
}: CvLeftColumnProps) {
|
||||||
@@ -17,6 +19,7 @@ export function CvLeftColumn({
|
|||||||
<aside>
|
<aside>
|
||||||
<CvSection title="État civil" html={etatCivilHtml} />
|
<CvSection title="État civil" html={etatCivilHtml} />
|
||||||
<CvSection title="Formations" html={formationsHtml} />
|
<CvSection title="Formations" html={formationsHtml} />
|
||||||
|
<CvSection title="Compétences" html={competencesHtml} />
|
||||||
<CvSection title="Soft skills" html={softSkillsHtml} />
|
<CvSection title="Soft skills" html={softSkillsHtml} />
|
||||||
<CvSection title="Centres d'intérêt" html={centresInteretHtml} />
|
<CvSection title="Centres d'intérêt" html={centresInteretHtml} />
|
||||||
</aside>
|
</aside>
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ export function CvPreview({ data }: { data: CVCareerData }) {
|
|||||||
const accrocheHtml = useMemo(() => markdownToHtml(data.accroche), [data.accroche]);
|
const accrocheHtml = useMemo(() => markdownToHtml(data.accroche), [data.accroche]);
|
||||||
const etatCivilHtml = useMemo(() => markdownToHtml(data.etatCivil), [data.etatCivil]);
|
const etatCivilHtml = useMemo(() => markdownToHtml(data.etatCivil), [data.etatCivil]);
|
||||||
const formationsHtml = useMemo(() => markdownToHtml(data.formations), [data.formations]);
|
const formationsHtml = useMemo(() => markdownToHtml(data.formations), [data.formations]);
|
||||||
|
const competencesHtml = useMemo(
|
||||||
|
() => markdownToHtml(data.competences),
|
||||||
|
[data.competences],
|
||||||
|
);
|
||||||
const softSkillsHtml = useMemo(() => markdownToHtml(data.softSkills), [data.softSkills]);
|
const softSkillsHtml = useMemo(() => markdownToHtml(data.softSkills), [data.softSkills]);
|
||||||
const centresInteretHtml = useMemo(
|
const centresInteretHtml = useMemo(
|
||||||
() => markdownToHtml(data.centresInteret),
|
() => markdownToHtml(data.centresInteret),
|
||||||
@@ -34,6 +38,7 @@ export function CvPreview({ data }: { data: CVCareerData }) {
|
|||||||
<CvLeftColumn
|
<CvLeftColumn
|
||||||
etatCivilHtml={etatCivilHtml}
|
etatCivilHtml={etatCivilHtml}
|
||||||
formationsHtml={formationsHtml}
|
formationsHtml={formationsHtml}
|
||||||
|
competencesHtml={competencesHtml}
|
||||||
softSkillsHtml={softSkillsHtml}
|
softSkillsHtml={softSkillsHtml}
|
||||||
centresInteretHtml={centresInteretHtml}
|
centresInteretHtml={centresInteretHtml}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ CV-Maker est une application web **sans backend** permettant à un utilisateur d
|
|||||||
│ Panneau GAUCHE │ Panneau DROIT │
|
│ Panneau GAUCHE │ Panneau DROIT │
|
||||||
│ 1/3 de la largeur │ 2/3 de la largeur │
|
│ 1/3 de la largeur │ 2/3 de la largeur │
|
||||||
│ │ │
|
│ │ │
|
||||||
│ [Tabs 1 ▸ 2 ▸ 3 ▸ 4 ▸ 5] │ ┌────────────────────────┐ │
|
│ [Tabs 1 ▸ 2 ▸ 3 ▸ 4 ▸ 5 ▸ 6] │ ┌────────────────────────┐ │
|
||||||
│ │ │ Feuille A4 │ │
|
│ │ │ Feuille A4 │ │
|
||||||
│ [MarkdownTextarea : │ │ ┌────────────────────┐ │ │
|
│ [MarkdownTextarea : │ │ ┌────────────────────┐ │ │
|
||||||
│ zone active] │ │ │ Header accroche │ │ │
|
│ zone active] │ │ │ Header accroche │ │ │
|
||||||
@@ -50,6 +50,8 @@ CV-Maker est une application web **sans backend** permettant à un utilisateur d
|
|||||||
│ • 1. Titre exp [Modif] [×] │ │ │ civil │ profession.│ │ │
|
│ • 1. Titre exp [Modif] [×] │ │ │ civil │ profession.│ │ │
|
||||||
│ • 2. Titre exp [Modif] [×] │ │ │ Forma-│ ... │ │ │
|
│ • 2. Titre exp [Modif] [×] │ │ │ Forma-│ ... │ │ │
|
||||||
│ │ │ │ tions │ │ │ │
|
│ │ │ │ tions │ │ │ │
|
||||||
|
│ │ │ │ Compé- │ │ │ │
|
||||||
|
│ │ │ │ tences │ │ │ │
|
||||||
│ │ │ │ Soft │ │ │ │
|
│ │ │ │ Soft │ │ │ │
|
||||||
│ │ │ │ skills│ │ │ │
|
│ │ │ │ skills│ │ │ │
|
||||||
│ │ │ │ Centr.│ │ │ │
|
│ │ │ │ Centr.│ │ │ │
|
||||||
@@ -63,15 +65,16 @@ CV-Maker est une application web **sans backend** permettant à un utilisateur d
|
|||||||
|
|
||||||
#### Structure
|
#### Structure
|
||||||
|
|
||||||
- **Zones 1 à 5** gérées par un **système d'onglets (tabs)** numérotés de 1 à 5. Un seul onglet actif à la fois ; chaque onglet affiche une `textarea` Markdown dédiée.
|
- **Zones 1 à 6** gérées par un **système d'onglets (tabs)** numérotés de 1 à 6. Un seul onglet actif à la fois ; chaque onglet affiche une `textarea` Markdown dédiée.
|
||||||
|
|
||||||
| N° onglet | Zone | Champ(s) du CV associé(s) | Préfixe de navigation |
|
| N° onglet | Zone | Champ(s) du CV associé(s) | Préfixe de navigation |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| 1 | Message d'accroche | En-tête du CV (sous le nom/poste) | |
|
| 1 | Message d'accroche | En-tête du CV (sous le nom/poste) | |
|
||||||
| 2 | État civil + contacts | Nom, prénom, adresse, e-mail, téléphone, **site web**, **lien GitHub** | |
|
| 2 | État civil + contacts | Nom, prénom, adresse, e-mail, téléphone, **site web**, **lien GitHub** | |
|
||||||
| 3 | Formations | Liste des formations / diplômes | |
|
| 3 | Formations | Liste des formations / diplômes | |
|
||||||
| 4 | Soft skills | Compétences comportementales | |
|
| 4 | Compétences | Compétences techniques (langages, frameworks, outils) | |
|
||||||
| 5 | Centres d'intérêt | Loisirs, activités | |
|
| 5 | Soft skills | Compétences comportementales | |
|
||||||
|
| 6 | Centres d'intérêt | Loisirs, activités | |
|
||||||
|
|
||||||
- **Zone « Expériences »** : fonctionne **hors du système d'onglets**. Elle est toujours visible sous la zone d'onglets. Elle sert à saisir **toutes les expériences professionnelles**, une par une.
|
- **Zone « Expériences »** : fonctionne **hors du système d'onglets**. Elle est toujours visible sous la zone d'onglets. Elle sert à saisir **toutes les expériences professionnelles**, une par une.
|
||||||
|
|
||||||
@@ -88,11 +91,12 @@ CV-Maker est une application web **sans backend** permettant à un utilisateur d
|
|||||||
|
|
||||||
- Une seule feuille blanche au **format A4** (210 × 297 mm), avec une marge définie, représentant 1 page de CV.
|
- Une seule feuille blanche au **format A4** (210 × 297 mm), avec une marge définie, représentant 1 page de CV.
|
||||||
- **Header** (toute la largeur de la feuille) : rendu de l'**onglet 1 — message d'accroche**.
|
- **Header** (toute la largeur de la feuille) : rendu de l'**onglet 1 — message d'accroche**.
|
||||||
- **Colonne gauche (1/3 de la largeur utile)** : rendus des sections correspondant aux **onglets 2 à 5** :
|
- **Colonne gauche (1/3 de la largeur utile)** : rendus des sections correspondant aux **onglets 2 à 6** :
|
||||||
- État civil (zone 2) : contacts, site web, GitHub ;
|
- État civil (zone 2) : contacts, site web, GitHub ;
|
||||||
- Formations (zone 3) ;
|
- Formations (zone 3) ;
|
||||||
- Soft skills (zone 4) ;
|
- Compétences (zone 4) : compétences techniques ;
|
||||||
- Centres d'intérêt (zone 5).
|
- Soft skills (zone 5) ;
|
||||||
|
- Centres d'intérêt (zone 6).
|
||||||
- **Colonne droite (2/3 de la largeur utile)** : rendu des **expériences professionnelles**, sous le titre fixe **« Expériences professionnelles »**.
|
- **Colonne droite (2/3 de la largeur utile)** : rendu des **expériences professionnelles**, sous le titre fixe **« Expériences professionnelles »**.
|
||||||
- Le rendu est **temps réel** : toute frappe dans une zone de saisie met à jour la zone cible correspondante instantanément.
|
- Le rendu est **temps réel** : toute frappe dans une zone de saisie met à jour la zone cible correspondante instantanément.
|
||||||
|
|
||||||
|
|||||||
@@ -108,13 +108,13 @@ components/
|
|||||||
├── CvWorkspace.tsx → layout 2 colonnes + barre d'app
|
├── CvWorkspace.tsx → layout 2 colonnes + barre d'app
|
||||||
├── CvToolbar.tsx → barre d'application (print, export/import JSON, liens)
|
├── CvToolbar.tsx → barre d'application (print, export/import JSON, liens)
|
||||||
├── CvInputPanel.tsx → panneau gauche
|
├── CvInputPanel.tsx → panneau gauche
|
||||||
│ ├── CvTabs.tsx → tabs 1..5 (labels + numéros)
|
│ ├── CvTabs.tsx → tabs 1..6 (labels + numéros)
|
||||||
│ ├── MarkdownTextarea.tsx → {label, value, onChange} (textarea md)
|
│ ├── MarkdownTextarea.tsx → {label, value, onChange} (textarea md)
|
||||||
│ └── ExperienceManager.tsx
|
│ └── ExperienceManager.tsx
|
||||||
│ └── ExperienceItem.tsx → ligne « n. titre [Modifier] [×] »
|
│ └── ExperienceItem.tsx → ligne « n. titre [Modifier] [×] »
|
||||||
└── CvPreview.tsx → panneau droit (feuille A4)
|
└── CvPreview.tsx → panneau droit (feuille A4)
|
||||||
├── CvHeader.tsx → accroche (zone 1)
|
├── CvHeader.tsx → accroche (zone 1)
|
||||||
├── CvLeftColumn.tsx → 1/3 (zones 2-5)
|
├── CvLeftColumn.tsx → 1/3 (zones 2-6)
|
||||||
│ └── CvSection.tsx → section générique {titre, html}
|
│ └── CvSection.tsx → section générique {titre, html}
|
||||||
└── CvRightColumn.tsx → 2/3 (Expériences professionnelles)
|
└── CvRightColumn.tsx → 2/3 (Expériences professionnelles)
|
||||||
└── CvExperiencePreview.tsx
|
└── CvExperiencePreview.tsx
|
||||||
|
|||||||
@@ -5,26 +5,28 @@
|
|||||||
- **Aucun serveur** : le « modèle de données » = structure de l'état applicatif + son image persistée dans **localStorage**.
|
- **Aucun serveur** : le « modèle de données » = structure de l'état applicatif + son image persistée dans **localStorage**.
|
||||||
- **Une seule clé de stockage** versionnée : `cv-maker:v1`.
|
- **Une seule clé de stockage** versionnée : `cv-maker:v1`.
|
||||||
- **Toutes les zones sont du Markdown brut** (chaînes de caractères). Le rendu HTML est calculé à la volée, jamais stocké.
|
- **Toutes les zones sont du Markdown brut** (chaînes de caractères). Le rendu HTML est calculé à la volée, jamais stocké.
|
||||||
- Les expériences sont une **liste d'objets** (avec identifiant stable), contrairement aux 5 zones simples.
|
- Les expériences sont une **liste d'objets** (avec identifiant stable), contrairement aux 6 zones simples.
|
||||||
|
|
||||||
## 2. Contracts TypeScript
|
## 2. Contracts TypeScript
|
||||||
|
|
||||||
`lib/types/cv.ts` :
|
`lib/types/cv.ts` :
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
/** Identifiant des 5 zones de saisie éditées par onglets. */
|
/** Identifiant des 6 zones de saisie éditées par onglets. */
|
||||||
export type CVTabId =
|
export type CVTabId =
|
||||||
| "accroche"
|
| "accroche"
|
||||||
| "etatCivil"
|
| "etatCivil"
|
||||||
| "formations"
|
| "formations"
|
||||||
|
| "competences"
|
||||||
| "softSkills"
|
| "softSkills"
|
||||||
| "centresInteret";
|
| "centresInteret";
|
||||||
|
|
||||||
/** Ordre d'affichage des onglets (1 → 5). */
|
/** Ordre d'affichage des onglets (1 → 6). */
|
||||||
export const CV_TAB_ORDER: CVTabId[] = [
|
export const CV_TAB_ORDER: CVTabId[] = [
|
||||||
"accroche",
|
"accroche",
|
||||||
"etatCivil",
|
"etatCivil",
|
||||||
"formations",
|
"formations",
|
||||||
|
"competences",
|
||||||
"softSkills",
|
"softSkills",
|
||||||
"centresInteret",
|
"centresInteret",
|
||||||
];
|
];
|
||||||
@@ -32,7 +34,7 @@ export const CV_TAB_ORDER: CVTabId[] = [
|
|||||||
/** Métadonnées d'affichage d'un onglet. */
|
/** Métadonnées d'affichage d'un onglet. */
|
||||||
export interface CVTabMeta {
|
export interface CVTabMeta {
|
||||||
id: CVTabId;
|
id: CVTabId;
|
||||||
numero: 1 | 2 | 3 | 4 | 5; // numéro affiché sur l'onglet
|
numero: 1 | 2 | 3 | 4 | 5 | 6; // numéro affiché sur l'onglet
|
||||||
label: string; // libellé court de l'onglet
|
label: string; // libellé court de l'onglet
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,8 +42,9 @@ export const CV_TABS: Record<CVTabId, CVTabMeta> = {
|
|||||||
accroche: { id: "accroche", numero: 1, label: "Accroche" },
|
accroche: { id: "accroche", numero: 1, label: "Accroche" },
|
||||||
etatCivil: { id: "etatCivil", numero: 2, label: "État civil" },
|
etatCivil: { id: "etatCivil", numero: 2, label: "État civil" },
|
||||||
formations: { id: "formations", numero: 3, label: "Formations" },
|
formations: { id: "formations", numero: 3, label: "Formations" },
|
||||||
softSkills: { id: "softSkills", numero: 4, label: "Soft skills" },
|
competences: { id: "competences", numero: 4, label: "Compétences" },
|
||||||
centresInteret: { id: "centresInteret", numero: 5, label: "Centres d'intérêt" },
|
softSkills: { id: "softSkills", numero: 5, label: "Soft skills" },
|
||||||
|
centresInteret: { id: "centresInteret", numero: 6, label: "Centres d'intérêt" },
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Une expérience professionnelle. */
|
/** Une expérience professionnelle. */
|
||||||
@@ -59,8 +62,9 @@ export interface CVCareerData {
|
|||||||
accroche: string; // zone 1
|
accroche: string; // zone 1
|
||||||
etatCivil: string; // zone 2
|
etatCivil: string; // zone 2
|
||||||
formations: string; // zone 3
|
formations: string; // zone 3
|
||||||
softSkills: string; // zone 4
|
competences: string; // zone 4
|
||||||
centresInteret: string; // zone 5
|
softSkills: string; // zone 5
|
||||||
|
centresInteret: string; // zone 6
|
||||||
experiences: Experience[]; // liste, ordre = ordre d'affichage dans le CV
|
experiences: Experience[]; // liste, ordre = ordre d'affichage dans le CV
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,6 +85,7 @@ export function createDefaultCVData(): CVCareerData {
|
|||||||
accroche: "",
|
accroche: "",
|
||||||
etatCivil: "",
|
etatCivil: "",
|
||||||
formations: "",
|
formations: "",
|
||||||
|
competences: "",
|
||||||
softSkills: "",
|
softSkills: "",
|
||||||
centresInteret: "",
|
centresInteret: "",
|
||||||
experiences: [],
|
experiences: [],
|
||||||
@@ -141,7 +146,7 @@ Regroupe toute la logique d'édition du CV, façade unique pour les composants :
|
|||||||
export interface UseCVDataReturn {
|
export interface UseCVDataReturn {
|
||||||
data: CVCareerData;
|
data: CVCareerData;
|
||||||
|
|
||||||
setText(tab: CVTabId, value: string): void; // édition d'une zone 1..5
|
setText(tab: CVTabId, value: string): void; // édition d'une zone 1..6
|
||||||
addExperience(titre: string, contenu: string): void; // ajoute en fin de liste
|
addExperience(titre: string, contenu: string): void; // ajoute en fin de liste
|
||||||
updateExperience(id: string, titre: string, contenu: string): void;
|
updateExperience(id: string, titre: string, contenu: string): void;
|
||||||
removeExperience(id: string): void; // suppression + confirmation côté UI
|
removeExperience(id: string): void; // suppression + confirmation côté UI
|
||||||
|
|||||||
@@ -44,10 +44,10 @@ Rendre les liens actifs en `font-semibold` et inactifs en `text-muted` (page cou
|
|||||||
|
|
||||||
## 3. Panneau latéral gauche
|
## 3. Panneau latéral gauche
|
||||||
|
|
||||||
### 3.1 `CvTabs` — onglets 1 à 5
|
### 3.1 `CvTabs` — onglets 1 à 6
|
||||||
|
|
||||||
- Basé sur la primitive `ui/Tabs` : `role="tablist"`, boutons `role="tab"`, `aria-selected`, `role="tabpanel"`.
|
- Basé sur la primitive `ui/Tabs` : `role="tablist"`, boutons `role="tab"`, `aria-selected`, `role="tabpanel"`.
|
||||||
- Affichage : barre de 5 boutons horizontaux, libellé = `numéro + label` (`1 · Accroche`, `2 · État civil`, …).
|
- Affichage : barre de 6 boutons horizontaux, libellé = `numéro + label` (`1 · Accroche`, `2 · État civil`, …).
|
||||||
- Onglet actif : fond `accent`, texte blanc ; inactifs : bordure `border` + texte `muted`.
|
- Onglet actif : fond `accent`, texte blanc ; inactifs : bordure `border` + texte `muted`.
|
||||||
- **Contenu piloté par le parent** (`activeTab` dans `CvInputPanel`) : un seul `MarkdownTextarea` monté pour l'onglet actif. Le texte de l'onglet actif est le `value` contrôlé.
|
- **Contenu piloté par le parent** (`activeTab` dans `CvInputPanel`) : un seul `MarkdownTextarea` monté pour l'onglet actif. Le texte de l'onglet actif est le `value` contrôlé.
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ Rendre les liens actifs en `font-semibold` et inactifs en `text-muted` (page cou
|
|||||||
|
|
||||||
Composition :
|
Composition :
|
||||||
- **En-tête** : titre « Expériences » + bouton **+** (`ui/Button`, variante `primary`) → prépare une nouvelle expérience (zones titre/contenu vidées, focus du formulaire).
|
- **En-tête** : titre « Expériences » + bouton **+** (`ui/Button`, variante `primary`) → prépare une nouvelle expérience (zones titre/contenu vidées, focus du formulaire).
|
||||||
- **Formulaire** : deux champs — `titre` (input texte) + `contenu` (textarea Markdown, même composant que les zones 1-5). Boutons : **Enregistrer** et **Annuler** (annulation quand modification).
|
- **Formulaire** : deux champs — `titre` (input texte) + `contenu` (textarea Markdown, même composant que les zones 1-6). Boutons : **Enregistrer** et **Annuler** (annulation quand modification).
|
||||||
- **Liste numérotée** : sous le formulaire.
|
- **Liste numérotée** : sous le formulaire.
|
||||||
|
|
||||||
États internes suivants :
|
États internes suivants :
|
||||||
@@ -118,13 +118,14 @@ Ratio 1/3–2/3 : `.cv-sheet-body { display:grid; grid-template-columns: 1fr 2fr
|
|||||||
- Rendu Markdown de `data.accroche` (contenu typiquement : nom + poste + accroche).
|
- Rendu Markdown de `data.accroche` (contenu typiquement : nom + poste + accroche).
|
||||||
- `border-bottom` de séparation avec le corps de feuille.
|
- `border-bottom` de séparation avec le corps de feuille.
|
||||||
|
|
||||||
### 4.3 `CvLeftColumn` (sections 2 à 5, 1/3)
|
### 4.3 `CvLeftColumn` (sections 2 à 6, 1/3)
|
||||||
|
|
||||||
Section générique `CvSection` :
|
Section générique `CvSection` :
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
<CvSection title="État civil" html={htmlEtatCivil} />
|
<CvSection title="État civil" html={htmlEtatCivil} />
|
||||||
<CvSection title="Formations" html={htmlFormations} />
|
<CvSection title="Formations" html={htmlFormations} />
|
||||||
|
<CvSection title="Compétences" html={htmlCompetences} />
|
||||||
<CvSection title="Soft skills" html={htmlSoftSkills} />
|
<CvSection title="Soft skills" html={htmlSoftSkills} />
|
||||||
<CvSection title="Centres d'intérêt" html={htmlCentresInteret} />
|
<CvSection title="Centres d'intérêt" html={htmlCentresInteret} />
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -148,9 +148,9 @@ Contrôle visuel : saisir du texte dans chaque onglet, ajouter/modifier/supprime
|
|||||||
|
|
||||||
Créer dans `components/cv/` :
|
Créer dans `components/cv/` :
|
||||||
|
|
||||||
1. `CvPreview.tsx` — wrapper feuille (dimensions `210mm`/`297mm`, ombre, classe d'impression) ; appelle `markdownToHtml` depuis `lib/markdown` pour les 5 zones + chaque expérience ; compose header/colonnes.
|
1. `CvPreview.tsx` — wrapper feuille (dimensions `210mm`/`297mm`, ombre, classe d'impression) ; appelle `markdownToHtml` depuis `lib/markdown` pour les 6 zones + chaque expérience ; compose header/colonnes.
|
||||||
2. `CvHeader.tsx` — accroche (zone 1).
|
2. `CvHeader.tsx` — accroche (zone 1).
|
||||||
3. `CvLeftColumn.tsx` — 4 × `CvSection` (État civil, Formations, Soft skills, Centres d'intérêt).
|
3. `CvLeftColumn.tsx` — 5 × `CvSection` (État civil, Formations, Compétences, Soft skills, Centres d'intérêt).
|
||||||
4. `CvSection.tsx` — titre stylé + `dangerouslySetInnerHTML` (HTML assaini) ; masquée si vide.
|
4. `CvSection.tsx` — titre stylé + `dangerouslySetInnerHTML` (HTML assaini) ; masquée si vide.
|
||||||
5. `CvRightColumn.tsx` — titre « Expériences professionnelles » + liste `CvExperiencePreview` + message vide alternatif (`print:hidden`).
|
5. `CvRightColumn.tsx` — titre « Expériences professionnelles » + liste `CvExperiencePreview` + message vide alternatif (`print:hidden`).
|
||||||
6. `CvExperiencePreview.tsx` — titre en gras + HTML assaini du contenu.
|
6. `CvExperiencePreview.tsx` — titre en gras + HTML assaini du contenu.
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ DOM
|
|||||||
|
|
||||||
## 3. Données personnelles : état de l'analyse
|
## 3. Données personnelles : état de l'analyse
|
||||||
|
|
||||||
Les zones 1 à 5 du CV contiennent des **données à caractère personnel** (nom, e-mail, téléphone, histoire professionnelle).
|
Les zones 1 à 6 du CV contiennent des **données à caractère personnel** (nom, e-mail, téléphone, histoire professionnelle).
|
||||||
|
|
||||||
| Propriété | État |
|
| Propriété | État |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
|
|||||||
@@ -30,18 +30,19 @@
|
|||||||
| G2 | Réduire la fenêtre sous 1024 px | Les panneaux s'empilent (gauche au-dessus de la droite) sans casse |
|
| G2 | Réduire la fenêtre sous 1024 px | Les panneaux s'empilent (gauche au-dessus de la droite) sans casse |
|
||||||
| G3 | Vérifier la barre d'app et le footer | Barre : logo, bouton « Imprimer / PDF », lien Documentation. Footer : lien Mentions légales (masqué à l'impression) |
|
| G3 | Vérifier la barre d'app et le footer | Barre : logo, bouton « Imprimer / PDF », lien Documentation. Footer : lien Mentions légales (masqué à l'impression) |
|
||||||
|
|
||||||
### 3.2 Onglets 1 à 5 (zones de saisie)
|
### 3.2 Onglets 1 à 6 (zones de saisie)
|
||||||
|
|
||||||
| # | Action | Résultat attendu |
|
| # | Action | Résultat attendu |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| T1 | Cliquer sur les onglets 1→5 | L'onglet actif change ; le textarea affiche le contenu **de cet onglet uniquement** |
|
| T1 | Cliquer sur les onglets 1→6 | L'onglet actif change ; le textarea affiche le contenu **de cet onglet uniquement** |
|
||||||
| T2 | Saisir du Markdown dans l'onglet 1 (`**Jean Dupont**`) | Rendu temps réel dans le **header** de la feuille A4 (gras) |
|
| T2 | Saisir du Markdown dans l'onglet 1 (`**Jean Dupont**`) | Rendu temps réel dans le **header** de la feuille A4 (gras) |
|
||||||
| T3 | Idem dans l'onglet 2 (nom, e-mail, site, GitHub en listes) | Rendu dans la section **État civil** (colonne 1/3) |
|
| T3 | Idem dans l'onglet 2 (nom, e-mail, site, GitHub en listes) | Rendu dans la section **État civil** (colonne 1/3) |
|
||||||
| T4 | Onglet 3 (formation ex. `### Master` + liste) | Rendu dans la section **Formations** |
|
| T4 | Onglet 3 (formation ex. `### Master` + liste) | Rendu dans la section **Formations** |
|
||||||
| T5 | Onglet 4 | Rendu dans la section **Soft skills** |
|
| T5 | Onglet 4 (compétences ex. `- React\n- TypeScript`) | Rendu dans la section **Compétences** |
|
||||||
| T6 | Onglet 5 | Rendu dans la section **Centres d'intérêt** |
|
| T6 | Onglet 5 | Rendu dans la section **Soft skills** |
|
||||||
| T7 | Basculer entre onglets après saisie | Chaque contenu est conservé, aucun écrasement croisé |
|
| T7 | Onglet 6 | Rendu dans la section **Centres d'intérêt** |
|
||||||
| T8 | Vider complètement une section | La section correspondante disparaît de la preview |
|
| T8 | Basculer entre onglets après saisie | Chaque contenu est conservé, aucun écrasement croisé |
|
||||||
|
| T9 | Vider complètement une section | La section correspondante disparaît de la preview |
|
||||||
|
|
||||||
### 3.3 Expériences professionnelles
|
### 3.3 Expériences professionnelles
|
||||||
|
|
||||||
@@ -128,7 +129,7 @@ Dès leur mise en place, ajouter à la recette :
|
|||||||
| Persistance localStorage | P1–P5 | ☐ |
|
| Persistance localStorage | P1–P5 | ☐ |
|
||||||
| Impression PDF | M1–M3 | ☐ |
|
| Impression PDF | M1–M3 | ☐ |
|
||||||
| Expériences (+, liste, modif, ×) | X1–X9 | ☐ |
|
| Expériences (+, liste, modif, ×) | X1–X9 | ☐ |
|
||||||
| Onglets 1-5 | T1–T8 | ☐ |
|
| Onglets 1-6 | T1–T9 | ☐ |
|
||||||
| Documentation + Mentions légales | D1–D3 | ☐ |
|
| Documentation + Mentions légales | D1–D3 | ☐ |
|
||||||
| PWA | W1–W5 | ☐ |
|
| PWA | W1–W5 | ☐ |
|
||||||
|
|
||||||
|
|||||||
+8
-3
@@ -2,6 +2,7 @@ export type CVTabId =
|
|||||||
| "accroche"
|
| "accroche"
|
||||||
| "etatCivil"
|
| "etatCivil"
|
||||||
| "formations"
|
| "formations"
|
||||||
|
| "competences"
|
||||||
| "softSkills"
|
| "softSkills"
|
||||||
| "centresInteret";
|
| "centresInteret";
|
||||||
|
|
||||||
@@ -9,13 +10,14 @@ export const CV_TAB_ORDER: CVTabId[] = [
|
|||||||
"accroche",
|
"accroche",
|
||||||
"etatCivil",
|
"etatCivil",
|
||||||
"formations",
|
"formations",
|
||||||
|
"competences",
|
||||||
"softSkills",
|
"softSkills",
|
||||||
"centresInteret",
|
"centresInteret",
|
||||||
];
|
];
|
||||||
|
|
||||||
export interface CVTabMeta {
|
export interface CVTabMeta {
|
||||||
id: CVTabId;
|
id: CVTabId;
|
||||||
numero: 1 | 2 | 3 | 4 | 5;
|
numero: 1 | 2 | 3 | 4 | 5 | 6;
|
||||||
label: string;
|
label: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,10 +25,11 @@ export const CV_TABS: Record<CVTabId, CVTabMeta> = {
|
|||||||
accroche: { id: "accroche", numero: 1, label: "Accroche" },
|
accroche: { id: "accroche", numero: 1, label: "Accroche" },
|
||||||
etatCivil: { id: "etatCivil", numero: 2, label: "État civil" },
|
etatCivil: { id: "etatCivil", numero: 2, label: "État civil" },
|
||||||
formations: { id: "formations", numero: 3, label: "Formations" },
|
formations: { id: "formations", numero: 3, label: "Formations" },
|
||||||
softSkills: { id: "softSkills", numero: 4, label: "Soft skills" },
|
competences: { id: "competences", numero: 4, label: "Compétences" },
|
||||||
|
softSkills: { id: "softSkills", numero: 5, label: "Soft skills" },
|
||||||
centresInteret: {
|
centresInteret: {
|
||||||
id: "centresInteret",
|
id: "centresInteret",
|
||||||
numero: 5,
|
numero: 6,
|
||||||
label: "Centres d'intérêt",
|
label: "Centres d'intérêt",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -44,6 +47,7 @@ export interface CVCareerData {
|
|||||||
accroche: string;
|
accroche: string;
|
||||||
etatCivil: string;
|
etatCivil: string;
|
||||||
formations: string;
|
formations: string;
|
||||||
|
competences: string;
|
||||||
softSkills: string;
|
softSkills: string;
|
||||||
centresInteret: string;
|
centresInteret: string;
|
||||||
experiences: Experience[];
|
experiences: Experience[];
|
||||||
@@ -57,6 +61,7 @@ export function createDefaultCVData(): CVCareerData {
|
|||||||
accroche: "",
|
accroche: "",
|
||||||
etatCivil: "",
|
etatCivil: "",
|
||||||
formations: "",
|
formations: "",
|
||||||
|
competences: "",
|
||||||
softSkills: "",
|
softSkills: "",
|
||||||
centresInteret: "",
|
centresInteret: "",
|
||||||
experiences: [],
|
experiences: [],
|
||||||
|
|||||||
Reference in new issue
Block a user