Ajoute colonnes Δ sem. / Fin cible, supprime solde global, indicateurs minima

- Nouvelles colonnes : Δ sem. (solde cumulé semaine) et Fin cible (heure de
  sortie aprem optimale pour 0h suppl. en fin de semaine)
- Δ sem. n'est affiché que quand la journée est complète et la chaîne de jours
  ininterrompue (pas de saut de jour non saisi)
- Fin cible disparaît quand la journée est validée (passée/complète)
- Delta = "—" pour les jours non travaillés (futur ou congé jour)
- Indicateur warn (fond rouge) sur matin/aprem si horaires < minimum
  (matin 09:00→12:00, aprem 14:00→17:00) ; mise à jour live via JS
- Suppression du solde global (3 cartes au lieu de 4)
- Tableau sans largeurs fixes + container 1100 px pour éviter scroll
  horizontal sur écrans normaux ; touch-scroll conservé sur mobile
- Base reste 39h/semaine (7h48/jour)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
toto
2026-06-24 15:35:17 +02:00
parent 6f6c7f154e
commit 6258fd3662
7 changed files with 197 additions and 54 deletions

View File

@ -1,3 +1,13 @@
{% set matin_cg = 'matin' in j.conge or 'jour' in j.conge %}
{% set aprem_cg = 'aprem' in j.conge or 'jour' in j.conge %}
{% set matin_warn = not matin_cg and j.du_min > 0 and (
(j.matin_entree and j.matin_entree > '09:00') or
(j.matin_sortie and j.matin_sortie < '12:00')
) %}
{% set aprem_warn = not aprem_cg and j.du_min > 0 and (
(j.aprem_entree and j.aprem_entree > '14:00') or
(j.aprem_sortie and j.aprem_sortie < '17:00')
) %}
<tr id="row-{{ j.date }}">
<td class="col-jour">
@ -6,7 +16,7 @@
</td>
<td class="c-matin">
<div id="cg-matin-{{ j.date }}" class="td-fill{% if 'matin' in j.conge or 'jour' in j.conge %} cg{% endif %}">
<div id="cg-matin-{{ j.date }}" class="td-fill{% if matin_cg %} cg{% elif matin_warn %} warn{% endif %}">
<div class="time-pair">
<input class="time-input" type="time" name="matin_entree"
data-date="{{ j.date }}" value="{{ j.matin_entree or '' }}">
@ -18,7 +28,7 @@
</td>
<td class="c-aprem">
<div id="cg-aprem-{{ j.date }}" class="td-fill{% if 'aprem' in j.conge or 'jour' in j.conge %} cg{% endif %}">
<div id="cg-aprem-{{ j.date }}" class="td-fill{% if aprem_cg %} cg{% elif aprem_warn %} warn{% endif %}">
<div class="time-pair">
<input class="time-input" type="time" name="aprem_entree"
data-date="{{ j.date }}" value="{{ j.aprem_entree or '' }}">
@ -37,14 +47,36 @@
<td class="col-calc">
<span id="calc-{{ j.date }}-delta">
<span class="{% if j.delta_min > 0 %}delta-pos{% elif j.delta_min < 0 %}delta-neg{% else %}delta-zero{% endif %}">
{%- if j.travaille_min or j.du_min -%}
<span class="{% if j.travaille_min and j.delta_min > 0 %}delta-pos{% elif j.travaille_min and j.delta_min < 0 %}delta-neg{% else %}delta-zero{% endif %}">
{%- if j.travaille_min -%}
{% if j.delta_min > 0 %}+{% endif %}{{ j.delta }}
{%- else %}—{%- endif %}
</span>
</span>
</td>
<td class="col-cumul">
<span id="calc-{{ j.date }}-cumul">
{%- if j.cumul_visible -%}
<span class="{% if j.delta_cumul_min > 0 %}delta-pos{% elif j.delta_cumul_min < 0 %}delta-neg{% else %}delta-zero{% endif %}">
{% if j.delta_cumul_min > 0 %}+{% elif j.delta_cumul_min < 0 %}-{% endif %}{{ j.delta_cumul if j.delta_cumul_min != 0 else '0h00' }}
</span>
{%- else -%}
<span class="delta-zero"></span>
{%- endif %}
</span>
</td>
<td class="col-cible">
<span id="calc-{{ j.date }}-cible">
{%- if j.sortie_cible and not j.is_complete -%}
<span class="cible-num">{{ j.sortie_cible }}</span>
{%- else -%}
<span class="delta-zero"></span>
{%- endif %}
</span>
</td>
<td class="col-save">
<button class="btn-valider"
hx-post="/pointage/{{ j.date }}"

View File

