first commit
This commit is contained in:
commit
e6c52820cd
227 changed files with 16156 additions and 0 deletions
323
Templates/email/bulk_email.html
Normal file
323
Templates/email/bulk_email.html
Normal file
|
|
@ -0,0 +1,323 @@
|
|||
{% extends 'layouts/base.html' %}
|
||||
|
||||
{% block title %}Suite Consultance - Campagne d'email{% endblock %}
|
||||
{% block module_name %}crm{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1 class="h2 text-crm">Campagne d'email aux prospects</h1>
|
||||
<a href="{{ url_for('crm') }}" class="btn btn-outline-crm">
|
||||
<i class="fas fa-arrow-left"></i> Retour au CRM
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{{ url_for('send_bulk_email') }}">
|
||||
<div class="row g-3">
|
||||
<!-- Sélection des prospects -->
|
||||
<div class="col-12">
|
||||
<h5 class="border-bottom pb-2 mb-3">Sélection des prospects</h5>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Filtre par statut</label>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="status_all" name="status_all" checked>
|
||||
<label class="form-check-label" for="status_all">
|
||||
Tous
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input status-option" type="checkbox" id="status_nouveau" name="status[]" value="Nouveau">
|
||||
<label class="form-check-label" for="status_nouveau">
|
||||
Nouveau
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input status-option" type="checkbox" id="status_contacte" name="status[]" value="Contacté">
|
||||
<label class="form-check-label" for="status_contacte">
|
||||
Contacté
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input status-option" type="checkbox" id="status_relance" name="status[]" value="Relancé">
|
||||
<label class="form-check-label" for="status_relance">
|
||||
Relancé
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input status-option" type="checkbox" id="status_qualifie" name="status[]" value="Qualifié">
|
||||
<label class="form-check-label" for="status_qualifie">
|
||||
Qualifié
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input status-option" type="checkbox" id="status_proposition" name="status[]" value="Proposition">
|
||||
<label class="form-check-label" for="status_proposition">
|
||||
Proposition
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Filtre par tags</label>
|
||||
<input type="text" class="form-control" id="tags_filter" name="tags" placeholder="ex: web, urgent, pme">
|
||||
<small class="text-muted">Séparer les tags par des virgules</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="50">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="select_all_prospects" checked>
|
||||
</div>
|
||||
</th>
|
||||
<th>Nom</th>
|
||||
<th>Email</th>
|
||||
<th>Statut</th>
|
||||
<th>Tags</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="prospects_table">
|
||||
{% for prospect in prospects %}
|
||||
{# Convertit le statut en chaîne si enum #}
|
||||
{% set s = prospect.status %}
|
||||
{% if not (s is string) %}{% set s = s.value %}{% endif %}
|
||||
<tr class="prospect-row" data-status="{{ s }}" data-tags="{{ prospect.tags|join(',') }}">
|
||||
<td>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input prospect-select" type="checkbox" name="prospect_ids[]" value="{{ prospect.id }}" checked>
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ prospect.name }}</td>
|
||||
<td>{{ prospect.email }}</td>
|
||||
<td>
|
||||
{# Mapping badge #}
|
||||
{% set cls = 'secondary' %}
|
||||
{% if s|lower == 'nouveau' %}
|
||||
{% set cls = 'primary' %}
|
||||
{% elif s|lower == 'contacté' or s|lower == 'contacte' %}
|
||||
{% set cls = 'info' %}
|
||||
{% elif s|lower == 'qualifié' or s|lower == 'qualifie' %}
|
||||
{% set cls = 'success' %}
|
||||
{% elif s|lower == 'proposition' %}
|
||||
{% set cls = 'warning' %}
|
||||
{% endif %}
|
||||
<span class="badge bg-{{ cls }}">{{ s }}</span>
|
||||
</td>
|
||||
<td>
|
||||
{% for tag in prospect.tags %}
|
||||
<span class="badge bg-secondary">{{ tag }}</span>
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Contenu de l'email -->
|
||||
<div class="col-12">
|
||||
<h5 class="border-bottom pb-2 mb-3 mt-3">Contenu de l'email</h5>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="template_id" class="form-label">Template</label>
|
||||
<select class="form-select" id="template_id" name="template_id">
|
||||
<option value="">Email personnalisé</option>
|
||||
{% for template in templates %}
|
||||
<option value="{{ template.id }}">{{ template.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<div class="mb-3">
|
||||
<label for="subject" class="form-label">Sujet *</label>
|
||||
<input type="text" class="form-control" id="subject" name="subject" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<div class="mb-3">
|
||||
<label for="body" class="form-label">Contenu *</label>
|
||||
<textarea class="form-control" id="body" name="body" rows="10" required></textarea>
|
||||
<small class="text-muted">Variables disponibles: {{nom}}, {{entreprise}}, {{email}}. Vous pouvez utiliser du texte formaté HTML.</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<div class="mb-3">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="update_status" name="update_status" checked>
|
||||
<label class="form-check-label" for="update_status">
|
||||
Mettre à jour le statut des prospects
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6" id="status_selection">
|
||||
<div class="mb-3">
|
||||
<label for="new_status" class="form-label">Nouveau statut</label>
|
||||
<select class="form-select" id="new_status" name="new_status">
|
||||
<option value="Contacté">Contacté</option>
|
||||
<option value="Relancé">Relancé</option>
|
||||
<option value="Qualifié">Qualifié</option>
|
||||
<option value="Proposition">Proposition</option>
|
||||
<option value="Non intéressé">Non intéressé</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 mt-3">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-paper-plane"></i> Envoyer la campagne
|
||||
</button>
|
||||
<a href="{{ url_for('crm') }}" class="btn btn-outline-secondary">
|
||||
Annuler
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const templateSelect = document.getElementById('template_id');
|
||||
const subjectInput = document.getElementById('subject');
|
||||
const bodyInput = document.getElementById('body');
|
||||
const updateStatusCheckbox = document.getElementById('update_status');
|
||||
const statusSelection = document.getElementById('status_selection');
|
||||
const selectAllProspects = document.getElementById('select_all_prospects');
|
||||
const statusAllCheckbox = document.getElementById('status_all');
|
||||
const statusOptions = document.querySelectorAll('.status-option');
|
||||
const prospectRows = document.querySelectorAll('.prospect-row');
|
||||
const tagsFilter = document.getElementById('tags_filter');
|
||||
|
||||
// Gestion du choix de template
|
||||
templateSelect.addEventListener('change', function() {
|
||||
const templateId = this.value;
|
||||
if (templateId) {
|
||||
// Charger le contenu du template
|
||||
fetch(`/api/email_template/${templateId}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
subjectInput.value = data.template.subject;
|
||||
bodyInput.value = data.template.content;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Afficher/masquer le sélecteur de statut
|
||||
updateStatusCheckbox.addEventListener('change', function() {
|
||||
statusSelection.style.display = this.checked ? 'block' : 'none';
|
||||
});
|
||||
|
||||
// Initialiser l'affichage du sélecteur de statut
|
||||
statusSelection.style.display = updateStatusCheckbox.checked ? 'block' : 'none';
|
||||
|
||||
// Sélectionner/désélectionner tous les prospects
|
||||
selectAllProspects.addEventListener('change', function() {
|
||||
document.querySelectorAll('.prospect-select:not(:disabled)').forEach(checkbox => {
|
||||
checkbox.checked = this.checked;
|
||||
});
|
||||
});
|
||||
|
||||
// Gestion des filtres par statut
|
||||
statusAllCheckbox.addEventListener('change', function() {
|
||||
if (this.checked) {
|
||||
statusOptions.forEach(option => {
|
||||
option.checked = false;
|
||||
option.disabled = true;
|
||||
});
|
||||
|
||||
// Afficher tous les prospects
|
||||
prospectRows.forEach(row => {
|
||||
row.style.display = '';
|
||||
row.querySelector('.prospect-select').disabled = false;
|
||||
});
|
||||
} else {
|
||||
statusOptions.forEach(option => {
|
||||
option.disabled = false;
|
||||
});
|
||||
|
||||
// Appliquer les filtres
|
||||
applyFilters();
|
||||
}
|
||||
});
|
||||
|
||||
// Gestion des options de statut individuelles
|
||||
statusOptions.forEach(option => {
|
||||
option.addEventListener('change', function() {
|
||||
applyFilters();
|
||||
});
|
||||
});
|
||||
|
||||
// Gestion du filtre par tags
|
||||
tagsFilter.addEventListener('input', function() {
|
||||
applyFilters();
|
||||
});
|
||||
|
||||
// Fonction pour appliquer les filtres
|
||||
function applyFilters() {
|
||||
// Désactiver "Tous" si une option spécifique est choisie
|
||||
if (Array.from(statusOptions).some(opt => opt.checked)) {
|
||||
statusAllCheckbox.checked = false;
|
||||
}
|
||||
|
||||
// Récupérer les statuts sélectionnés
|
||||
const selectedStatuses = Array.from(statusOptions)
|
||||
.filter(option => option.checked)
|
||||
.map(option => option.value);
|
||||
|
||||
// Récupérer les tags saisis
|
||||
const tags = tagsFilter.value.toLowerCase().split(',').map(tag => tag.trim()).filter(tag => tag);
|
||||
|
||||
// Appliquer les filtres aux lignes
|
||||
prospectRows.forEach(row => {
|
||||
const rowStatus = row.getAttribute('data-status');
|
||||
const rowTags = row.getAttribute('data-tags').toLowerCase().split(',');
|
||||
|
||||
let statusMatch = selectedStatuses.length === 0 || selectedStatuses.includes(rowStatus);
|
||||
let tagsMatch = tags.length === 0 || tags.some(tag => rowTags.includes(tag));
|
||||
|
||||
if (statusAllCheckbox.checked) {
|
||||
statusMatch = true;
|
||||
}
|
||||
|
||||
if (statusMatch && tagsMatch) {
|
||||
row.style.display = '';
|
||||
row.querySelector('.prospect-select').disabled = false;
|
||||
} else {
|
||||
row.style.display = 'none';
|
||||
row.querySelector('.prospect-select').disabled = true;
|
||||
row.querySelector('.prospect-select').checked = false;
|
||||
}
|
||||
});
|
||||
|
||||
// Mettre à jour "Sélectionner tout" si aucun prospect n'est visible
|
||||
const visibleProspects = document.querySelectorAll('.prospect-row[style=""]');
|
||||
selectAllProspects.disabled = visibleProspects.length === 0;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
141
Templates/email/config.html
Normal file
141
Templates/email/config.html
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
{% extends 'layouts/base.html' %}
|
||||
|
||||
{% block title %}Suite Consultance - Configuration Email{% endblock %}
|
||||
{% block module_name %}crm{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1 class="h2 text-crm">Configuration Email</h1>
|
||||
<a href="{{ url_for('crm') }}" class="btn btn-outline-crm">
|
||||
<i class="fas fa-arrow-left"></i> Retour au CRM
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{{ url_for('email_config') }}">
|
||||
<div class="row g-3">
|
||||
<!-- Configuration SMTP -->
|
||||
<div class="col-12">
|
||||
<h5 class="border-bottom pb-2 mb-3">Configuration SMTP</h5>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="smtp_server" class="form-label">Serveur SMTP *</label>
|
||||
<input type="text" class="form-control" id="smtp_server" name="smtp_server" value="{{ config.smtp_server }}" required>
|
||||
<small class="text-muted">Exemple: smtp.gmail.com</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="smtp_port" class="form-label">Port SMTP *</label>
|
||||
<input type="number" class="form-control" id="smtp_port" name="smtp_port" value="{{ config.smtp_port }}" required>
|
||||
<small class="text-muted">Exemple: 587 (TLS) ou 465 (SSL)</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Authentification -->
|
||||
<div class="col-12">
|
||||
<h5 class="border-bottom pb-2 mb-3 mt-3">Authentification</h5>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">Adresse email *</label>
|
||||
<input type="email" class="form-control" id="username" name="username" value="{{ config.username }}" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Mot de passe *</label>
|
||||
<input type="password" class="form-control" id="password" name="password" value="{{ config.password }}" required>
|
||||
<small class="text-muted">Pour Gmail, utilisez un "mot de passe d'application"</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Information d'expéditeur -->
|
||||
<div class="col-12">
|
||||
<h5 class="border-bottom pb-2 mb-3 mt-3">Informations d'expéditeur</h5>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="sender_name" class="form-label">Nom d'expéditeur *</label>
|
||||
<input type="text" class="form-control" id="sender_name" name="sender_name" value="{{ config.sender_name }}" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="sender_email" class="form-label">Email d'expéditeur *</label>
|
||||
<input type="email" class="form-control" id="sender_email" name="sender_email" value="{{ config.sender_email }}" required>
|
||||
<small class="text-muted">Doit généralement correspondre à l'adresse email d'authentification</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 mt-3">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-save"></i> Enregistrer la configuration
|
||||
</button>
|
||||
<a href="{{ url_for('crm') }}" class="btn btn-outline-secondary">
|
||||
Annuler
|
||||
</a>
|
||||
<button type="button" id="test_config" class="btn btn-info float-end">
|
||||
<i class="fas fa-vial"></i> Tester la configuration
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Test de la configuration
|
||||
document.getElementById('test_config').addEventListener('click', function() {
|
||||
// Récupérer les données du formulaire
|
||||
const formData = {
|
||||
smtp_server: document.getElementById('smtp_server').value,
|
||||
smtp_port: document.getElementById('smtp_port').value,
|
||||
username: document.getElementById('username').value,
|
||||
password: document.getElementById('password').value,
|
||||
sender_name: document.getElementById('sender_name').value,
|
||||
sender_email: document.getElementById('sender_email').value
|
||||
};
|
||||
|
||||
// Vérifier que tous les champs sont remplis
|
||||
for (const key in formData) {
|
||||
if (!formData[key]) {
|
||||
alert('Veuillez remplir tous les champs avant de tester la configuration.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Envoyer une requête pour tester la configuration
|
||||
fetch('/api/test_email_config', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(formData)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
alert('Test réussi ! La configuration email fonctionne correctement.');
|
||||
} else {
|
||||
alert('Échec du test : ' + data.error);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
alert('Erreur lors du test : ' + error);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
128
Templates/email/edit_template.html
Normal file
128
Templates/email/edit_template.html
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
{% extends 'layouts/base.html' %}
|
||||
|
||||
{% block title %}Suite Consultance - {% if template %}Modifier{% else %}Créer{% endif %} un template d'email{% endblock %}
|
||||
{% block module_name %}crm{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1 class="h2 text-crm">{% if template %}Modifier{% else %}Créer{% endif %} un template d'email</h1>
|
||||
<a href="{{ url_for('email_templates') }}" class="btn btn-outline-crm">
|
||||
<i class="fas fa-arrow-left"></i> Retour aux templates
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{{ url_for('edit_email_template', template_id=template.id) if template else url_for('create_email_template') }}">
|
||||
<div class="row g-3">
|
||||
<!-- Informations du template -->
|
||||
<div class="col-12">
|
||||
<h5 class="border-bottom pb-2 mb-3">Informations du template</h5>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="name" class="form-label">Nom du template *</label>
|
||||
<input type="text" class="form-control" id="name" name="name" value="{{ template.name if template else '' }}" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="description" class="form-label">Description</label>
|
||||
<input type="text" class="form-control" id="description" name="description" value="{{ template.description if template else '' }}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Contenu du template -->
|
||||
<div class="col-12">
|
||||
<h5 class="border-bottom pb-2 mb-3 mt-3">Contenu du template</h5>
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<div class="mb-3">
|
||||
<label for="subject" class="form-label">Sujet *</label>
|
||||
<input type="text" class="form-control" id="subject" name="subject" value="{{ template.subject if template else '' }}" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<div class="mb-3">
|
||||
<label for="content" class="form-label">Contenu *</label>
|
||||
<textarea class="form-control" id="content" name="content" rows="15" required>{{ template.content if template else '' }}</textarea>
|
||||
<small class="text-muted">Variables disponibles: {{name}}, {{company}}, {{email}}, {{phone}}. Vous pouvez utiliser du texte formaté HTML.</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 mt-3">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-save"></i> {% if template %}Mettre à jour{% else %}Enregistrer{% endif %} le template
|
||||
</button>
|
||||
<a href="{{ url_for('email_templates') }}" class="btn btn-outline-secondary">
|
||||
Annuler
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Aperçu du template -->
|
||||
<div class="card mt-4">
|
||||
<div class="card-header bg-info text-white">
|
||||
<h5 class="card-title mb-0">Aperçu du template</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<h6>Sujet</h6>
|
||||
<p id="preview_subject" class="border p-2">{{ template.subject if template else 'Votre sujet apparaîtra ici' }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<h6>Contenu</h6>
|
||||
<div id="preview_content" class="border p-3">
|
||||
{{ template.content|safe if template else '<p>Votre contenu apparaîtra ici</p>' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const subjectInput = document.getElementById('subject');
|
||||
const contentInput = document.getElementById('content');
|
||||
const previewSubject = document.getElementById('preview_subject');
|
||||
const previewContent = document.getElementById('preview_content');
|
||||
|
||||
// Mise à jour de l'aperçu lors de la saisie
|
||||
subjectInput.addEventListener('input', updatePreview);
|
||||
contentInput.addEventListener('input', updatePreview);
|
||||
|
||||
function updatePreview() {
|
||||
// Mettre à jour le sujet
|
||||
previewSubject.textContent = subjectInput.value || 'Votre sujet apparaîtra ici';
|
||||
|
||||
// Mettre à jour le contenu
|
||||
let content = contentInput.value || '<p>Votre contenu apparaîtra ici</p>';
|
||||
|
||||
// Remplacer les variables par des valeurs d'exemple
|
||||
const replacements = {
|
||||
'{{name}}': 'Nom du prospect',
|
||||
'{{company}}': 'Entreprise du prospect',
|
||||
'{{email}}': 'email@exemple.com',
|
||||
'{{phone}}': '01 23 45 67 89'
|
||||
};
|
||||
|
||||
for (const [variable, value] of Object.entries(replacements)) {
|
||||
content = content.replace(new RegExp(variable, 'g'), value);
|
||||
}
|
||||
|
||||
previewContent.innerHTML = content;
|
||||
}
|
||||
|
||||
// Initialiser l'aperçu au chargement
|
||||
updatePreview();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
112
Templates/email/email_history.html
Normal file
112
Templates/email/email_history.html
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
{% extends 'layouts/base.html' %}
|
||||
|
||||
{% block title %}Suite Consultance - Historique des emails{% endblock %}
|
||||
{% block module_name %}crm{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1 class="h2 text-crm">Historique des emails de {{ prospect.name }}</h1>
|
||||
<a href="{{ url_for('prospect_details', prospect_id=prospect.id) }}" class="btn btn-outline-crm">
|
||||
<i class="fas fa-arrow-left"></i> Retour au prospect
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
{% if emails %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Sujet</th>
|
||||
<th>Statut</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for email in emails %}
|
||||
<tr>
|
||||
<td>{{ email.timestamp|datetime }}</td>
|
||||
<td>{{ email.subject }}</td>
|
||||
<td>
|
||||
{% if email.success %}
|
||||
<span class="badge bg-success">Envoyé</span>
|
||||
{% else %}
|
||||
<span class="badge bg-danger">Échec</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-sm btn-info view-email" data-email='{{ email|tojson }}'>
|
||||
<i class="fas fa-eye"></i> Voir
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-info">
|
||||
Aucun email n'a été envoyé à ce prospect.
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal de visualisation d'email -->
|
||||
<div class="modal fade" id="emailModal" tabindex="-1" aria-labelledby="emailModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="emailModalLabel">Détails de l'email</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<h6>Date d'envoi</h6>
|
||||
<p id="email_date"></p>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<h6>Destinataire</h6>
|
||||
<p id="email_recipient"></p>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<h6>Sujet</h6>
|
||||
<p id="email_subject"></p>
|
||||
</div>
|
||||
<div>
|
||||
<h6>Contenu</h6>
|
||||
<div id="email_content" class="border p-3"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Fermer</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Afficher les détails de l'email dans le modal
|
||||
document.querySelectorAll('.view-email').forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
const emailData = JSON.parse(this.getAttribute('data-email'));
|
||||
|
||||
// Remplir les champs du modal
|
||||
document.getElementById('email_date').textContent = new Date(emailData.timestamp).toLocaleString();
|
||||
document.getElementById('email_recipient').textContent = emailData.to;
|
||||
document.getElementById('email_subject').textContent = emailData.subject;
|
||||
document.getElementById('email_content').innerHTML = emailData.content;
|
||||
|
||||
// Afficher le modal
|
||||
const emailModal = new bootstrap.Modal(document.getElementById('emailModal'));
|
||||
emailModal.show();
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
96
Templates/email/new_scraping.html
Normal file
96
Templates/email/new_scraping.html
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
{% extends "layouts/base.html" %}
|
||||
|
||||
{% block title %}Nouveau scraping d'emails{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-8 offset-md-2">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-search"></i> Nouveau scraping d'emails
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" id="scrapingForm">
|
||||
<div class="mb-3">
|
||||
<label for="url" class="form-label">URL à scraper *</label>
|
||||
<input type="url" class="form-control" id="url" name="url"
|
||||
placeholder="https://exemple.com" required>
|
||||
<div class="form-text">
|
||||
Entrez l'URL complète du site web à analyser pour extraire les adresses email.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="mb-3">
|
||||
<label for="max_pages" class="form-label">Nombre de pages à scraper</label>
|
||||
<select class="form-select" id="max_pages" name="max_pages">
|
||||
<option value="1">1 page</option>
|
||||
<option value="3" selected>3 pages</option>
|
||||
<option value="5">5 pages</option>
|
||||
<option value="10">10 pages</option>
|
||||
<option value="20">20 pages</option>
|
||||
<option value="50">50 pages</option>
|
||||
</select>
|
||||
<div class="form-text">
|
||||
Nombre de pages à analyser avec gestion automatique de la pagination.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="auto_create_prospects"
|
||||
name="auto_create_prospects" checked>
|
||||
<label class="form-check-label" for="auto_create_prospects">
|
||||
Créer automatiquement des prospects
|
||||
</label>
|
||||
<div class="form-text">
|
||||
Les contacts trouvés seront automatiquement ajoutés comme nouveaux prospects.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
<strong>Informations importantes :</strong>
|
||||
<ul class="mb-0 mt-2">
|
||||
<li>Le scrappeur analyse les pages avec gestion automatique de la pagination</li>
|
||||
<li>Spécialement conçu pour les annuaires d'entreprises et pages de résultats</li>
|
||||
<li>Extraction automatique des données : nom, entreprise, email, téléphone, localité</li>
|
||||
<li>Un délai de 2 secondes est appliqué entre chaque page pour éviter la surcharge</li>
|
||||
<li>Respectez les politiques du site web et les conditions d'utilisation</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between">
|
||||
<a href="{{ url_for('email_scraper_page') }}" class="btn btn-secondary">
|
||||
<i class="fas fa-arrow-left"></i> Retour
|
||||
</a>
|
||||
<button type="submit" class="btn btn-primary" id="submitBtn">
|
||||
<i class="fas fa-search"></i> Lancer le scraping
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById('scrapingForm').addEventListener('submit', function(e) {
|
||||
const submitBtn = document.getElementById('submitBtn');
|
||||
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Scraping en cours...';
|
||||
submitBtn.disabled = true;
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
91
Templates/email/scraper.html
Normal file
91
Templates/email/scraper.html
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
{% extends "layouts/base.html" %}
|
||||
|
||||
{% block title %}Scrapping d'emails{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1 class="h3 mb-0">Scrapping d'emails</h1>
|
||||
<a href="{{ url_for('new_email_scraping') }}" class="btn btn-primary">
|
||||
<i class="fas fa-plus"></i> Nouveau scraping
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% if scrapings %}
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">Historique des scrapings</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>URL</th>
|
||||
<th>Emails trouvés</th>
|
||||
<th>Pages scrapées</th>
|
||||
<th>Date</th>
|
||||
<th>Statut</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for scraping in scrapings %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{ scraping.url }}" target="_blank" class="text-decoration-none">
|
||||
{{ scraping.url[:50] }}{% if scraping.url|length > 50 %}...{% endif %}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-primary">{{ scraping.emails_count }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-info">{{ scraping.pages_count }}</span>
|
||||
</td>
|
||||
<td>
|
||||
{{ scraping.start_time | datetime('%d/%m/%Y %H:%M') }}
|
||||
</td>
|
||||
<td>
|
||||
{% if scraping.errors_count > 0 %}
|
||||
<span class="badge bg-warning">{{ scraping.errors_count }} erreur(s)</span>
|
||||
{% else %}
|
||||
<span class="badge bg-success">Succès</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm">
|
||||
<a href="{{ url_for('scraping_results', filename=scraping.filename) }}"
|
||||
class="btn btn-outline-primary">
|
||||
<i class="fas fa-eye"></i> Voir
|
||||
</a>
|
||||
<a href="{{ url_for('delete_scraping', filename=scraping.filename) }}"
|
||||
class="btn btn-outline-danger"
|
||||
onclick="return confirm('Êtes-vous sûr de vouloir supprimer ce scraping ?')">
|
||||
<i class="fas fa-trash"></i>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center py-5">
|
||||
<i class="fas fa-envelope fa-3x text-muted mb-3"></i>
|
||||
<h4 class="text-muted">Aucun scraping d'email effectué</h4>
|
||||
<p class="text-muted">Commencez par créer votre premier scraping d'emails.</p>
|
||||
<a href="{{ url_for('new_email_scraping') }}" class="btn btn-primary">
|
||||
<i class="fas fa-plus"></i> Nouveau scraping
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
289
Templates/email/scraping_results.html
Normal file
289
Templates/email/scraping_results.html
Normal file
|
|
@ -0,0 +1,289 @@
|
|||
{% extends "layouts/base.html" %}
|
||||
|
||||
{% block title %}Résultats du scraping{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1 class="h3 mb-0">Résultats du scraping</h1>
|
||||
<a href="{{ url_for('email_scraper_page') }}" class="btn btn-secondary">
|
||||
<i class="fas fa-arrow-left"></i> Retour
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Informations générales -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-3">
|
||||
<div class="card text-center">
|
||||
<div class="card-body">
|
||||
<h3 class="text-primary">{{ results.contacts|length }}</h3>
|
||||
<p class="text-muted mb-0">Contacts trouvés</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card text-center">
|
||||
<div class="card-body">
|
||||
<h3 class="text-info">{{ results.pages_scraped|length }}</h3>
|
||||
<p class="text-muted mb-0">Pages scrapées</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card text-center">
|
||||
<div class="card-body">
|
||||
<h3 class="text-warning">{{ results.errors|length }}</h3>
|
||||
<p class="text-muted mb-0">Erreurs</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card text-center">
|
||||
<div class="card-body">
|
||||
<small class="text-muted">Durée</small>
|
||||
<p class="mb-0">
|
||||
{% if results.start_time and results.end_time %}
|
||||
{% set start = results.start_time | parse_datetime %}
|
||||
{% set end = results.end_time | parse_datetime %}
|
||||
{{ ((end - start).total_seconds() / 60) | round(1) }} min
|
||||
{% else %}
|
||||
N/A
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- URL source -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">URL source</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<a href="{{ results.url }}" target="_blank" class="text-decoration-none">
|
||||
{{ results.url }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Contacts trouvés -->
|
||||
{% if results.contacts %}
|
||||
<div class="card mb-4">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="card-title mb-0">Contacts trouvés ({{ results.contacts|length }})</h5>
|
||||
<div>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" onclick="selectAllContacts()">
|
||||
Tout sélectionner
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-primary" onclick="createProspectsFromSelected()">
|
||||
Créer prospects sélectionnés
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="40"></th>
|
||||
<th>Email</th>
|
||||
<th>Nom</th>
|
||||
<th>Entreprise</th>
|
||||
<th>Téléphone</th>
|
||||
<th>Localité</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for contact in results.contacts %}
|
||||
<tr>
|
||||
<td>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input contact-checkbox" type="checkbox"
|
||||
value="{{ contact.email }}" id="contact_{{ loop.index }}">
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<strong>{{ contact.email }}</strong>
|
||||
</td>
|
||||
<td>
|
||||
{% if contact.first_name or contact.last_name %}
|
||||
{{ contact.first_name }} {{ contact.last_name }}
|
||||
{% elif contact.name %}
|
||||
{{ contact.name }}
|
||||
{% else %}
|
||||
<span class="text-muted">-</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if contact.company %}
|
||||
{{ contact.company }}
|
||||
{% else %}
|
||||
<span class="text-muted">-</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if contact.phone %}
|
||||
{{ contact.phone }}
|
||||
{% else %}
|
||||
<span class="text-muted">-</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if contact.location %}
|
||||
{{ contact.location }}
|
||||
{% else %}
|
||||
<span class="text-muted">-</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Pages scrapées -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">Pages scrapées ({{ results.pages_scraped|length }})</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>URL</th>
|
||||
<th>Niveau</th>
|
||||
<th>Contacts trouvés</th>
|
||||
<th>Statut</th>
|
||||
<th>Heure</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for page in results.pages_scraped %}
|
||||
<tr class="{% if page.status == 'error' %}table-danger{% endif %}">
|
||||
<td>
|
||||
<a href="{{ page.url }}" target="_blank" class="text-decoration-none">
|
||||
{{ page.url[:60] }}{% if page.url|length > 60 %}...{% endif %}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-secondary">{{ page.depth }}</span>
|
||||
</td>
|
||||
<td>
|
||||
{% if page.contacts_found %}
|
||||
<span class="badge bg-primary">{{ page.contacts_found }}</span>
|
||||
{% if page.contacts and page.contacts|length > 0 %}
|
||||
<small class="text-muted d-block">
|
||||
{% for contact in page.contacts[:3] %}
|
||||
{{ contact.email }}{% if not loop.last %}, {% endif %}
|
||||
{% endfor %}
|
||||
{% if page.contacts|length > 3 %}...{% endif %}
|
||||
</small>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span class="badge bg-light text-dark">0</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if page.status == 'success' %}
|
||||
<span class="badge bg-success">Succès</span>
|
||||
{% else %}
|
||||
<span class="badge bg-danger">Erreur</span>
|
||||
{% if page.error %}
|
||||
<small class="text-danger d-block">{{ page.error }}</small>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<small class="text-muted">
|
||||
{{ page.timestamp | datetime('%H:%M:%S') }}
|
||||
</small>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Erreurs -->
|
||||
{% if results.errors %}
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0 text-danger">Erreurs ({{ results.errors|length }})</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% for error in results.errors %}
|
||||
<div class="alert alert-danger mb-2">{{ error }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function selectAllContacts() {
|
||||
const checkboxes = document.querySelectorAll('.contact-checkbox');
|
||||
const allChecked = Array.from(checkboxes).every(cb => cb.checked);
|
||||
|
||||
checkboxes.forEach(cb => {
|
||||
cb.checked = !allChecked;
|
||||
});
|
||||
}
|
||||
|
||||
function createProspectsFromSelected() {
|
||||
const selectedEmails = Array.from(document.querySelectorAll('.contact-checkbox:checked'))
|
||||
.map(cb => cb.value);
|
||||
|
||||
if (selectedEmails.length === 0) {
|
||||
alert('Veuillez sélectionner au moins un contact');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm(`Créer ${selectedEmails.length} prospect(s) à partir des contacts sélectionnés ?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
fetch('/api/scraping/create_prospects', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
filename: '{{ filename }}',
|
||||
emails: selectedEmails
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
let message = `${data.created} prospect(s) créé(s) avec succès`;
|
||||
if (data.existing > 0) {
|
||||
message += ` (${data.existing} contact(s) déjà existant(s))`;
|
||||
}
|
||||
alert(message);
|
||||
|
||||
// Décocher les contacts traités
|
||||
document.querySelectorAll('.contact-checkbox:checked').forEach(cb => {
|
||||
cb.checked = false;
|
||||
});
|
||||
} else {
|
||||
alert(`Erreur: ${data.error}`);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Erreur:', error);
|
||||
alert('Erreur lors de la création des prospects');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
155
Templates/email/send_email.html
Normal file
155
Templates/email/send_email.html
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
{% extends 'layouts/base.html' %}
|
||||
|
||||
{% block title %}Suite Consultance - Envoyer un email{% endblock %}
|
||||
{% block module_name %}crm{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1 class="h2 text-crm">Envoyer un email à {{ prospect.name }}</h1>
|
||||
<a href="{{ url_for('prospect_details', prospect_id=prospect.id) }}" class="btn btn-outline-crm">
|
||||
<i class="fas fa-arrow-left"></i> Retour au prospect
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{{ url_for('send_prospect_email', prospect_id=prospect.id) }}">
|
||||
<div class="row g-3">
|
||||
<!-- Informations du destinataire -->
|
||||
<div class="col-12">
|
||||
<h5 class="border-bottom pb-2 mb-3">Destinataire</h5>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="to_email" class="form-label">Email du destinataire</label>
|
||||
<input type="email" class="form-control" id="to_email" name="to_email" value="{{ prospect.email }}" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="template_id" class="form-label">Template</label>
|
||||
<select class="form-select" id="template_id" name="template_id">
|
||||
<option value="">Email personnalisé</option>
|
||||
{% for template in templates %}
|
||||
<option value="{{ template.id }}">{{ template.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Contenu de l'email -->
|
||||
<div class="col-12">
|
||||
<h5 class="border-bottom pb-2 mb-3 mt-3">Contenu de l'email</h5>
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<div class="mb-3">
|
||||
<label for="subject" class="form-label">Sujet *</label>
|
||||
<input type="text" class="form-control" id="subject" name="subject" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<div class="mb-3">
|
||||
<label for="body" class="form-label">Contenu *</label>
|
||||
<textarea class="form-control" id="body" name="body" rows="10" required></textarea>
|
||||
<small class="text-muted">Vous pouvez utiliser du texte formaté HTML.</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<div class="mb-3">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="update_status" name="update_status" checked>
|
||||
<label class="form-check-label" for="update_status">
|
||||
Mettre à jour le statut du prospect
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6" id="status_selection">
|
||||
<div class="mb-3">
|
||||
<label for="new_status" class="form-label">Nouveau statut</label>
|
||||
<select class="form-select" id="new_status" name="new_status">
|
||||
<option value="Contacté" {% if prospect.status == 'Contacté' %}selected{% endif %}>Contacté</option>
|
||||
<option value="Qualifié" {% if prospect.status == 'Qualifié' %}selected{% endif %}>Qualifié</option>
|
||||
<option value="Proposition" {% if prospect.status == 'Proposition' %}selected{% endif %}>Proposition</option>
|
||||
<option value="Non intéressé" {% if prospect.status == 'Non intéressé' %}selected{% endif %}>Non intéressé</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 mt-3">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-paper-plane"></i> Envoyer l'email
|
||||
</button>
|
||||
<a href="{{ url_for('prospect_details', prospect_id=prospect.id) }}" class="btn btn-outline-secondary">
|
||||
Annuler
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const templateSelect = document.getElementById('template_id');
|
||||
const subjectInput = document.getElementById('subject');
|
||||
const bodyInput = document.getElementById('body');
|
||||
const updateStatusCheckbox = document.getElementById('update_status');
|
||||
const statusSelection = document.getElementById('status_selection');
|
||||
|
||||
// Gestion du choix de template
|
||||
templateSelect.addEventListener('change', function() {
|
||||
const templateId = this.value;
|
||||
if (templateId) {
|
||||
// Charger le contenu du template
|
||||
fetch(`/api/email_template/${templateId}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
const template = data.template;
|
||||
|
||||
// Remplacer les variables dans le sujet et le contenu
|
||||
let subject = template.subject;
|
||||
let content = template.content;
|
||||
|
||||
// Variables de remplacement
|
||||
const replacements = {
|
||||
'name': '{{ prospect.name }}',
|
||||
'company': '{{ prospect.company }}',
|
||||
'email': '{{ prospect.email }}',
|
||||
'phone': '{{ prospect.phone }}'
|
||||
};
|
||||
|
||||
// Appliquer les remplacements
|
||||
for (const [key, value] of Object.entries(replacements)) {
|
||||
// Escape $ character for Jinja by using string concatenation instead of template literals
|
||||
const placeholder = "{{" + key + "}}";
|
||||
subject = subject.replace(new RegExp(placeholder, 'g'), value);
|
||||
content = content.replace(new RegExp(placeholder, 'g'), value);
|
||||
}
|
||||
|
||||
subjectInput.value = subject;
|
||||
bodyInput.value = content;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Afficher/masquer le sélecteur de statut
|
||||
updateStatusCheckbox.addEventListener('change', function() {
|
||||
statusSelection.style.display = this.checked ? 'block' : 'none';
|
||||
});
|
||||
|
||||
// Initialiser l'affichage du sélecteur de statut
|
||||
statusSelection.style.display = updateStatusCheckbox.checked ? 'block' : 'none';
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
60
Templates/email/templates.html
Normal file
60
Templates/email/templates.html
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
{% extends 'layouts/base.html' %}
|
||||
|
||||
{% block title %}Suite Consultance - Templates d'email{% endblock %}
|
||||
{% block module_name %}crm{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1 class="h2 text-crm">Templates d'email</h1>
|
||||
<div>
|
||||
<a href="{{ url_for('crm') }}" class="btn btn-outline-crm me-2">
|
||||
<i class="fas fa-arrow-left"></i> Retour au CRM
|
||||
</a>
|
||||
<a href="{{ url_for('create_email_template') }}" class="btn btn-primary">
|
||||
<i class="fas fa-plus"></i> Créer un template
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nom</th>
|
||||
<th>Sujet</th>
|
||||
<th>Description</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% if templates %}
|
||||
{% for template in templates %}
|
||||
<tr>
|
||||
<td>{{ template.name }}</td>
|
||||
<td>{{ template.subject }}</td>
|
||||
<td>{{ template.description }}</td>
|
||||
<td>
|
||||
<div class="btn-group" role="group">
|
||||
<a href="{{ url_for('edit_email_template', template_id=template.id) }}" class="btn btn-sm btn-primary" title="Modifier">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<a href="{{ url_for('delete_email_template', template_id=template.id) }}" class="btn btn-sm btn-danger delete-confirm" title="Supprimer" onclick="return confirm('Êtes-vous sûr de vouloir supprimer ce template ?');">
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="4" class="text-center">Aucun template trouvé</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Loading…
Add table
Add a link
Reference in a new issue