feat: viewer local upload, Bambu collapse, planning done/fail, stats waste, portal link in jobs, sidebar collapsible, settings layout fix
Deploy via Portainer / deploy (push) Successful in 0s
Deploy via Portainer / deploy (push) Successful in 0s
This commit is contained in:
+110
-30
@@ -7,35 +7,73 @@
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css" rel="stylesheet">
|
||||
<style>
|
||||
:root { --sidebar-w: 220px; }
|
||||
:root {
|
||||
--sidebar-w: 220px;
|
||||
--sidebar-collapsed-w: 52px;
|
||||
--sidebar-transition: width .2s ease, margin-left .2s ease;
|
||||
}
|
||||
body { background: #f4f6f9; }
|
||||
|
||||
#sidebar {
|
||||
width: var(--sidebar-w); min-height: 100vh;
|
||||
background: #1a1d23; position: fixed; top: 0; left: 0;
|
||||
display: flex; flex-direction: column;
|
||||
transition: width .2s ease;
|
||||
overflow: hidden;
|
||||
z-index: 1000;
|
||||
}
|
||||
#sidebar.collapsed { width: var(--sidebar-collapsed-w); }
|
||||
|
||||
#sidebar .brand {
|
||||
padding: 1.25rem 1.25rem 1rem; color: #fff;
|
||||
padding: 1rem 1.25rem; color: #fff;
|
||||
font-weight: 700; font-size: 1.1rem;
|
||||
border-bottom: 1px solid #2e323d;
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
white-space: nowrap;
|
||||
}
|
||||
#sidebar .brand .brand-text { transition: opacity .15s; }
|
||||
#sidebar.collapsed .brand .brand-text { opacity: 0; pointer-events: none; }
|
||||
#sidebar .brand span { color: #ff6b35; }
|
||||
#sidebar nav { flex: 1; padding: .5rem 0; }
|
||||
|
||||
#sidebarToggle {
|
||||
background: none; border: none; color: #9ba3af; cursor: pointer;
|
||||
padding: .2rem .3rem; border-radius: 4px; flex-shrink: 0;
|
||||
transition: color .15s, background .15s;
|
||||
font-size: 1rem; line-height: 1;
|
||||
}
|
||||
#sidebarToggle:hover { color: #fff; background: #2e323d; }
|
||||
|
||||
#sidebar nav { flex: 1; padding: .5rem 0; overflow: hidden; }
|
||||
#sidebar nav a {
|
||||
display: flex; align-items: center; gap: .65rem;
|
||||
padding: .6rem 1.25rem; color: #9ba3af;
|
||||
text-decoration: none; font-size: .875rem;
|
||||
transition: background .15s, color .15s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
#sidebar nav a:hover, #sidebar nav a.active { background: #2e323d; color: #fff; }
|
||||
#sidebar nav a.active { border-left: 3px solid #ff6b35; }
|
||||
#sidebar nav a i { flex-shrink: 0; font-size: 1rem; width: 1.1rem; text-align: center; }
|
||||
#sidebar nav .nav-label { transition: opacity .15s; }
|
||||
#sidebar.collapsed nav .nav-label { opacity: 0; }
|
||||
#sidebar nav .nav-section {
|
||||
padding: .75rem 1.25rem .25rem; font-size: .7rem;
|
||||
text-transform: uppercase; letter-spacing: .08em; color: #4b5563;
|
||||
transition: opacity .15s;
|
||||
}
|
||||
#sidebar.collapsed nav .nav-section { opacity: 0; }
|
||||
|
||||
#sidebar .sidebar-user { transition: opacity .15s; }
|
||||
#sidebar.collapsed .sidebar-user { opacity: 0; pointer-events: none; }
|
||||
|
||||
#main {
|
||||
margin-left: var(--sidebar-w);
|
||||
padding: 1.75rem 2rem;
|
||||
transition: margin-left .2s ease;
|
||||
min-width: 0;
|
||||
}
|
||||
#main.sidebar-collapsed { margin-left: var(--sidebar-collapsed-w); }
|
||||
|
||||
#main { margin-left: var(--sidebar-w); padding: 1.75rem 2rem; }
|
||||
.page-header { margin-bottom: 1.5rem; }
|
||||
.page-header h1 { font-size: 1.4rem; font-weight: 700; margin: 0; }
|
||||
|
||||
@@ -66,51 +104,56 @@
|
||||
<body>
|
||||
|
||||
<div id="sidebar">
|
||||
<div class="brand"><i class="bi bi-layers-half"></i> 3D<span>Pricing</span></div>
|
||||
<div class="brand">
|
||||
<span class="brand-text"><i class="bi bi-layers-half"></i> 3D<span>Pricing</span></span>
|
||||
<button id="sidebarToggle" title="Réduire / Agrandir">
|
||||
<i class="bi bi-layout-sidebar-reverse" id="sidebarToggleIcon"></i>
|
||||
</button>
|
||||
</div>
|
||||
<nav>
|
||||
<div class="nav-section">Navigation</div>
|
||||
<a href="{{ url_for('dashboard') }}" class="{% if request.endpoint=='dashboard' %}active{% endif %}">
|
||||
<i class="bi bi-speedometer2"></i> Dashboard
|
||||
<a href="{{ url_for('dashboard') }}" class="{% if request.endpoint=='dashboard' %}active{% endif %}" title="Dashboard">
|
||||
<i class="bi bi-speedometer2"></i><span class="nav-label"> Dashboard</span>
|
||||
</a>
|
||||
<a href="{{ url_for('jobs') }}" class="{% if request.endpoint in ['jobs','job_detail'] %}active{% endif %}">
|
||||
<i class="bi bi-printer"></i> Commandes
|
||||
<a href="{{ url_for('jobs') }}" class="{% if request.endpoint in ['jobs','job_detail'] %}active{% endif %}" title="Commandes">
|
||||
<i class="bi bi-printer"></i><span class="nav-label"> Commandes</span>
|
||||
</a>
|
||||
<a href="{{ url_for('new_job') }}" class="{% if request.endpoint=='new_job' %}active{% endif %}">
|
||||
<i class="bi bi-plus-circle"></i> Nouveau calcul
|
||||
<a href="{{ url_for('new_job') }}" class="{% if request.endpoint=='new_job' %}active{% endif %}" title="Nouveau calcul">
|
||||
<i class="bi bi-plus-circle"></i><span class="nav-label"> Nouveau calcul</span>
|
||||
</a>
|
||||
<a href="{{ url_for('clients') }}" class="{% if 'client' in request.endpoint %}active{% endif %}">
|
||||
<i class="bi bi-building"></i> Clients
|
||||
<a href="{{ url_for('clients') }}" class="{% if 'client' in request.endpoint %}active{% endif %}" title="Clients">
|
||||
<i class="bi bi-building"></i><span class="nav-label"> Clients</span>
|
||||
</a>
|
||||
<a href="{{ url_for('materials') }}" class="{% if 'material' in request.endpoint %}active{% endif %}">
|
||||
<i class="bi bi-boxes"></i> Matières
|
||||
<a href="{{ url_for('materials') }}" class="{% if 'material' in request.endpoint %}active{% endif %}" title="Matières">
|
||||
<i class="bi bi-boxes"></i><span class="nav-label"> Matières</span>
|
||||
</a>
|
||||
<a href="{{ url_for('profiles') }}" class="{% if 'profile' in request.endpoint %}active{% endif %}">
|
||||
<i class="bi bi-bookmark-star"></i> Templates
|
||||
<a href="{{ url_for('profiles') }}" class="{% if 'profile' in request.endpoint %}active{% endif %}" title="Templates">
|
||||
<i class="bi bi-bookmark-star"></i><span class="nav-label"> Templates</span>
|
||||
</a>
|
||||
<a href="{{ url_for('projects') }}" class="{% if request.endpoint in ['projects','new_project','edit_project','project_detail'] %}active{% endif %}">
|
||||
<i class="bi bi-folder2-open"></i> Projets
|
||||
<a href="{{ url_for('projects') }}" class="{% if request.endpoint in ['projects','new_project','edit_project','project_detail'] %}active{% endif %}" title="Projets">
|
||||
<i class="bi bi-folder2-open"></i><span class="nav-label"> Projets</span>
|
||||
</a>
|
||||
<a href="{{ url_for('planning') }}" class="{% if request.endpoint in ['planning','printers_page','working_schedule','calendar_blocks'] %}active{% endif %}">
|
||||
<i class="bi bi-calendar3"></i> Planning
|
||||
<a href="{{ url_for('planning') }}" class="{% if request.endpoint in ['planning','printers_page','working_schedule','calendar_blocks'] %}active{% endif %}" title="Planning">
|
||||
<i class="bi bi-calendar3"></i><span class="nav-label"> Planning</span>
|
||||
</a>
|
||||
<div class="nav-section">Analyse</div>
|
||||
<a href="{{ url_for('stats') }}" class="{% if request.endpoint=='stats' %}active{% endif %}">
|
||||
<i class="bi bi-bar-chart-line"></i> Statistiques
|
||||
<a href="{{ url_for('stats') }}" class="{% if request.endpoint=='stats' %}active{% endif %}" title="Statistiques">
|
||||
<i class="bi bi-bar-chart-line"></i><span class="nav-label"> Statistiques</span>
|
||||
</a>
|
||||
<a href="{{ url_for('export_csv') }}">
|
||||
<i class="bi bi-download"></i> Export CSV
|
||||
<a href="{{ url_for('export_csv') }}" title="Export CSV">
|
||||
<i class="bi bi-download"></i><span class="nav-label"> Export CSV</span>
|
||||
</a>
|
||||
<div class="nav-section">Config</div>
|
||||
<a href="{{ url_for('settings') }}" class="{% if request.endpoint=='settings' %}active{% endif %}">
|
||||
<i class="bi bi-sliders"></i> Paramètres
|
||||
<a href="{{ url_for('settings') }}" class="{% if request.endpoint=='settings' %}active{% endif %}" title="Paramètres">
|
||||
<i class="bi bi-sliders"></i><span class="nav-label"> Paramètres</span>
|
||||
</a>
|
||||
<a href="{{ url_for('mobile_quick') }}" class="{% if request.endpoint=='mobile_quick' %}active{% endif %}">
|
||||
<i class="bi bi-phone"></i> Vue mobile
|
||||
<a href="{{ url_for('mobile_quick') }}" class="{% if request.endpoint=='mobile_quick' %}active{% endif %}" title="Vue mobile">
|
||||
<i class="bi bi-phone"></i><span class="nav-label"> Vue mobile</span>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
{% if session.user %}
|
||||
<div style="padding:.75rem 1.25rem;border-top:1px solid #2e323d;margin-top:auto">
|
||||
<div class="sidebar-user" style="padding:.75rem 1.25rem;border-top:1px solid #2e323d;margin-top:auto">
|
||||
<div style="font-size:.75rem;color:#9ba3af;white-space:nowrap;overflow:hidden;text-overflow:ellipsis"
|
||||
title="{{ session.user.email }}">
|
||||
<i class="bi bi-person-circle me-1"></i>{{ session.user.name }}
|
||||
@@ -135,6 +178,43 @@
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
(function () {
|
||||
const SIDEBAR_KEY = 'sidebar_collapsed';
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
const main = document.getElementById('main');
|
||||
const btn = document.getElementById('sidebarToggle');
|
||||
const icon = document.getElementById('sidebarToggleIcon');
|
||||
|
||||
function applyState(collapsed, animate) {
|
||||
if (!animate) {
|
||||
sidebar.style.transition = 'none';
|
||||
main.style.transition = 'none';
|
||||
}
|
||||
sidebar.classList.toggle('collapsed', collapsed);
|
||||
main.classList.toggle('sidebar-collapsed', collapsed);
|
||||
icon.className = collapsed
|
||||
? 'bi bi-layout-sidebar'
|
||||
: 'bi bi-layout-sidebar-reverse';
|
||||
if (!animate) {
|
||||
// Force reflow then restore transition
|
||||
sidebar.offsetHeight;
|
||||
sidebar.style.transition = '';
|
||||
main.style.transition = '';
|
||||
}
|
||||
}
|
||||
|
||||
// Restore state without animation on page load
|
||||
const stored = localStorage.getItem(SIDEBAR_KEY) === 'true';
|
||||
applyState(stored, false);
|
||||
|
||||
btn.addEventListener('click', () => {
|
||||
const nowCollapsed = !sidebar.classList.contains('collapsed');
|
||||
applyState(nowCollapsed, true);
|
||||
localStorage.setItem(SIDEBAR_KEY, nowCollapsed);
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user