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 %}
|
||||
Loading…
Add table
Add a link
Reference in a new issue