219 lines
8.7 KiB
HTML
219 lines
8.7 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
|
<meta name="mobile-web-app-capable" content="yes">
|
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
<title>3D Pricing — Mobile</title>
|
|
<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>
|
|
body { background: #f4f6f9; padding-bottom: 2rem; }
|
|
.topbar {
|
|
background: #1a1d23; color: #fff; padding: .75rem 1rem;
|
|
display: flex; align-items: center; justify-content: space-between;
|
|
position: sticky; top: 0; z-index: 100;
|
|
}
|
|
.topbar .brand { font-weight: 700; font-size: 1.1rem; }
|
|
.topbar .brand span { color: #ff6b35; }
|
|
.section-title {
|
|
font-size: .7rem; font-weight: 700; text-transform: uppercase;
|
|
letter-spacing: .08em; color: #6b7280; padding: 1rem 1rem .4rem;
|
|
}
|
|
.slot-card {
|
|
background: #fff; border-radius: 10px; padding: .9rem 1rem;
|
|
margin: .4rem .75rem; box-shadow: 0 1px 4px rgba(0,0,0,.06);
|
|
}
|
|
.slot-card .job-name { font-weight: 600; font-size: .95rem; }
|
|
.slot-card .meta { font-size: .78rem; color: #6b7280; }
|
|
.slot-card .time { font-size: .82rem; color: #374151; }
|
|
.tab-bar {
|
|
display: flex; background: #fff; border-bottom: 2px solid #f0f0f0;
|
|
position: sticky; top: 53px; z-index: 99;
|
|
}
|
|
.tab-bar button {
|
|
flex: 1; border: none; background: none; padding: .7rem .5rem;
|
|
font-size: .8rem; color: #6b7280; font-weight: 600;
|
|
border-bottom: 2px solid transparent; margin-bottom: -2px;
|
|
transition: color .15s, border-color .15s;
|
|
}
|
|
.tab-bar button.active { color: #ff6b35; border-bottom-color: #ff6b35; }
|
|
.tab-pane { display: none; }
|
|
.tab-pane.active { display: block; }
|
|
.form-card { background: #fff; border-radius: 10px; margin: .75rem; padding: 1rem; box-shadow: 0 1px 4px rgba(0,0,0,.06); }
|
|
.btn-primary-orange { background: #ff6b35; color: #fff; border: none; border-radius: 8px; }
|
|
.btn-primary-orange:hover { background: #e85d2d; color: #fff; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div class="topbar">
|
|
<div class="brand"><i class="bi bi-layers-half me-1"></i>3D<span>Pricing</span></div>
|
|
<a href="/" class="btn btn-sm btn-outline-light btn-sm">
|
|
<i class="bi bi-grid me-1"></i>Bureau
|
|
</a>
|
|
</div>
|
|
|
|
<!-- Tabs -->
|
|
<div class="tab-bar">
|
|
<button class="active" onclick="showTab('planning', this)">
|
|
<i class="bi bi-calendar3 d-block mb-1"></i>Planning
|
|
</button>
|
|
<button onclick="showTab('block', this)">
|
|
<i class="bi bi-calendar-x d-block mb-1"></i>Indispo
|
|
</button>
|
|
<button onclick="showTab('shift', this)">
|
|
<i class="bi bi-arrow-left-right d-block mb-1"></i>Décaler
|
|
</button>
|
|
<button onclick="showTab('fail', this)">
|
|
<i class="bi bi-exclamation-triangle d-block mb-1"></i>Échec
|
|
</button>
|
|
</div>
|
|
|
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
|
{% for cat, msg in messages %}
|
|
<div class="alert alert-{{ cat }} mx-3 mt-2 mb-0 py-2 small">{{ msg }}</div>
|
|
{% endfor %}
|
|
{% endwith %}
|
|
|
|
<!-- ── Tab : Planning ─────────────────────────────────── -->
|
|
<div class="tab-pane active" id="tab-planning">
|
|
<div class="section-title">Prochaines impressions</div>
|
|
{% if upcoming %}
|
|
{% for s in upcoming %}
|
|
<div class="slot-card">
|
|
<div class="d-flex justify-content-between align-items-start">
|
|
<div>
|
|
<div class="job-name">{{ s.job_name }}</div>
|
|
<div class="meta">
|
|
<i class="bi bi-printer me-1"></i>{{ s.printer_name }}
|
|
{% if s.client_name %} · {{ s.client_name }}{% endif %}
|
|
</div>
|
|
<div class="time mt-1">
|
|
<i class="bi bi-clock me-1"></i>
|
|
{{ s.planned_start[:16].replace('T',' ') }} →
|
|
{{ s.planned_end[11:16] }}
|
|
</div>
|
|
</div>
|
|
<span class="badge {% if s.status=='done' %}bg-success{% elif s.status=='failed' %}bg-danger{% else %}bg-secondary{% endif %}">
|
|
{{ s.status }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
{% else %}
|
|
<div class="text-center text-muted py-5">
|
|
<i class="bi bi-calendar-check fs-1 d-block mb-2"></i>
|
|
Aucune impression planifiée.
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- ── Tab : Indisponibilité ──────────────────────────── -->
|
|
<div class="tab-pane" id="tab-block">
|
|
<div class="section-title">Ajouter une indisponibilité</div>
|
|
<div class="form-card">
|
|
<form method="POST">
|
|
<input type="hidden" name="action" value="block">
|
|
<div class="mb-3">
|
|
<label class="form-label fw-semibold">Date</label>
|
|
<input type="date" name="date" class="form-control" value="{{ today }}" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label fw-semibold">Type</label>
|
|
<select name="type" class="form-select">
|
|
<option value="off">🔴 Jour off (toutes machines)</option>
|
|
<option value="maintenance">🔧 Maintenance</option>
|
|
<option value="other">📌 Autre</option>
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label fw-semibold">Machine (optionnel)</label>
|
|
<select name="printer_id" class="form-select">
|
|
<option value="">Toutes les machines</option>
|
|
{% for p in printers %}
|
|
<option value="{{ p.id }}">{{ p.name }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label fw-semibold">Note</label>
|
|
<input type="text" name="notes" class="form-control" placeholder="Raison…">
|
|
</div>
|
|
<button type="submit" class="btn btn-primary-orange w-100">
|
|
<i class="bi bi-calendar-plus me-1"></i>Ajouter
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── Tab : Décaler ──────────────────────────────────── -->
|
|
<div class="tab-pane" id="tab-shift">
|
|
<div class="section-title">Décaler un créneau</div>
|
|
<div class="form-card">
|
|
<form method="POST">
|
|
<input type="hidden" name="action" value="shift">
|
|
<div class="mb-3">
|
|
<label class="form-label fw-semibold">Créneau à décaler</label>
|
|
<select name="slot_id" class="form-select" required>
|
|
<option value="">Choisir…</option>
|
|
{% for s in upcoming %}
|
|
<option value="{{ s.id }}">
|
|
{{ s.job_name }} — {{ s.planned_start[:16].replace('T',' ') }} [{{ s.printer_name }}]
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label fw-semibold">Nouveau début</label>
|
|
<input type="datetime-local" name="new_start" class="form-control" required>
|
|
</div>
|
|
<button type="submit" class="btn btn-primary-orange w-100">
|
|
<i class="bi bi-arrow-left-right me-1"></i>Décaler
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── Tab : Échec ────────────────────────────────────── -->
|
|
<div class="tab-pane" id="tab-fail">
|
|
<div class="section-title">Signaler un échec d'impression</div>
|
|
<div class="form-card">
|
|
<form method="POST">
|
|
<input type="hidden" name="action" value="fail">
|
|
<div class="mb-3">
|
|
<label class="form-label fw-semibold">Créneau concerné</label>
|
|
<select name="slot_id" class="form-select" required>
|
|
<option value="">Choisir…</option>
|
|
{% for s in upcoming %}
|
|
<option value="{{ s.id }}">
|
|
{{ s.job_name }} — {{ s.planned_start[:16].replace('T',' ') }} [{{ s.printer_name }}]
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label fw-semibold">Description du problème</label>
|
|
<textarea name="failure_note" class="form-control" rows="3"
|
|
placeholder="Détachement, sous-extrusion, bourrage…"></textarea>
|
|
</div>
|
|
<button type="submit" class="btn btn-danger w-100">
|
|
<i class="bi bi-exclamation-triangle me-1"></i>Signaler l'échec
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
|
<script>
|
|
function showTab(name, btn) {
|
|
document.querySelectorAll('.tab-pane').forEach(p => p.classList.remove('active'));
|
|
document.querySelectorAll('.tab-bar button').forEach(b => b.classList.remove('active'));
|
|
document.getElementById('tab-' + name).classList.add('active');
|
|
btn.classList.add('active');
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|