- Extract useDragDrop.js (drag/drop, block move, resize) - Extract useSelection.js (lasso, multi-select, hover linking) - Extract WeekCalendar.vue, MonthCalendar.vue, RightPanel.vue - DispatchV2Page.vue: 3018 → 1438 lines (orchestration only) - Remove <style scoped> — styles cascade to child components - Add .dockerignore (build context 214MB → 112KB) - Add infra/ with docker-compose reference and .env.example Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
148 lines
6.5 KiB
JavaScript
148 lines
6.5 KiB
JavaScript
// ── Selection composable: lasso, multi-select, hover linking, batch ops ───────
|
|
import { ref, computed } from 'vue'
|
|
import { localDateStr } from './useHelpers'
|
|
|
|
export function useSelection (deps) {
|
|
const { store, periodStart, smartAssign, invalidateRoutes, fullUnassign } = deps
|
|
|
|
const hoveredJobId = ref(null)
|
|
const selectedJob = ref(null) // { job, techId, isAssist?, assistTechId? }
|
|
const multiSelect = ref([]) // [{ job, techId, isAssist?, assistTechId? }]
|
|
|
|
// ── Select / toggle ─────────────────────────────────────────────────────────
|
|
function selectJob (job, techId, isAssist = false, assistTechId = null, event = null, rightPanel = null) {
|
|
const entry = { job, techId, isAssist, assistTechId }
|
|
const isMulti = event && (event.ctrlKey || event.metaKey)
|
|
if (isMulti) {
|
|
const idx = multiSelect.value.findIndex(s => s.job.id === job.id && s.isAssist === isAssist)
|
|
if (idx >= 0) multiSelect.value.splice(idx, 1)
|
|
else multiSelect.value.push(entry)
|
|
selectedJob.value = entry
|
|
} else {
|
|
multiSelect.value = []
|
|
const same = selectedJob.value?.job?.id === job.id && selectedJob.value?.isAssist === isAssist && selectedJob.value?.assistTechId === assistTechId
|
|
selectedJob.value = same ? null : entry
|
|
if (!same && rightPanel !== undefined) {
|
|
const tech = store.technicians.find(t => t.id === (techId || job.assignedTech))
|
|
if (rightPanel !== null && typeof rightPanel === 'object' && 'value' in rightPanel) {
|
|
rightPanel.value = { mode: 'details', data: { job, tech: tech || null } }
|
|
}
|
|
} else if (rightPanel !== null && typeof rightPanel === 'object' && 'value' in rightPanel) {
|
|
rightPanel.value = null
|
|
}
|
|
}
|
|
}
|
|
|
|
function isJobMultiSelected (jobId, isAssist = false) {
|
|
return multiSelect.value.some(s => s.job.id === jobId && s.isAssist === isAssist)
|
|
}
|
|
|
|
// ── Batch ops ─────────────────────────────────────────────────────────────────
|
|
function batchUnassign () {
|
|
if (!multiSelect.value.length) return
|
|
multiSelect.value.forEach(s => {
|
|
if (s.isAssist && s.assistTechId) store.removeAssistant(s.job.id, s.assistTechId)
|
|
else fullUnassign(s.job)
|
|
})
|
|
multiSelect.value = []; selectedJob.value = null
|
|
invalidateRoutes()
|
|
}
|
|
|
|
function batchMoveTo (techId) {
|
|
if (!multiSelect.value.length) return
|
|
const dayStr = localDateStr(periodStart.value)
|
|
multiSelect.value.filter(s => !s.isAssist).forEach(s => smartAssign(s.job, techId, dayStr))
|
|
multiSelect.value = []; selectedJob.value = null
|
|
invalidateRoutes()
|
|
}
|
|
|
|
// ── Lasso ─────────────────────────────────────────────────────────────────────
|
|
const lasso = ref(null)
|
|
const boardScroll = ref(null)
|
|
|
|
const lassoStyle = computed(() => {
|
|
if (!lasso.value) return {}
|
|
const l = lasso.value
|
|
return {
|
|
left: Math.min(l.x1, l.x2) + 'px', top: Math.min(l.y1, l.y2) + 'px',
|
|
width: Math.abs(l.x2 - l.x1) + 'px', height: Math.abs(l.y2 - l.y1) + 'px',
|
|
}
|
|
})
|
|
|
|
function startLasso (e) {
|
|
if (e.target.closest('.sb-block, .sb-chip, .sb-res-cell, .sb-travel-trail, button, input, select, a')) return
|
|
if (e.button !== 0) return
|
|
e.preventDefault()
|
|
if (!e.ctrlKey && !e.metaKey) {
|
|
if (selectedJob.value || multiSelect.value.length) {
|
|
selectedJob.value = null; multiSelect.value = []
|
|
}
|
|
}
|
|
const rect = boardScroll.value.getBoundingClientRect()
|
|
const x = e.clientX - rect.left + boardScroll.value.scrollLeft
|
|
const y = e.clientY - rect.top + boardScroll.value.scrollTop
|
|
lasso.value = { x1: x, y1: y, x2: x, y2: y }
|
|
}
|
|
|
|
function moveLasso (e) {
|
|
if (!lasso.value) return
|
|
e.preventDefault()
|
|
const rect = boardScroll.value.getBoundingClientRect()
|
|
lasso.value.x2 = e.clientX - rect.left + boardScroll.value.scrollLeft
|
|
lasso.value.y2 = e.clientY - rect.top + boardScroll.value.scrollTop
|
|
}
|
|
|
|
function endLasso () {
|
|
if (!lasso.value) return
|
|
const l = lasso.value
|
|
const w = Math.abs(l.x2 - l.x1), h = Math.abs(l.y2 - l.y1)
|
|
if (w > 10 && h > 10) {
|
|
const boardRect = boardScroll.value.getBoundingClientRect()
|
|
const lassoLeft = Math.min(l.x1, l.x2) - boardScroll.value.scrollLeft + boardRect.left
|
|
const lassoTop = Math.min(l.y1, l.y2) - boardScroll.value.scrollTop + boardRect.top
|
|
const lassoRight = lassoLeft + w, lassoBottom = lassoTop + h
|
|
const blocks = boardScroll.value.querySelectorAll('.sb-block[data-job-id], .sb-chip')
|
|
const selected = []
|
|
blocks.forEach(el => {
|
|
const r = el.getBoundingClientRect()
|
|
if (r.right > lassoLeft && r.left < lassoRight && r.bottom > lassoTop && r.top < lassoBottom) {
|
|
const jobId = el.dataset?.jobId
|
|
if (jobId) {
|
|
const job = store.jobs.find(j => j.id === jobId)
|
|
if (job) selected.push({ job, techId: job.assignedTech, isAssist: false, assistTechId: null })
|
|
}
|
|
}
|
|
})
|
|
if (selected.length) {
|
|
multiSelect.value = selected
|
|
if (selected.length === 1) selectedJob.value = selected[0]
|
|
}
|
|
}
|
|
lasso.value = null
|
|
}
|
|
|
|
// ── Hover linking helpers ─────────────────────────────────────────────────────
|
|
function techHasLinkedJob (tech) {
|
|
const hId = hoveredJobId.value, sId = selectedJob.value?.job?.id
|
|
if (hId && (tech.assistJobs || []).some(j => j.id === hId)) return true
|
|
if (hId && tech.queue.some(j => j.id === hId)) return true
|
|
if (sId && !selectedJob.value?.isAssist && (tech.assistJobs || []).some(j => j.id === sId)) return true
|
|
if (sId && selectedJob.value?.isAssist && tech.queue.some(j => j.id === sId)) return true
|
|
return false
|
|
}
|
|
|
|
function techIsHovered (tech) {
|
|
const hId = hoveredJobId.value
|
|
if (!hId) return false
|
|
const job = tech.queue.find(j => j.id === hId)
|
|
return job && job.assistants?.length > 0
|
|
}
|
|
|
|
return {
|
|
hoveredJobId, selectedJob, multiSelect,
|
|
selectJob, isJobMultiSelected, batchUnassign, batchMoveTo,
|
|
lasso, boardScroll, lassoStyle, startLasso, moveLasso, endLasso,
|
|
techHasLinkedJob, techIsHovered,
|
|
}
|
|
}
|