Ajoute les jours fériés, l'astreinte week-end 0h-0h et les ratios éditables
- Bouton F : marque un jour férié (dû 0, sans consommer de congé) ; il
qualifie les règles « ferie » et étend la fenêtre d'astreinte du jour.
- Samedi, dimanche et fériés : l'astreinte compte de 0h00 à 0h00 ; un jour
AS déclaré garde sa borne du lendemain 18h pour ne pas tronquer les
interventions nocturnes.
- Ratios d'astreinte éditables à la main dans Réglages (nom, jours, fenêtre,
ratio), avec ajout/suppression de règles et validation.
- Un quart d'heure entamé par deux interventions qui se suivent n'est compté
qu'une fois : les lignes d'événements somment au total du jour.
- Décompte mieux expliqué dans l'interface : quarts d'horloge (:00/:15/:30/:45),
fenêtre couverte par événement, exemple chiffré.
💘 Generated with Crush
Assisted-by: Crush:glm-5.2
This commit is contained in:
@ -54,24 +54,74 @@
|
||||
<span class="hist-label">Ratios des heures d'astreinte</span>
|
||||
<p class="settings-p">
|
||||
Pondération des heures d'astreinte selon le type de jour et la fenêtre horaire :
|
||||
le décompte se fait par tranches de 15 min (toute tranche entamée compte en
|
||||
totalité) et chaque tranche est multipliée par le ratio de la première règle qui
|
||||
la couvre. Une tranche hors règle compte ×1 (« Autre »). Règles globales à tous
|
||||
les comptes, ajustables dans <code>data/config.json</code> (clé
|
||||
<code>astreinte_facteurs</code>).
|
||||
le décompte se fait par quarts d'heure de l'horloge (:00, :15, :30, :45) — tout
|
||||
quart entamé compte 15 min entières (ex. 22:39 → 23:49 touche les quarts 22:30 à
|
||||
23:45, soit 6 quarts = 1h30) — puis chaque quart est multiplié par le ratio de la
|
||||
première règle qui le couvre. Un quart hors règle compte ×1 (« Autre ») ; la
|
||||
première règle qui couvre un quart gagne, place les fenêtres spécifiques avant
|
||||
les générales. Samedi, dimanche et jours fériés comptent de 0h00 à 0h00. Règles
|
||||
globales à tous les comptes.
|
||||
</p>
|
||||
<div class="as-ratios">
|
||||
{% for f in facteurs %}
|
||||
<div class="as-ratio-row">
|
||||
<span class="as-ratio-nom">{{ f.nom }}</span>
|
||||
<span class="as-ratio-jours">{% for j in f.jours %}{{ {'semaine': 'Lun–Ven', 'sam': 'Samedi', 'dim': 'Dimanche', 'ferie': 'Férié'}[j] }}{% if not loop.last %} · {% endif %}{% endfor %}</span>
|
||||
<span class="as-ratio-fenetre">{{ f.debut }} → {{ f.fin }}</span>
|
||||
<span class="as-ratio-facteur">×{{ '%g' % f.facteur }}</span>
|
||||
|
||||
{% if facteurs_saved %}
|
||||
<div class="settings-saved">Enregistré.</div>
|
||||
{% endif %}
|
||||
{% if facteurs_error %}
|
||||
<div class="settings-error">Règles invalides : chaque règle doit avoir un nom, au moins un type de jour, une fenêtre HH:MM et un ratio > 0 ; au moins une règle est requise (efface le nom pour supprimer une règle).</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" action="/settings/astreinte-facteurs" class="as-facteurs-form">
|
||||
<div id="as-ratio-rows">
|
||||
{% for f in facteurs %}
|
||||
{% set i = loop.index0 %}
|
||||
<div class="as-ratio-edit" data-idx="{{ i }}">
|
||||
<input type="text" class="login-input as-f-nom" name="f_nom_{{ i }}" value="{{ f.nom }}" maxlength="40" placeholder="Nom de la règle">
|
||||
<div class="as-f-jours">
|
||||
{% for j, lbl in [('semaine', 'Lun–Ven'), ('sam', 'Sam'), ('dim', 'Dim'), ('ferie', 'Férié')] %}
|
||||
<label class="as-f-jour" title="Types de jours couverts par la règle"><input type="checkbox" name="f_jours_{{ i }}" value="{{ j }}" {% if j in f.jours %}checked{% endif %}> {{ lbl }}</label>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<input class="login-input time-range-input as-f-h" type="time" name="f_debut_{{ i }}" value="{{ f.debut }}" aria-label="Début de fenêtre">
|
||||
<span class="settings-range-sep">→</span>
|
||||
<input class="login-input time-range-input as-f-h" type="time" name="f_fin_{{ i }}" value="{{ f.fin }}" aria-label="Fin de fenêtre (avant début = à cheval sur minuit)">
|
||||
<label class="as-f-x">×<input class="login-input as-f-facteur" type="number" name="f_facteur_{{ i }}" value="{{ '%g' % f.facteur }}" step="0.05" min="0.05" aria-label="Ratio"></label>
|
||||
<button type="button" class="as-f-del" title="Supprimer la règle" onclick="asDelRatio(this)">×</button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="as-ratio-actions">
|
||||
<button type="button" class="as-f-add" onclick="asAddRatio()">+ règle</button>
|
||||
<button class="login-btn" type="submit">Enregistrer les ratios</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function asAddRatio() {
|
||||
const rows = document.getElementById('as-ratio-rows');
|
||||
const idx = Math.max(-1, ...[...rows.querySelectorAll('.as-ratio-edit')].map(r => +r.dataset.idx)) + 1;
|
||||
const div = document.createElement('div');
|
||||
div.className = 'as-ratio-edit';
|
||||
div.dataset.idx = idx;
|
||||
div.innerHTML =
|
||||
'<input type="text" class="login-input as-f-nom" name="f_nom_' + idx + '" maxlength="40" placeholder="Nom de la règle">' +
|
||||
'<div class="as-f-jours">' +
|
||||
'<label class="as-f-jour"><input type="checkbox" name="f_jours_' + idx + '" value="semaine" checked> Lun–Ven</label>' +
|
||||
'<label class="as-f-jour"><input type="checkbox" name="f_jours_' + idx + '" value="sam"> Sam</label>' +
|
||||
'<label class="as-f-jour"><input type="checkbox" name="f_jours_' + idx + '" value="dim"> Dim</label>' +
|
||||
'<label class="as-f-jour"><input type="checkbox" name="f_jours_' + idx + '" value="ferie"> Férié</label>' +
|
||||
'</div>' +
|
||||
'<input class="login-input time-range-input as-f-h" type="time" name="f_debut_' + idx + '" value="20:00">' +
|
||||
'<span class="settings-range-sep">→</span>' +
|
||||
'<input class="login-input time-range-input as-f-h" type="time" name="f_fin_' + idx + '" value="08:00">' +
|
||||
'<label class="as-f-x">×<input class="login-input as-f-facteur" type="number" name="f_facteur_' + idx + '" value="1.5" step="0.05" min="0.05"></label>' +
|
||||
'<button type="button" class="as-f-del" title="Supprimer la règle" onclick="asDelRatio(this)">×</button>';
|
||||
rows.appendChild(div);
|
||||
div.querySelector('.as-f-nom').focus();
|
||||
}
|
||||
function asDelRatio(btn) { btn.closest('.as-ratio-edit').remove(); }
|
||||
</script>
|
||||
|
||||
<div class="settings-card">
|
||||
<span class="hist-label">Forfait par jour d'astreinte</span>
|
||||
<p class="settings-p">
|
||||
@ -226,22 +276,47 @@
|
||||
border: 1px solid var(--rule-l);
|
||||
padding: 0 .25rem;
|
||||
}
|
||||
.as-ratios { border: 1px solid var(--rule-l); display: flex; flex-direction: column; }
|
||||
.as-ratio-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(110px, auto) 1fr auto auto;
|
||||
gap: .5rem 1rem;
|
||||
align-items: baseline;
|
||||
padding: .45rem .8rem;
|
||||
.as-facteurs-form { display: flex; flex-direction: column; gap: .5rem; }
|
||||
.as-ratio-edit {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: .5rem .75rem;
|
||||
padding: .5rem .8rem;
|
||||
border: 1px solid var(--rule-l);
|
||||
}
|
||||
.as-ratio-row + .as-ratio-row { border-top: 1px solid var(--rule-l); }
|
||||
.as-ratio-nom { font-size: 13px; font-weight: 500; }
|
||||
.as-ratio-jours { font-size: 12px; color: var(--muted); }
|
||||
.as-ratio-fenetre { font-family: var(--mono); font-size: 12px; color: var(--muted); }
|
||||
.as-ratio-facteur { font-family: var(--mono); font-size: 13px; font-weight: 500; color: var(--accent-2); }
|
||||
.as-ratio-edit + .as-ratio-edit { margin-top: .4rem; }
|
||||
.as-ratio-edit .as-f-nom { width: 140px; flex-shrink: 0; }
|
||||
.as-f-jours { display: flex; gap: .75rem; flex-wrap: wrap; flex: 1; }
|
||||
.as-f-jour { font-size: 12px; color: var(--text); display: inline-flex; align-items: center; gap: .3rem; white-space: nowrap; }
|
||||
.as-ratio-edit .as-f-h { width: 90px; flex-shrink: 0; }
|
||||
.as-f-x { font-family: var(--mono); color: var(--muted); display: inline-flex; align-items: center; gap: .3rem; }
|
||||
.as-ratio-edit .as-f-facteur { width: 70px; }
|
||||
.as-f-del {
|
||||
border: 1px solid var(--rule);
|
||||
background: var(--surface);
|
||||
color: var(--muted);
|
||||
border-radius: 4px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
cursor: pointer;
|
||||
line-height: 1;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.as-f-del:hover { color: var(--neg); border-color: var(--neg); }
|
||||
.as-ratio-actions { display: flex; gap: .75rem; align-items: center; margin-top: .3rem; }
|
||||
.as-f-add {
|
||||
border: 1px dashed var(--rule);
|
||||
background: none;
|
||||
color: var(--muted);
|
||||
border-radius: 4px;
|
||||
padding: .35rem .7rem;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
}
|
||||
.as-f-add:hover { color: var(--accent); border-color: var(--accent); }
|
||||
@media (max-width: 600px) {
|
||||
.as-ratio-row { grid-template-columns: 1fr auto; row-gap: .1rem; }
|
||||
.as-ratio-jours, .as-ratio-fenetre { grid-column: 1 / -1; }
|
||||
.as-f-jours { order: 3; width: 100%; }
|
||||
}
|
||||
.settings-form { display: flex; flex-direction: column; gap: .5rem; max-width: 420px; }
|
||||
.settings-saved {
|
||||
|
||||
Reference in New Issue
Block a user