@ -14,11 +14,5 @@
{% if result.solde_min > 0 %}+{% endif %}{{ result.solde }}
</div>
</div>
<div class="solde-item {% if solde_global_min > 0 %}item-pos{% elif solde_global_min < 0 %}item-neg{% endif %}">
<div class="solde-eyebrow">Solde global</div>
<div class="solde-num {% if solde_global_min > 0 %}pos{% elif solde_global_min < 0 %}neg{% endif %}">
{% if solde_global_min > 0 %}+{% endif %}{{ solde_global }}
</div>
</div>
</div>
</div>

View File

@ -82,7 +82,7 @@
/* ── Layout ── */
.container {
max-width: 940px;
max-width: 1100px;
margin: 0 auto;
padding: 2rem 1.5rem;
display: flex;
@ -124,7 +124,7 @@
/* ── Soldes ── */
.soldes {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-columns: repeat(3, 1fr);
border: 1.5px solid var(--rule);
background: var(--surface);
}
@ -156,12 +156,12 @@
.solde-num.neutral { color: var(--text); }
/* ── Table ── */
.table-section { overflow-x: auto; }
.table-section { overflow-x: auto; -webkit-overflow-scrolling: touch; }
table {
width: 100%;
border-collapse: collapse;
border: 1.5px solid var(--rule);
background: var(--surface);
white-space: nowrap;
}
.th-group th {
@ -272,14 +272,27 @@
.time-input:focus { outline: none; border-color: var(--accent); background: #fff; }
tr.htmx-request .time-input { opacity: .4; }
.col-calc { text-align: right; white-space: nowrap; width: 72px; }
.col-calc { text-align: right; padding-left: .5rem; }
.calc-num { font-family: var(--mono); font-size: 13px; color: var(--muted); }
.delta-pos { font-family: var(--mono); font-size: 13px; font-weight: 500; color: var(--pos); }
.delta-neg { font-family: var(--mono); font-size: 13px; font-weight: 500; color: var(--neg); }
.delta-zero{ font-family: var(--mono); font-size: 13px; color: var(--rule); }
.col-save { width: 36px; text-align: center; padding: .4rem .3rem; }
.g-save { width: 36px; background: var(--ground); }
/* Warn state: time input violates minimum directive */
td.c-matin .td-fill.warn,
td.c-aprem .td-fill.warn { border-left-color: var(--neg); background: var(--neg-bg); }
.td-fill.warn .time-input { background: transparent; }
tbody tr:hover .td-fill.warn { background: var(--neg-bg); }
/* Cumulative delta and sortie cible columns */
.col-cumul { text-align: right; padding-left: .5rem; }
.col-cible { text-align: right; padding-left: .5rem; }
.g-cumul { text-align: right; }
.g-cible { text-align: right; }
.cible-num { font-family: var(--mono); font-size: 12px; color: var(--accent); font-weight: 500; }
.col-save { text-align: center; padding: .4rem .3rem; }
.g-save { background: var(--ground); }
.btn-valider {
display: inline-flex;
align-items: center;
@ -294,7 +307,7 @@
}
.btn-valider:hover { background: var(--accent); color: #fff; }
.btn-valider.htmx-request { opacity: .4; pointer-events: none; }
.col-conge { width: 88px; text-align: center; }
.col-conge { text-align: center; }
.conge-btns { display: flex; gap: 3px; justify-content: center; }
.btn-cg {
display: inline-flex;

View File

@ -46,7 +46,9 @@
<th class="g-matin">Matin</th>
<th class="g-aprem">Après-midi</th>
<th class="g-calc" rowspan="2">Travaillé</th>
<th class="g-calc" rowspan="2">Delta</th>
<th class="g-calc" rowspan="2">Δ jour</th>
<th class="g-cumul" rowspan="2">Δ sem.</th>
<th class="g-cible" rowspan="2">Fin cible</th>
<th class="g-save" rowspan="2"></th>
<th class="g-conge" rowspan="2">Congé</th>
</tr>
@ -202,4 +204,25 @@
})();
</script>
<script>
// Live warn on time inputs that violate minimum directives
document.addEventListener('input', function(e) {
if (!e.target.matches('.time-input')) return;
var date = e.target.dataset.date;
var me = document.querySelector('[name="matin_entree"][data-date="' + date + '"]');
var ms = document.querySelector('[name="matin_sortie"][data-date="' + date + '"]');
var ae = document.querySelector('[name="aprem_entree"][data-date="' + date + '"]');
var as = document.querySelector('[name="aprem_sortie"][data-date="' + date + '"]');
var maDiv = document.getElementById('cg-matin-' + date);
var apDiv = document.getElementById('cg-aprem-' + date);
if (maDiv && !maDiv.classList.contains('cg')) {
var warn = (me && me.value && me.value > '09:00') || (ms && ms.value && ms.value < '12:00');
maDiv.classList.toggle('warn', !!warn);
}
if (apDiv && !apDiv.classList.contains('cg')) {
var warnA = (ae && ae.value && ae.value > '14:00') || (as && as.value && as.value < '17:00');
apDiv.classList.toggle('warn', !!warnA);
}
});
</script>
{% endblock %}