Files
turf_saas/crm_candidatures.html
2026-04-25 17:18:43 +02:00

396 lines
18 KiB
HTML
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>CRM Candidatures</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, sans-serif; background: #1a1a2e; color: #eee; padding: 20px; }
header { background: linear-gradient(90deg, #e94560, #7b2cbf); padding: 20px; text-align: center; margin: -20px -20px 20px; border-radius: 0 0 20px 20px; }
h1 { font-size: 28px; margin-bottom: 10px; }
.subtitle { color: #aaa; font-size: 14px; }
.stats { display: grid; grid-template-columns: repeat(6, 1fr); gap: 10px; margin-bottom: 20px; }
.stat-card { background: #16213e; padding: 15px; border-radius: 8px; text-align: center; }
.stat-num { font-size: 24px; color: #00d9ff; font-weight: bold; }
.stat-label { color: #aaa; font-size: 12px; }
.kanban { display: grid; grid-template-columns: repeat(6, 1fr); gap: 15px; overflow-x: auto; }
.column { background: #16213e; border-radius: 12px; padding: 15px; min-width: 250px; }
.column-header { font-size: 14px; font-weight: bold; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 2px solid; }
.column-a_postuler { border-color: #ffd700; }
.column-en_attente { border-color: #ff9f43; }
.column-entretien_1 { border-color: #00d9ff; }
.column-entretien_2 { border-color: #7b2cbf; }
.column-offre { border-color: #00ff88; }
.column-refus { border-color: #e94560; }
.card { background: #0f3460; padding: 12px; border-radius: 8px; margin-bottom: 10px; cursor: pointer; }
.card:hover { background: #1a4a7a; }
.card-entreprise { font-weight: bold; color: #fff; font-size: 14px; }
.card-poste { color: #00d9ff; font-size: 12px; margin: 5px 0; }
.card-date { color: #888; font-size: 11px; }
.card-rappel { background: #e94560; color: #fff; padding: 2px 6px; border-radius: 4px; font-size: 10px; margin-top: 5px; display: inline-block; }
.modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); z-index: 1000; }
.modal.show { display: flex; justify-content: center; align-items: center; }
.modal-content { background: #16213e; padding: 30px; border-radius: 15px; width: 600px; max-width: 90%; max-height: 80vh; overflow-y: auto; }
.form-group { margin-bottom: 15px; }
.form-group label { display: block; color: #aaa; margin-bottom: 5px; font-size: 12px; }
.form-group input, .form-group select, .form-group textarea { width: 100%; padding: 10px; background: #0f3460; border: 1px solid #333; border-radius: 8px; color: #fff; }
.btn { padding: 10px 20px; border: none; border-radius: 8px; cursor: pointer; font-weight: bold; }
.btn-primary { background: #00d9ff; color: #000; }
.btn-secondary { background: #666; color: #fff; }
.btn-danger { background: #e94560; color: #fff; }
.contact-history { margin-top: 20px; padding-top: 20px; border-top: 1px solid #333; }
.contact-item { background: #0f3460; padding: 10px; border-radius: 8px; margin-bottom: 10px; }
.contact-date { color: #00d9ff; font-size: 11px; }
.contact-type { color: #aaa; font-size: 11px; }
.add-form { background: #16213e; padding: 20px; border-radius: 12px; margin-bottom: 20px; }
.form-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.home-btn{position:fixed;top:10px;left:10px;z-index:9999;background:linear-gradient(135deg,#00d9ff,#7b2cbf);color:#fff;border:none;border-radius:8px;padding:10px 15px;font-size:14px;cursor:pointer;text-decoration:none;display:flex;align-items:center;gap:8px;box-shadow:0 2px 10px rgba(0,217,255,0.3);transition:all 0.3s;font-family:-apple-system,BlinkMacSystemFont,sans-serif}.home-btn:hover{transform:translateY(-2px);box-shadow:0 4px 15px rgba(0,217,255,0.5)}
</style>
</head>
<body>
<a href="https://portal-kolifee.duckdns.org/" class="home-btn" title="Retour au portail"><span style="font-size:18px">🏠</span><span>Accueil</span></a>
<header>
<h1>🎯 CRM Candidatures</h1>
<div class="subtitle">Suivi de recherche d'emploi - Pipeline</div>
</header>
<div class="stats" id="stats">
<div class="stat-card"><div class="stat-num" id="total">0</div><div class="stat-label">Total</div></div>
<div class="stat-card"><div class="stat-num" id="a_postuler">0</div><div class="stat-label">À Postuler</div></div>
<div class="stat-card"><div class="stat-num" id="en_attente">0</div><div class="stat-label">En Attente</div></div>
<div class="stat-card"><div class="stat-num" id="entretien">0</div><div class="stat-label">Entretiens</div></div>
<div class="stat-card"><div class="stat-num" id="offres">0</div><div class="stat-label">Offres</div></div>
<div class="stat-card"><div class="stat-num" id="refus">0</div><div class="stat-label">Refus</div></div>
</div>
<div class="add-form">
<h2> Nouvelle Candidature</h2>
<div class="form-row">
<input type="text" id="entreprise" placeholder="Entreprise *">
<input type="text" id="poste" placeholder="Poste Recherché *">
<input type="text" id="source" placeholder="Source (LinkedIn, Indeed...)">
</div>
<div class="form-row" style="margin-top:10px">
<input type="url" id="url" placeholder="URL offre">
<input type="number" id="salaire" placeholder="Salaire (€)">
<input type="date" id="date_publication"> Date publication
</div>
<button class="btn btn-primary" style="margin-top:15px" onclick="addCandidature()">Ajouter</button>
</div>
<div class="kanban">
<div class="column column-a_postuler">
<div class="column-header">📝 À Postuler</div>
<div id="a_postuler-list"></div>
</div>
<div class="column column-en_attente">
<div class="column-header">⏳ En Attente</div>
<div id="en_attente-list"></div>
</div>
<div class="column column-entretien_1">
<div class="column-header">🎤 Entretien 1</div>
<div id="entretien_1-list"></div>
</div>
<div class="column column-entretien_2">
<div class="column-header">🎓 Entretien 2</div>
<div id="entretien_2-list"></div>
</div>
<div class="column column-offre">
<div class="column-header">✅ Offre</div>
<div id="offre-list"></div>
</div>
<div class="column column-refus">
<div class="column-header">❌ Refus</div>
<div id="refus-list"></div>
</div>
</div>
<div class="modal" id="modal">
<div class="modal-content">
<h2 id="modal-title">Modifier Candidature</h2>
<input type="hidden" id="cand-id">
<div class="form-group">
<label>Entreprise</label>
<input type="text" id="edit-entreprise">
</div>
<div class="form-group">
<label>Poste</label>
<input type="text" id="edit-poste">
</div>
<div class="form-group">
<label>URL Offre</label>
<input type="url" id="edit-url">
</div>
<div class="form-group">
<label>Salaire</label>
<input type="number" id="edit-salaire">
</div>
<div class="form-group">
<label>Étape Pipeline</label>
<select id="edit-etape">
<option value="a_postuler">À Postuler</option>
<option value="en_attente">En Attente</option>
<option value="entretien_1">Entretien 1</option>
<option value="entretien_2">Entretien 2</option>
<option value="offre">Offre</option>
<option value="refus">Refus</option>
</select>
</div>
<div class="form-group">
<label>Prochain Rappel</label>
<input type="date" id="edit-rappel">
</div>
<div class="form-group">
<label>Notes</label>
<textarea id="edit-notes" rows="3"></textarea>
</div>
<div class="contact-history">
<h3>📞 Historique Contacts</h3>
<div id="contacts-list"></div>
<div class="form-group" style="margin-top:15px">
<input type="text" id="new-contact-type" placeholder="Type (appel, email, rdv)" style="width:30%">
<input type="text" id="new-contact-interlocuteur" placeholder="Interlocuteur" style="width:30%">
<input type="date" id="new-contact-date" style="width:30%">
<textarea id="new-contact-notes" placeholder="Notes contact" rows="2" style="margin-top:5px"></textarea>
<button class="btn btn-primary" style="margin-top:5px" onclick="addContact()">Ajouter Contact</button>
</div>
</div>
<div style="display:flex;gap:10px;margin-top:20px">
<button class="btn btn-primary" onclick="saveCandidature()">Enregistrer</button>
<button class="btn btn-secondary" onclick="closeModal()">Annuler</button>
<button class="btn btn-danger" onclick="deleteCandidature()">Supprimer</button>
</div>
</div>
</div>
<script>
let candidatures = [];
// Load from localStorage or API
function load() {
const stored = localStorage.getItem('candidatures');
if (stored) {
candidatures = JSON.parse(stored);
} else {
// Fetch from API and transform
fetch('api/prospects')
.then(r => r.json())
.then(data => {
// Transform prospects to candidatures
candidatures = data.prospects.map(p => ({
id: p.id,
entreprise: {
nom: p.nom,
adresse: p.adresse,
tel: p.tel,
email: p.email,
categorie: p.categorie
},
poste: {
titre: '',
url: '',
salaire: null,
source: '',
localisation: p.adresse
},
pipeline: {
etape: 'a_postuler',
date_changement: new Date().toISOString().split('T')[0],
historique: []
},
suivi: {
dernier_contact: null,
prochain_rappel: null,
contacts: [],
notes: p.notes || '',
score_confiance: p.score || 0
},
created: p.created,
updated: new Date().toISOString().split('T')[0]
}));
save();
});
}
render();
}
function save() {
localStorage.setItem('candidatures', JSON.stringify(candidatures));
render();
}
function render() {
// Clear columns
['a_postuler', 'en_attente', 'entretien_1', 'entretien_2', 'offre', 'refus'].forEach(etape => {
document.getElementById(etape + '-list').innerHTML = '';
});
// Stats
const stats = { a_postuler: 0, en_attente: 0, entretien_1: 0, entretien_2: 0, offre: 0, refus: 0, total: candidatures.length };
// Render cards
candidatures.forEach((c, i) => {
const etape = c.pipeline.etape;
stats[etape] = (stats[etape] || 0) + 1;
const card = document.createElement('div');
card.className = 'card';
card.onclick = () => openEdit(i);
const today = new Date().toISOString().split('T')[0];
let rappelHtml = '';
if (c.suivi.prochain_rappel && c.suivi.prochain_rappel <= today) {
rappelHtml = '<span class="card-rappel">⚠️ Rappel!</span>';
}
card.innerHTML = `
<div class="card-entreprise">${c.entreprise.nom || '-'}</div>
<div class="card-poste">${c.poste.titre || 'Poste à définir'}</div>
${c.poste.salaire ? '<div class="card-date">💰 ' + c.poste.salaire + '€</div>' : ''}
${rappelHtml}
<div class="card-date">📅 ${c.pipeline.date_changement}</div>
`;
document.getElementById(etape + '-list').appendChild(card);
});
// Update stats
document.getElementById('total').textContent = stats.total;
document.getElementById('a_postuler').textContent = stats.a_postuler;
document.getElementById('en_attente').textContent = stats.en_attente;
document.getElementById('entretien').textContent = (stats.entretien_1 || 0) + (stats.entretien_2 || 0);
document.getElementById('offres').textContent = stats.offre;
document.getElementById('refus').textContent = stats.refus;
}
function addCandidature() {
const entreprise = document.getElementById('entreprise').value;
const poste = document.getElementById('poste').value;
if (!entreprise || !poste) return alert('Entreprise et Poste requis!');
const cand = {
id: 'cand_' + Date.now(),
entreprise: { nom: entreprise },
poste: {
titre: poste,
url: document.getElementById('url').value,
salaire: document.getElementById('salaire').value,
source: document.getElementById('source').value,
date_publication: document.getElementById('date_publication').value
},
pipeline: { etape: 'a_postuler', date_changement: new Date().toISOString().split('T')[0], historique: [] },
suivi: { contacts: [], notes: '' },
created: new Date().toISOString().split('T')[0],
updated: new Date().toISOString().split('T')[0]
};
candidatures.push(cand);
save();
// Clear form
document.getElementById('entreprise').value = '';
document.getElementById('poste').value = '';
document.getElementById('url').value = '';
document.getElementById('salaire').value = '';
}
let currentIdx = null;
function openEdit(idx) {
currentIdx = idx;
const c = candidatures[idx];
document.getElementById('modal-title').textContent = 'Modifier: ' + (c.entreprise.nom || '');
document.getElementById('cand-id').value = c.id;
document.getElementById('edit-entreprise').value = c.entreprise.nom || '';
document.getElementById('edit-poste').value = c.poste.titre || '';
document.getElementById('edit-url').value = c.poste.url || '';
document.getElementById('edit-salaire').value = c.poste.salaire || '';
document.getElementById('edit-etape').value = c.pipeline.etape;
document.getElementById('edit-rappel').value = c.suivi.prochain_rappel || '';
document.getElementById('edit-notes').value = c.suivi.notes || '';
renderContacts(c);
document.getElementById('modal').classList.add('show');
}
function renderContacts(c) {
const list = document.getElementById('contacts-list');
list.innerHTML = (c.suivi.contacts || []).map(contact => `
<div class="contact-item">
<div class="contact-date">${contact.date} - <span class="contact-type">${contact.type}</span></div>
<div>${contact.interlocuteur || ''}</div>
<div style="color:#888;font-size:11px">${contact.notes || ''}</div>
</div>
`).join('');
}
function addContact() {
const c = candidatures[currentIdx];
if (!c.suivi.contacts) c.suivi.contacts = [];
c.suivi.contacts.push({
type: document.getElementById('new-contact-type').value || 'appel',
interlocuteur: document.getElementById('new-contact-interlocuteur').value,
date: document.getElementById('new-contact-date').value || new Date().toISOString().split('T')[0],
notes: document.getElementById('new-contact-notes').value
});
c.suivi.dernier_contact = new Date().toISOString().split('T')[0];
save();
renderContacts(c);
}
function saveCandidature() {
const c = candidatures[currentIdx];
c.entreprise.nom = document.getElementById('edit-entreprise').value;
c.poste.titre = document.getElementById('edit-poste').value;
c.poste.url = document.getElementById('edit-url').value;
c.poste.salaire = document.getElementById('edit-salaire').value;
c.suivi.notes = document.getElementById('edit-notes').value;
c.suivi.prochain_rappel = document.getElementById('edit-rappel').value;
const newEtape = document.getElementById('edit-etape').value;
if (newEtape !== c.pipeline.etape) {
c.pipeline.historique.push({ etape: c.pipeline.etape, date: c.pipeline.date_changement });
c.pipeline.etape = newEtape;
c.pipeline.date_changement = new Date().toISOString().split('T')[0];
}
c.updated = new Date().toISOString().split('T')[0];
save();
closeModal();
}
function deleteCandidature() {
if (!confirm('Supprimer cette candidature?')) return;
candidatures.splice(currentIdx, 1);
save();
closeModal();
}
function closeModal() {
document.getElementById('modal').classList.remove('show');
currentIdx = null;
}
// Initialize
load();
</script>
</body>
</html>