SuiteConsultance/Templates/layouts/base.html
2025-09-20 13:18:04 +02:00

324 lines
16 KiB
HTML
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">
<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 deffectuer 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 souvre (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>