/**
* CV Generator - JavaScript
* Gère le formulaire multi-étapes et l'interaction avec l'API
*/
// Configuration
const API_BASE_URL = window.location.origin + '/api/cv';
let currentStep = 1;
const totalSteps = 5;
// ========================================
// Navigation entre les étapes
// ========================================
function updateProgress() {
const progress = ((currentStep - 1) / (totalSteps - 1)) * 100;
document.getElementById('progress').style.width = `${progress}%`;
// Mettre à jour les indicateurs d'étapes
document.querySelectorAll('.step').forEach((step, index) => {
const stepNum = index + 1;
step.classList.remove('active', 'completed');
if (stepNum < currentStep) {
step.classList.add('completed');
} else if (stepNum === currentStep) {
step.classList.add('active');
}
});
}
function showStep(step) {
document.querySelectorAll('.form-step').forEach(el => {
el.classList.remove('active');
});
document.getElementById(`step-${step}`).classList.add('active');
// Gérer les boutons de navigation
document.getElementById('prev-btn').style.display = step === 1 ? 'none' : 'block';
document.getElementById('next-btn').style.display = step === totalSteps ? 'none' : 'block';
// Mettre à jour le preview à l'étape finale
if (step === totalSteps) {
updatePreview();
}
updateProgress();
}
function nextStep() {
// Valider l'étape actuelle avant de passer à la suivante
if (!validateCurrentStep()) {
return;
}
if (currentStep < totalSteps) {
currentStep++;
showStep(currentStep);
window.scrollTo({ top: 0, behavior: 'smooth' });
}
}
function prevStep() {
if (currentStep > 1) {
currentStep--;
showStep(currentStep);
window.scrollTo({ top: 0, behavior: 'smooth' });
}
}
// ========================================
// Validation
// ========================================
function validateCurrentStep() {
const currentStepEl = document.getElementById(`step-${currentStep}`);
const requiredFields = currentStepEl.querySelectorAll('[required]');
let isValid = true;
requiredFields.forEach(field => {
if (!field.value.trim()) {
field.style.borderColor = '#ef4444';
isValid = false;
} else {
field.style.borderColor = '#e2e8f0';
}
});
if (!isValid) {
alert('Veuillez remplir tous les champs obligatoires.');
}
return isValid;
}
// ========================================
// Ajout dynamique d'entrées
// ========================================
function addEducation() {
const container = document.getElementById('education-container');
const newEntry = document.createElement('div');
newEntry.className = 'entry-card education-entry';
newEntry.innerHTML = `
`;
container.appendChild(newEntry);
}
function addExperience() {
const container = document.getElementById('experience-container');
const newEntry = document.createElement('div');
newEntry.className = 'entry-card experience-entry';
newEntry.innerHTML = `
`;
container.appendChild(newEntry);
}
function addSkill() {
const container = document.getElementById('skills-container');
const newEntry = document.createElement('div');
newEntry.className = 'skill-entry form-row';
newEntry.innerHTML = `
`;
container.appendChild(newEntry);
}
function addLanguage() {
const container = document.getElementById('languages-container');
const newEntry = document.createElement('div');
newEntry.className = 'language-entry form-row';
newEntry.innerHTML = `
`;
container.appendChild(newEntry);
}
function removeEntry(button) {
const entry = button.closest('.entry-card, .skill-entry, .language-entry');
entry.remove();
}
// ========================================
// Collecte des données du formulaire
// ========================================
function collectFormData() {
// Informations personnelles
const personalInfo = {
full_name: document.getElementById('full_name').value,
email: document.getElementById('email').value,
phone: document.getElementById('phone').value,
address: document.getElementById('address').value,
linkedin: document.getElementById('linkedin').value || null,
portfolio: document.getElementById('portfolio').value || null,
summary: document.getElementById('summary').value || null
};
// Formation
const education = [];
const eduDegrees = document.getElementsByName('edu_degree[]');
const eduInstitutions = document.getElementsByName('edu_institution[]');
const eduStarts = document.getElementsByName('edu_start[]');
const eduEnds = document.getElementsByName('edu_end[]');
const eduDescriptions = document.getElementsByName('edu_description[]');
for (let i = 0; i < eduDegrees.length; i++) {
if (eduDegrees[i].value) {
education.push({
degree: eduDegrees[i].value,
institution: eduInstitutions[i].value,
start_date: eduStarts[i].value,
end_date: eduEnds[i].value,
description: eduDescriptions[i].value || null
});
}
}
// Expériences
const experiences = [];
const expTitles = document.getElementsByName('exp_title[]');
const expCompanies = document.getElementsByName('exp_company[]');
const expStarts = document.getElementsByName('exp_start[]');
const expEnds = document.getElementsByName('exp_end[]');
const expDescriptions = document.getElementsByName('exp_description[]');
for (let i = 0; i < expTitles.length; i++) {
if (expTitles[i].value) {
experiences.push({
job_title: expTitles[i].value,
company: expCompanies[i].value,
start_date: expStarts[i].value,
end_date: expEnds[i].value,
description: expDescriptions[i].value
});
}
}
// Compétences
const skills = [];
const skillNames = document.getElementsByName('skill_name[]');
const skillLevels = document.getElementsByName('skill_level[]');
for (let i = 0; i < skillNames.length; i++) {
if (skillNames[i].value) {
skills.push({
name: skillNames[i].value,
level: skillLevels[i].value || null
});
}
}
// Langues
const languages = [];
const langNames = document.getElementsByName('lang_name[]');
const langLevels = document.getElementsByName('lang_level[]');
for (let i = 0; i < langNames.length; i++) {
if (langNames[i].value) {
languages.push({
name: langNames[i].value,
level: langLevels[i].value
});
}
}
// Hobbies
const hobbiesInput = document.getElementById('hobbies').value;
const hobbies = hobbiesInput ? hobbiesInput.split(',').map(h => h.trim()).filter(h => h) : null;
return {
personal_info: personalInfo,
education: education,
experiences: experiences,
skills: skills,
languages: languages,
hobbies: hobbies
};
}
// ========================================
// Preview
// ========================================
function updatePreview() {
const data = collectFormData();
// Personal Info
const personalHtml = `
Nom: ${data.personal_info.full_name}
Email: ${data.personal_info.email}
Téléphone: ${data.personal_info.phone}
Adresse: ${data.personal_info.address}
${data.personal_info.linkedin ? `LinkedIn: ${data.personal_info.linkedin}
` : ''}
${data.personal_info.summary ? `Résumé: ${data.personal_info.summary}
` : 'Le résumé sera généré par l\'IA
'}
`;
document.getElementById('preview-personal').innerHTML = personalHtml;
// Education
const educationHtml = data.education.map(edu => `
${edu.degree} - ${edu.institution}
${edu.start_date} - ${edu.end_date}
`).join('');
document.getElementById('preview-education').innerHTML = educationHtml || 'Aucune formation ajoutée';
// Experience
const experienceHtml = data.experiences.map(exp => `
${exp.job_title} - ${exp.company}
${exp.start_date} - ${exp.end_date}
`).join('');
document.getElementById('preview-experience').innerHTML = experienceHtml || 'Aucune expérience ajoutée';
// Skills & Languages
const skillsHtml = `
Compétences: ${data.skills.map(s => s.name).join(', ') || 'Aucune'}
Langues: ${data.languages.map(l => `${l.name} (${l.level})`).join(', ') || 'Aucune'}
${data.hobbies ? `Centres d'intérêt: ${data.hobbies.join(', ')}
` : ''}
`;
document.getElementById('preview-skills').innerHTML = skillsHtml;
}
// ========================================
// IA Features
// ========================================
async function generateSummary() {
const btn = document.querySelector('.btn-ai');
btn.disabled = true;
btn.textContent = '⏳ Génération...';
try {
const data = collectFormData();
const response = await fetch(`${API_BASE_URL}/enhance-summary`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
const result = await response.json();
if (result.success) {
document.getElementById('summary').value = result.summary;
} else {
alert('Erreur lors de la génération du résumé');
}
} catch (error) {
console.error('Erreur:', error);
alert('Erreur de connexion au serveur');
} finally {
btn.disabled = false;
btn.textContent = '✨ Générer avec l\'IA';
}
}
async function enhanceExperience(button) {
const entry = button.closest('.entry-card');
const textarea = entry.querySelector('textarea[name="exp_description[]"]');
const titleInput = entry.querySelector('input[name="exp_title[]"]');
if (!textarea.value.trim()) {
alert('Veuillez d\'abord entrer une description à améliorer');
return;
}
button.disabled = true;
button.textContent = '⏳ Amélioration...';
try {
const response = await fetch(`${API_BASE_URL}/enhance-experience?description=${encodeURIComponent(textarea.value)}&job_title=${encodeURIComponent(titleInput.value)}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
});
const result = await response.json();
if (result.success) {
textarea.value = result.enhanced_description;
} else {
alert('Erreur lors de l\'amélioration');
}
} catch (error) {
console.error('Erreur:', error);
alert('Erreur de connexion au serveur');
} finally {
button.disabled = false;
button.textContent = '✨ Améliorer avec l\'IA';
}
}
// ========================================
// Génération du CV
// ========================================
async function generateCV(event) {
event.preventDefault();
const generateBtn = document.getElementById('generate-btn');
const loading = document.getElementById('loading');
generateBtn.disabled = true;
generateBtn.querySelector('.btn-text').classList.add('hidden');
generateBtn.querySelector('.btn-loading').classList.remove('hidden');
loading.classList.remove('hidden');
try {
const data = collectFormData();
const response = await fetch(`${API_BASE_URL}/generate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
const result = await response.json();
if (result.success) {
showDownloadModal(result.message, result.file_path);
} else {
alert('Erreur: ' + (result.detail || result.message));
}
} catch (error) {
console.error('Erreur:', error);
alert('Erreur de connexion au serveur. Vérifiez que le backend est lancé.');
} finally {
generateBtn.disabled = false;
generateBtn.querySelector('.btn-text').classList.remove('hidden');
generateBtn.querySelector('.btn-loading').classList.add('hidden');
loading.classList.add('hidden');
}
}
// ========================================
// Modal
// ========================================
function showDownloadModal(message, filename) {
const modal = document.getElementById('download-modal');
const messageEl = document.getElementById('modal-message');
const downloadLink = document.getElementById('download-link');
messageEl.textContent = message;
downloadLink.href = `${API_BASE_URL}/download/${filename}`;
downloadLink.download = filename;
modal.classList.remove('hidden');
}
function closeModal() {
document.getElementById('download-modal').classList.add('hidden');
}
// ========================================
// Initialisation
// ========================================
document.addEventListener('DOMContentLoaded', function() {
// Initialiser la première étape
showStep(1);
// Gérer la soumission du formulaire
document.getElementById('cv-form').addEventListener('submit', generateCV);
// Fermer le modal en cliquant à l'extérieur
document.getElementById('download-modal').addEventListener('click', function(e) {
if (e.target === this) {
closeModal();
}
});
});