324 lines
16 KiB
HTML
324 lines
16 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="fr">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>{% block title %}Suite Consultance{% endblock %}</title>
|
||
<!-- Bootstrap CSS -->
|
||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
|
||
<!-- Font Awesome -->
|
||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||
<!-- Custom CSS -->
|
||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||
<link rel="stylesheet" href="{{ url_for('static', filename='css/tasks.css') }}">
|
||
{% block extra_css %}{% endblock %}
|
||
</head>
|
||
<body data-module="{% block module_name %}default{% endblock %}">
|
||
<!-- Navigation -->
|
||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary
|
||
{% if request.path.startswith('/crm') %}navbar-module-crm{% endif %}
|
||
{% if request.path.startswith('/propositions') %}navbar-module-propositions{% endif %}
|
||
{% if request.path.startswith('/devis') %}navbar-module-devis{% endif %}
|
||
{% if request.path.startswith('/projects') %}navbar-module-project{% endif %}
|
||
{% if request.path.startswith('/email') %}navbar-module-email{% endif %}">
|
||
<div class="container">
|
||
<a class="navbar-brand" href="{{ url_for('index') }}">
|
||
<img src="{{ url_for('static', filename='img/logo_light.png') }}" alt="Logo" height="40" class="d-inline-block align-text-top me-2">
|
||
Suite Consultance
|
||
</a>
|
||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||
<span class="navbar-toggler-icon"></span>
|
||
</button>
|
||
<div class="collapse navbar-collapse" id="navbarNav">
|
||
<ul class="navbar-nav ms-auto">
|
||
<li class="nav-item">
|
||
<a class="nav-link {% if request.path == url_for('index') %}active{% endif %}" href="{{ url_for('index') }}">
|
||
<i class="fas fa-home"></i> Accueil
|
||
</a>
|
||
</li>
|
||
<li class="nav-item">
|
||
<a class="nav-link {% if request.path.startswith('/crm') %}active{% endif %}" href="{{ url_for('crm') }}">
|
||
<i class="fas fa-users"></i> CRM
|
||
</a>
|
||
</li>
|
||
<li class="nav-item">
|
||
<a class="nav-link {% if request.path.startswith('/propositions') %}active{% endif %}" href="{{ url_for('propositions') }}">
|
||
<i class="fas fa-file-contract"></i> Propositions
|
||
</a>
|
||
</li>
|
||
<li class="nav-item">
|
||
<a class="nav-link {% if request.path.startswith('/devis') %}active{% endif %}" href="{{ url_for('devis') }}">
|
||
<i class="fas fa-file-invoice-dollar"></i> Devis
|
||
</a>
|
||
</li>
|
||
<li class="nav-item">
|
||
<a class="nav-link {% if request.path.startswith('/projects') %}active{% endif %}" href="{{ url_for('projects.projects_index') }}">
|
||
<i class="fas fa-diagram-project"></i> Projets
|
||
</a>
|
||
</li>
|
||
<li class="nav-item dropdown">
|
||
<a class="nav-link dropdown-toggle {% if request.path.startswith('/email') %}active{% endif %}" href="#" id="emailDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||
<i class="fas fa-envelope"></i> Emails
|
||
</a>
|
||
<ul class="dropdown-menu" aria-labelledby="emailDropdown">
|
||
<li><a class="dropdown-item" href="{{ url_for('email_templates') }}">
|
||
<i class="fas fa-file-alt"></i> Templates
|
||
</a></li>
|
||
<li><a class="dropdown-item" href="{{ url_for('email_config') }}">
|
||
<i class="fas fa-cog"></i> Configuration
|
||
</a></li>
|
||
<li><hr class="dropdown-divider"></li>
|
||
<li><a class="dropdown-item" href="{{ url_for('email_scraper_page') }}">
|
||
<i class="fas fa-search"></i> Scrapper d'emails
|
||
</a></li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
<button class="btn btn-outline-light ms-3 d-none d-lg-inline-flex" type="button" data-bs-toggle="offcanvas" data-bs-target="#tasksTodaySidebar" aria-controls="tasksTodaySidebar" title="Tâches du jour">
|
||
<i class="fas fa-list-check"></i>
|
||
<span class="badge rounded-pill bg-warning text-dark ms-1 tasks-today-badge" style="display:none;">0</span>
|
||
</button>
|
||
<a class="btn btn-outline-light ms-2 d-lg-none" data-bs-toggle="offcanvas" href="#tasksTodaySidebar" role="button" aria-controls="tasksTodaySidebar" title="Tâches du jour">
|
||
<i class="fas fa-list-check"></i>
|
||
<span class="badge rounded-pill bg-warning text-dark ms-1 tasks-today-badge" style="display:none;">0</span>
|
||
</a>
|
||
<button class="btn btn-light ms-2 d-none d-lg-inline-flex" type="button" data-bs-toggle="modal" data-bs-target="#createTaskModal" title="Nouvelle tâche">
|
||
<i class="fas fa-plus"></i>
|
||
</button>
|
||
<a class="btn btn-light ms-2 d-lg-none" data-bs-toggle="modal" href="#createTaskModal" role="button" title="Nouvelle tâche">
|
||
<i class="fas fa-plus"></i>
|
||
</a>
|
||
<button class="btn btn-outline-light ms-2" type="button" data-bs-toggle="modal" data-bs-target="#prospectSearchModal" title="Rechercher des prospects">
|
||
<i class="fas fa-magnifying-glass"></i>
|
||
</button>
|
||
<div class="form-check form-switch ms-3 text-light d-flex align-items-center">
|
||
<input class="form-check-input" type="checkbox" id="darkModeSwitch">
|
||
<label class="form-check-label ms-2" for="darkModeSwitch" id="darkModeLabel">
|
||
<i class="fas fa-sun"></i>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</nav>
|
||
|
||
<!-- Flash Messages -->
|
||
<div class="container mt-3">
|
||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||
{% if messages %}
|
||
{% for category, message in messages %}
|
||
<div class="alert alert-{{ category if category != 'error' else 'danger' }} alert-dismissible fade show" role="alert">
|
||
{{ message }}
|
||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||
</div>
|
||
{% endfor %}
|
||
{% endif %}
|
||
{% endwith %}
|
||
</div>
|
||
|
||
<!-- Main Content -->
|
||
<main class="container py-4">
|
||
<div class="row">
|
||
<div class="col-12">
|
||
{% block content %}{% endblock %}
|
||
</div>
|
||
</div>
|
||
</main>
|
||
|
||
<!-- Footer -->
|
||
<footer class="footer mt-auto py-3 bg-light">
|
||
<div class="container text-center">
|
||
<span class="text-muted">© 2025 Suite Consultance. Tous droits réservés.</span>
|
||
</div>
|
||
</footer>
|
||
|
||
{% include 'partials/tasks_sidebar.html' %}
|
||
{% include 'partials/task_create_modal.html' %}
|
||
|
||
<!-- Modal: Recherche Prospects -->
|
||
<div class="modal fade" id="prospectSearchModal" tabindex="-1" aria-labelledby="prospectSearchModalLabel" aria-hidden="true">
|
||
<div class="modal-dialog modal-xl modal-dialog-scrollable">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="prospectSearchModalLabel"><i class="fas fa-magnifying-glass me-2"></i>Recherche prospects</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Fermer"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<form id="prospectSearchForm" class="row g-3 mb-3">
|
||
<div class="col-md-4">
|
||
<label class="form-label">Recherche globale</label>
|
||
<input type="text" name="q" class="form-control" placeholder="Nom, email, société, ville">
|
||
</div>
|
||
<div class="col-md-4">
|
||
<label class="form-label">Nom</label>
|
||
<input type="text" name="name" class="form-control" placeholder="Nom">
|
||
</div>
|
||
<div class="col-md-4">
|
||
<label class="form-label">Email</label>
|
||
<input type="email" name="email" class="form-control" placeholder="email@exemple.com">
|
||
</div>
|
||
<div class="col-md-4">
|
||
<label class="form-label">Société</label>
|
||
<input type="text" name="company" class="form-control" placeholder="Société">
|
||
</div>
|
||
<div class="col-md-4">
|
||
<label class="form-label">Statut</label>
|
||
<input type="text" name="status" class="form-control" placeholder="ex: nouveau, en cours...">
|
||
</div>
|
||
<div class="col-md-4">
|
||
<label class="form-label">Ville</label>
|
||
<input type="text" name="city" class="form-control" placeholder="Ville">
|
||
</div>
|
||
<div class="col-md-4">
|
||
<label class="form-label">Tags</label>
|
||
<input type="text" name="tags" class="form-control" placeholder="tag1, tag2">
|
||
</div>
|
||
<div class="col-md-4">
|
||
<label class="form-label">Date de création - du</label>
|
||
<input type="date" name="date_from" class="form-control">
|
||
</div>
|
||
<div class="col-md-4">
|
||
<label class="form-label">Date de création - au</label>
|
||
<input type="date" name="date_to" class="form-control">
|
||
</div>
|
||
<div class="col-12 d-flex justify-content-end">
|
||
<button type="reset" class="btn btn-outline-secondary me-2" id="resetProspectSearch">Réinitialiser</button>
|
||
<button type="submit" class="btn btn-primary"><i class="fas fa-search me-2"></i>Chercher</button>
|
||
</div>
|
||
</form>
|
||
|
||
<div id="prospectSearchSummary" class="text-muted mb-2"></div>
|
||
<div id="prospectSearchResults" class="list-group"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
(function(){
|
||
const form = document.getElementById('prospectSearchForm');
|
||
const results = document.getElementById('prospectSearchResults');
|
||
const summary = document.getElementById('prospectSearchSummary');
|
||
const modalEl = document.getElementById('prospectSearchModal');
|
||
|
||
function buildQuery(params) {
|
||
const usp = new URLSearchParams();
|
||
Object.entries(params).forEach(([k,v])=>{
|
||
if(v !== undefined && v !== null && String(v).trim() !== '') {
|
||
usp.append(k, String(v).trim());
|
||
}
|
||
});
|
||
return usp.toString();
|
||
}
|
||
|
||
function serializeForm() {
|
||
const data = new FormData(form);
|
||
const obj = {};
|
||
data.forEach((v, k) => {
|
||
obj[k] = v;
|
||
});
|
||
return obj;
|
||
}
|
||
|
||
function renderResults(items) {
|
||
results.innerHTML = '';
|
||
if (!items || items.length === 0) {
|
||
results.innerHTML = '<div class="text-muted">Aucun prospect trouvé.</div>';
|
||
return;
|
||
}
|
||
items.forEach(it => {
|
||
const tags = (it.tags || []).join(', ');
|
||
const created = it.created_at ? ` • Créé le ${it.created_at}` : '';
|
||
const related = Array.isArray(it.related) ? it.related : [];
|
||
const div = document.createElement('a');
|
||
div.className = 'list-group-item list-group-item-action';
|
||
div.href = it.url || '#';
|
||
div.innerHTML = `
|
||
<div class="d-flex w-100 justify-content-between">
|
||
<h6 class="mb-1">${it.name || '(Sans nom)'}</h6>
|
||
<small class="text-muted">${it.status || ''}</small>
|
||
</div>
|
||
<div class="mb-1">
|
||
<span class="me-3"><i class="fas fa-building me-1"></i>${it.company || ''}</span>
|
||
<span class="me-3"><i class="fas fa-envelope me-1"></i>${it.email || ''}</span>
|
||
<span class="me-3"><i class="fas fa-location-dot me-1"></i>${it.city || ''}</span>
|
||
${created}
|
||
</div>
|
||
${tags ? `<small class="text-muted"><i class="fas fa-tags me-1"></i>${tags}</small>` : '' }
|
||
${related.length ? `<div class="mt-2">
|
||
${related.map(r => {
|
||
const icon = r.type === 'client' ? 'fa-user' : (r.type === 'project' ? 'fa-diagram-project' : 'fa-link');
|
||
return `<a href="${r.url}" class="badge rounded-pill bg-light text-primary border me-1"><i class="fas ${icon} me-1"></i>${r.type}</a>`;
|
||
}).join('')}
|
||
</div>` : ''}
|
||
`;
|
||
results.appendChild(div);
|
||
});
|
||
}
|
||
|
||
async function performSearch(e){
|
||
if (e) e.preventDefault();
|
||
const params = serializeForm();
|
||
const qs = buildQuery(params);
|
||
summary.textContent = 'Recherche en cours...';
|
||
results.innerHTML = '<div class="text-muted">Chargement...</div>';
|
||
try {
|
||
const res = await fetch('/api/crm/prospects/search?' + qs, {headers: {'X-Requested-With':'XMLHttpRequest'}});
|
||
if (!res.ok) throw new Error('HTTP ' + res.status);
|
||
const data = await res.json();
|
||
summary.textContent = `${data.count || 0} résultat(s)`;
|
||
renderResults(data.results || []);
|
||
} catch (err) {
|
||
summary.textContent = 'Erreur lors de la recherche.';
|
||
results.innerHTML = '<div class="text-danger">Impossible d’effectuer la recherche.</div>';
|
||
}
|
||
}
|
||
|
||
form.addEventListener('submit', performSearch);
|
||
document.getElementById('resetProspectSearch').addEventListener('click', function(){
|
||
// Laisser le reset natif vider le formulaire, puis effacer résultats
|
||
setTimeout(()=>{
|
||
summary.textContent = '';
|
||
results.innerHTML = '';
|
||
}, 0);
|
||
});
|
||
|
||
// Auto recherche quand la modale s’ouvre (facultatif)
|
||
modalEl.addEventListener('shown.bs.modal', () => {
|
||
if (!results.innerHTML) {
|
||
performSearch();
|
||
}
|
||
});
|
||
})();
|
||
</script>
|
||
|
||
<!-- Bootstrap JS Bundle with Popper -->
|
||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
|
||
<!-- Custom JS -->
|
||
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
|
||
<script>
|
||
(function(){
|
||
async function refreshTasksBadge(){
|
||
try{
|
||
const res = await fetch('{{ url_for("tasks.today_count") }}', {headers:{'X-Requested-With':'XMLHttpRequest'}});
|
||
if(!res.ok) throw new Error('HTTP ' + res.status);
|
||
const data = await res.json();
|
||
const count = Number(data.count) || 0;
|
||
document.querySelectorAll('.tasks-today-badge').forEach(el=>{
|
||
if(count > 0){
|
||
el.textContent = count;
|
||
el.style.display = 'inline-block';
|
||
} else {
|
||
el.style.display = 'none';
|
||
}
|
||
});
|
||
}catch(e){
|
||
// silencieux
|
||
}
|
||
}
|
||
document.addEventListener('DOMContentLoaded', refreshTasksBadge);
|
||
document.addEventListener('visibilitychange', ()=>{ if(!document.hidden) refreshTasksBadge(); });
|
||
})();
|
||
</script>
|
||
{% block extra_js %}{% endblock %}
|
||
</body>
|
||
</html>
|