diff --git a/apps/ops/src/components/planif/LeaveDialog.vue b/apps/ops/src/components/planif/LeaveDialog.vue new file mode 100644 index 0000000..6e994f3 --- /dev/null +++ b/apps/ops/src/components/planif/LeaveDialog.vue @@ -0,0 +1,78 @@ + + + + + diff --git a/apps/ops/src/pages/PlanificationPage.vue b/apps/ops/src/pages/PlanificationPage.vue index f03bf63..586679e 100644 --- a/apps/ops/src/pages/PlanificationPage.vue +++ b/apps/ops/src/pages/PlanificationPage.vue @@ -15,7 +15,7 @@ Semaine suivante Générer (auto) - Congés / absences + Congés / absences Notifier par SMS Créer un job @@ -97,7 +97,7 @@ 🗓  Horaires & personnel Générer l'horaire (semaine)solveur de quarts — la semaine affichée Générer les horaires (lot)modèle → plusieurs techs, N semaines - Congés / absences + Congés / absences Synchroniser les techniciens @@ -654,40 +654,8 @@ - - - -
Congés & disponibilités
- - -
- - - - - - - - - - - -
TechnicienTypeDuAuMotifStatut
{{ l.technician_name || l.technician }}{{ l.availability_type }}{{ l.from_date }}{{ l.to_date }}{{ l.reason }}{{ l.status }}
Aucune demande.
- -
Nouvelle demande
-
- - - - - - Maternité, invalidité… → à remplacer (pas juste sauter comme des vacances) - -
-
Une demande approuvée rend le tech indisponible pour le solveur sur ces dates.
-
-
-
+ + @@ -2055,6 +2023,7 @@ import { messageIdentity, priorityMeta, PRIORITY_LEVELS } from 'src/composables/ import { useSla, SLA_BADGE } from 'src/composables/useSla' // SLA existant (politiques éditables) — réutilisé sur les jobs (échéance = création + résolution) import { useAuthStore } from 'src/stores/auth' // email de l'agent (X-Authentik-Email) pour l'envoi de réponses client import TechSelect from 'src/components/shared/TechSelect.vue' +import LeaveDialog from 'src/components/planif/LeaveDialog.vue' // Congés & disponibilités (extrait — décomposition #4) import TicketStatusControl from 'src/components/shared/TicketStatusControl.vue' import TagEditor from 'src/components/shared/TagEditor.vue' // module de tags/compétences PARTAGÉ (chips colorées, création + palette, niveaux) — SOURCE UNIQUE : jobs ET techs import RouteMap from 'src/components/shared/RouteMap.vue' // carte de tournées réutilisable (revue dispatch auto + onglet Tournées) : OSRM réel, arrêts numérotés, fitTo @@ -2227,8 +2196,7 @@ async function saveNotifyContact (m) { else $q.notify({ type: 'negative', message: (r && r.error) || 'Échec de l’enregistrement' }) } catch (e) { err(e) } finally { m.saving = false } } -const showLeave = ref(false); const leaveRows = ref([]); const leaveFilter = ref('Demandé') -const newLeave = reactive({ technician: '', availability_type: 'Congé', from_date: '', to_date: '', reason: '', long_term: 0 }) +const showLeave = ref(false) // ouverture de (état leaveRows/leaveFilter/newLeave + fonctions déplacés dans le composant) const newTpl = reactive({ template_name: '', start: '08:00', end: '16:00', color: '#1976d2', on_call: 0 }) function numToTime (h) { const hh = Math.floor(h); const mm = Math.round((h - hh) * 60); return String(hh).padStart(2, '0') + ':' + String(mm).padStart(2, '0') } // Slider à 2 poignées pour le nouveau modèle (heures custom) ↔ newTpl.start/end @@ -5798,15 +5766,8 @@ function openTeamEditor () { editTechs.value = techs.value.map(t => ({ id: t.id, function onTeamSkills (t, items) { t.skills = normSkillList(items); saveSkills(t) } async function saveSkills (t) { const csv = normSkillList(t.skills).join(','); try { await roster.setTechSkills(t.id, csv); const tt = techs.value.find(x => x.id === t.id); if (tt) tt.skills = csv ? csv.split(',') : []; $q.notify({ type: 'positive', message: t.name + ' : compétences enregistrées' }) } catch (e) { err(e) } } -// congés / disponibilités -async function openLeave () { showLeave.value = true; await loadLeave() } -async function loadLeave () { try { leaveRows.value = (await roster.listAvailability(leaveFilter.value)).availability || [] } catch (e) { err(e) } } -async function approveLeave (l, reject) { try { await roster.approveAvailability(l.name, { reject, approver: 'ops' }); $q.notify({ type: reject ? 'info' : 'positive', message: (l.technician_name || l.technician) + (reject ? ' refusé' : ' approuvé') }); await loadLeave() } catch (e) { err(e) } } -async function createLeave () { - if (!newLeave.technician || !newLeave.from_date || !newLeave.to_date) { $q.notify({ type: 'warning', message: 'Technicien + dates requis' }); return } - const t = techs.value.find(x => x.id === newLeave.technician) - try { await roster.requestAvailability({ technician: newLeave.technician, technician_name: t ? t.name : '', availability_type: newLeave.availability_type, from_date: newLeave.from_date, to_date: newLeave.to_date, reason: newLeave.reason, long_term: newLeave.long_term }); $q.notify({ type: 'positive', message: 'Demande créée' }); newLeave.from_date = ''; newLeave.to_date = ''; newLeave.reason = ''; newLeave.long_term = 0; await loadLeave() } catch (e) { err(e) } -} +// congés / disponibilités → extrait dans components/planif/LeaveDialog.vue (état + fonctions déplacés). + async function saveEff (t) { const eff = Number(t.efficiency) || 1; try { await roster.setTechEfficiency(t.id, eff); const tt = techs.value.find(x => x.id === t.id); if (tt) tt.efficiency = eff; $q.notify({ type: 'positive', message: t.name + ' : cadence ' + eff }) } catch (e) { err(e) } } function loadedCost (t) { return Math.round(((Number(t.salary) || 0) * (1 + (Number(t.charges) || 0) / 100) + (Number(t.other) || 0)) * 100) / 100 } async function saveCost (t) { try { await roster.setTechCost(t.id, { salary: t.salary, charges: t.charges, other: t.other }); const tt = techs.value.find(x => x.id === t.id); if (tt) { tt.cost_salary_h = Number(t.salary) || 0; tt.cost_charges_pct = Number(t.charges) || 0; tt.cost_other_h = Number(t.other) || 0; tt.cost_h = loadedCost(t) } $q.notify({ type: 'positive', message: t.name + ' : ' + loadedCost(t) + ' $/h chargé' }) } catch (e) { err(e) } }