diff --git a/apps/ops/src/api/roster.js b/apps/ops/src/api/roster.js index 3117bc7..4cc3dc3 100644 --- a/apps/ops/src/api/roster.js +++ b/apps/ops/src/api/roster.js @@ -34,8 +34,14 @@ export const listRequirements = (start, days = 7) => jget(`/roster/requirements? export const createRequirement = (r) => jpost('/roster/requirements', r) export const listAssignments = (start, days = 7) => jget(`/roster/assignments?start=${start}&days=${days}`) export const getCoverage = (start, days = 7) => jget(`/roster/coverage?start=${start}&days=${days}`) -// Dispo restante par jour × segment (AM/PM/Soir) : capacité (quarts) − jobs placés, + charge due -export const getCapacity = (start, days = 7) => jget(`/roster/capacity?start=${start}&days=${days}`) +// Dispo restante par jour × segment (AM/PM/Soir) : capacité (quarts) − jobs placés, + charge due. +// skill (chaîne ou tableau) → dénominateur FILTRÉ : ne compte que les techs qui ont cette/ces compétence(s). +export const getCapacity = (start, days = 7, skill = '') => { + const s = (Array.isArray(skill) ? skill : (skill ? [skill] : [])).filter(Boolean) + return jget(`/roster/capacity?start=${start}&days=${days}${s.length ? '&skill=' + encodeURIComponent(s.join(',')) : ''}`) +} +// Résolveur « raison → compétence(s) » : { text?, department?, jobType?, useAI? } → { skills, primary, confidence, source, reason }. +export const resolveSkills = (body) => jpost('/roster/resolve-skills', body || {}) export const getStats = (start, days = 7) => jget(`/roster/stats?start=${start}&days=${days}`) export const getOccupancy = (start, days = 7) => jget(`/roster/occupancy?start=${start}&days=${days}`) export const getAbsences = (start, days = 7) => jget(`/roster/absences?start=${start}&days=${days}`) diff --git a/apps/ops/src/components/shared/AvailabilityByReason.vue b/apps/ops/src/components/shared/AvailabilityByReason.vue new file mode 100644 index 0000000..72023a1 --- /dev/null +++ b/apps/ops/src/components/shared/AvailabilityByReason.vue @@ -0,0 +1,176 @@ + + + + emit('update:modelValue', v)"> + + + + Disponibilité par motif + + + + + + Motif de la demande + + + {{ d.category }} + {{ d.skills.length ? 'Compétence : ' + d.skills.join(', ') + ' · ' + d.dur + ' h par défaut' : 'À distance / bureau — aucune compétence terrain' }} + + + + + + + + + + + + + + + + Compétence requise : + {{ s }} + + Aucune compétence terrain — traité à distance (tous les agents). + {{ confidenceLabel[resolved.confidence] || resolved.confidence }} + Effacer le motif + « {{ resolved.reason }} » + + + + + + + + + + {{ summary.free_h }} h libres + {{ summary.techs }} tech{{ summary.techs > 1 ? 's' : '' }} qualifié{{ summary.techs > 1 ? 's' : '' }}{{ primarySkill ? ' « ' + primarySkill + ' »' : '' }} · {{ summary.shift_h }} h de quart + + + occ = v" /> + + Choisis un motif ou dicte la raison pour voir la disponibilité ajustée à la compétence. + + + + + + Ouvre la recherche de créneau pré-remplie ({{ primarySkill || 'sans compétence' }} · {{ durH }} h · dès {{ dayShort(afterDate) }}). + + + + + + + diff --git a/apps/ops/src/components/shared/ConversationPanel.vue b/apps/ops/src/components/shared/ConversationPanel.vue index 51a805f..aa5256d 100644 --- a/apps/ops/src/components/shared/ConversationPanel.vue +++ b/apps/ops/src/components/shared/ConversationPanel.vue @@ -96,7 +96,7 @@ {{ ticketTitle(t) }} {{ ticketCategory(t) }} - {{ t.customer || '—' }} + {{ t.customer }}— @@ -145,6 +145,9 @@ Ajouter ce fil à une tâche (existante ou nouvelle) + + Dispo par motif : compétence probable de cette demande → disponibilité ajustée + Re-synchroniser depuis Gmail — récupère les réponses envoyées hors hub + les courriels manqués (panne / sortis de la boîte) @@ -1010,6 +1013,11 @@ + + + @@ -1030,6 +1038,7 @@ import { useAuthStore } from 'src/stores/auth' import { useRouter } from 'vue-router' import AddToTaskDialog from 'src/components/shared/AddToTaskDialog.vue' import EmailExpandDialog from 'src/components/shared/conversation/EmailExpandDialog.vue' +import AvailabilityByReason from 'src/components/shared/AvailabilityByReason.vue' // raison → compétence → disponibilité (dénominateur filtré) import { emailSrcdoc } from 'src/composables/useEmailRender' const $q = useQuasar() @@ -1048,6 +1057,7 @@ const { } = useConversations() // ── Identité d'expédition « De : » — défaut = groupe (anonymisé), modifiable par message ── +const availReasonOpen = ref(false) // « Dispo par motif » depuis la conversation const senders = ref({ default: '', identities: [] }) const replySendAs = ref('') const composeSendAs = ref('') diff --git a/apps/ops/src/components/shared/OccupancyBands.vue b/apps/ops/src/components/shared/OccupancyBands.vue new file mode 100644 index 0000000..f8701ac --- /dev/null +++ b/apps/ops/src/components/shared/OccupancyBands.vue @@ -0,0 +1,117 @@ + + + + + + + Aucune ressource{{ skill ? ' pour « ' + skill + ' »' : '' }} sur l'horizon. + + + + + {{ occShort(d) }} + + + {{ t.tech_name }} + + {{ pct(t.occupancy) }} · {{ t.free_h }} h libre + + + {{ occShort(c.date) }} · {{ offLabel(c) }} + + + {{ occShort(c.date) }} · {{ c.shift_start }}–{{ c.shift_end }}{{ pct(c.occupancy) }} occupé · {{ c.free_h }} h libre · {{ c.jobs }} job{{ c.jobs > 1 ? 's' : '' }} + + + + + + occupé + réservé + libre + pas de quart + clic sur un jour → cale la recherche + + + + + + diff --git a/apps/ops/src/components/shared/detail-sections/IssueDetail.vue b/apps/ops/src/components/shared/detail-sections/IssueDetail.vue index 9c1a728..0a58a8f 100644 --- a/apps/ops/src/components/shared/detail-sections/IssueDetail.vue +++ b/apps/ops/src/components/shared/detail-sections/IssueDetail.vue @@ -12,7 +12,7 @@ - {{ customerLabel || doc.customer }} + {{ customerLabel || doc.customer }} Délier le client @@ -145,6 +145,11 @@ ProjetModèle multi-étapes (installation…) — les tâches en découlent + + + + Dispo par motifCompétence requise → disponibilité ajustée, puis créer la tâche + @@ -279,6 +284,14 @@ :existing-jobs="dispatchJobs" @created="onJobCreated" /> + + +