Files
hotline-planner/component split/not good/hlp q gemini code assist split/planner-state-service.js
2026-02-23 14:02:44 +01:00

258 lines
9.2 KiB
JavaScript

import { ref, reactive, computed, onMounted } from 'https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.prod.js';
import { useQuasar } from 'https://cdn.jsdelivr.net/npm/quasar@2.16.0/dist/quasar.umd.prod.js';
import { HUBS, DEPARTMENTS, ROLES, SKILLS, SHIFTS, MOCK_COMMENTS_TEXT, MOCK_NOTES_TEXT } from './data-constants.js';
const state = reactive({
// Data
agents: [],
assignments: {},
comments: {},
notes: {},
holidays: {},
specialDays: {},
lockedCells: new Set(),
loading: false,
// UI Controls
isCompact: false,
weekendsAreWorkingDays: false,
viewScope: 8,
pickerStartDay: 1,
showEodTargets: false,
showAvailability: false,
crosshairActive: true,
// Filtering & Grouping
search: "",
activeDept: "All",
activeHub: "All",
filterRoles: [],
filterSkills: [],
filterByAvailability: false,
filterDate: null, // Initialized in setup
filterShiftTypes: [],
groupingSelection: ['hub', 'dept'],
// Interaction State
startDate: null, // Initialized in setup
selectedAgent: null,
selectedDate: null,
pendingShift: null,
highlightedRowId: null,
highlightedDateStr: null,
hoveredDateStr: null,
});
const formatDateForId = (date) => {
if (!date) return '';
const d = new Date(date);
const month = '' + (d.getMonth() + 1);
const day = '' + d.getDate();
const year = d.getFullYear();
return [year, month.padStart(2, '0'), day.padStart(2, '0')].join('-');
};
const getStartOfWeek = (date) => {
const d = new Date(date);
const day = d.getDay();
const diff = (day < state.pickerStartDay ? 7 : 0) + day - state.pickerStartDay;
d.setDate(d.getDate() - diff);
d.setHours(0, 0, 0, 0);
return d;
};
// Initialize date-dependent state
state.filterDate = formatDateForId(new Date());
state.startDate = getStartOfWeek(new Date());
const generateMockAgents = (count) => {
return Array.from({ length: count }, (_, i) => {
const hub = HUBS[Math.floor(Math.random() * HUBS.length)];
return {
id: i + 1,
name: `Agent ${i + 1}`,
dept: DEPARTMENTS[i % DEPARTMENTS.length],
role: ROLES[i % ROLES.length],
hub: hub.id,
hubName: hub.name,
skills: [SKILLS[i % SKILLS.length], SKILLS[(i + 2) % SKILLS.length]],
avatar: `https://api.dicebear.com/7.x/avataaars/svg?seed=Agent${i}`
};
});
};
const methods = {
loadDataFromDatabase: ($q) => {
state.loading = true;
setTimeout(() => {
const fetchedAgents = generateMockAgents(800);
state.agents = fetchedAgents;
const mockStartDate = new Date(state.startDate);
fetchedAgents.forEach(agent => {
state.assignments[agent.id] = {};
state.comments[agent.id] = {};
state.notes[agent.id] = {};
for (let i = 0; i < 60; i++) {
const d = new Date(mockStartDate);
d.setDate(d.getDate() + i);
const dStr = formatDateForId(d);
if ((agent.id + i) % 7 === 0) state.assignments[agent.id][dStr] = SHIFTS[0].id;
else if ((agent.id + i) % 5 === 0) state.assignments[agent.id][dStr] = SHIFTS[1].id;
if ((agent.id + i) % 20 === 0) state.comments[agent.id][dStr] = MOCK_COMMENTS_TEXT[(agent.id + i) % MOCK_COMMENTS_TEXT.length];
if ((agent.id + i) % 25 === 0) state.notes[agent.id][dStr] = MOCK_NOTES_TEXT[(agent.id + i) % MOCK_NOTES_TEXT.length];
}
});
Object.keys(state.holidays).forEach(key => delete state.holidays[key]);
Object.keys(state.specialDays).forEach(key => delete state.specialDays[key]);
const today = new Date();
const tomorrow = new Date(today);
tomorrow.setDate(today.getDate() + 1);
state.holidays[formatDateForId(tomorrow)] = 'Regional Holiday';
const eventDay = new Date(today);
eventDay.setDate(today.getDate() + 5);
state.specialDays[formatDateForId(eventDay)] = 'Quarterly Planning';
state.loading = false;
if ($q) $q.notify({ message: `Data Loaded: ${fetchedAgents.length} Agents`, color: 'positive', position: 'top', timeout: 1000 });
}, 2000);
},
simulateWssLock: ($q) => {
if (state.agents.length === 0) return;
const randomAgent = state.agents[Math.floor(Math.random() * state.agents.length)];
let targetDate = new Date();
const day = targetDate.getDay();
if (day === 0) targetDate.setDate(targetDate.getDate() + 1);
else if (day === 6) targetDate.setDate(targetDate.getDate() + 2);
const dateStr = formatDateForId(targetDate);
const key = `${randomAgent.id}:${dateStr}`;
state.lockedCells.add(key);
if ($q) $q.notify({ message: `WSS: Cell Locked for ${randomAgent.name} on ${dateStr}`, color: 'negative', position: 'top', icon: 'lock' });
},
isCellLocked: (agentId, date) => state.lockedCells.has(`${agentId}:${formatDateForId(date)}`),
getAssignment: (agentId, date) => {
const dateStr = formatDateForId(date);
const assignmentId = state.assignments[agentId]?.[dateStr];
return assignmentId ? SHIFTS.find(s => s.id === assignmentId) : null;
},
hasComment: (agentId, date) => !!state.comments[agentId]?.[formatDateForId(date)],
getCommentText: (agentId, date) => state.comments[agentId]?.[formatDateForId(date)] || '',
hasNote: (agentId, date) => !!state.notes[agentId]?.[formatDateForId(date)],
getNoteText: (agentId, date) => state.notes[agentId]?.[formatDateForId(date)] || '',
getHoliday: (date) => state.holidays[formatDateForId(date)] || null,
getSpecialDay: (date) => state.specialDays[formatDateForId(date)] || null,
isWeekend: (date) => {
const day = date.getDay();
return day === 0 || day === 6;
},
openAssignment: (agent, date) => {
if (methods.isCellLocked(agent.id, date) || (!state.weekendsAreWorkingDays && methods.isWeekend(date))) return;
state.selectedAgent = agent;
state.selectedDate = date;
state.pendingShift = null;
},
openProfile: (agent) => {
state.selectedAgent = { ...agent };
state.selectedDate = null;
},
saveAssignment: () => {
if (!state.selectedAgent || !state.selectedDate) return;
const { id } = state.selectedAgent;
const dateStr = formatDateForId(state.selectedDate);
if (!state.assignments[id]) state.assignments[id] = {};
state.assignments[id][dateStr] = state.pendingShift ? state.pendingShift.id : null;
state.pendingShift = null;
state.selectedAgent = null;
state.selectedDate = null;
},
clearFilters: () => {
state.search = "";
state.activeDept = "All";
state.activeHub = "All";
state.filterRoles = [];
state.filterSkills = [];
state.filterByAvailability = false;
state.filterShiftTypes = [];
state.filterDate = formatDateForId(new Date());
},
resetToToday: () => {
const today = new Date();
state.startDate = getStartOfWeek(today);
},
applyDateSelection: (proxyDate) => {
if (!proxyDate) return;
const target = new Date(proxyDate);
state.startDate = getStartOfWeek(target);
},
toggleRowHighlight: (agentId) => { state.highlightedRowId = state.highlightedRowId === agentId ? null : agentId; },
toggleColHighlight: (date) => {
const str = formatDateForId(date);
state.highlightedDateStr = state.highlightedDateStr === str ? null : str;
},
clearHighlights: () => {
state.highlightedRowId = null;
state.highlightedDateStr = null;
},
formatDateForId,
};
const getters = {
dates: computed(() => {
const res = [];
const now = new Date(state.startDate);
for (let i = 0; i < state.viewScope * 7; i++) {
const d = new Date(now);
d.setDate(now.getDate() + i);
res.push(d);
}
return res;
}),
filteredAgents: computed(() => {
// This logic remains complex and is well-suited for a computed property.
// It depends on many reactive state properties.
return state.agents.filter(a => {
const term = (state.search || "").toLowerCase();
const matchSearch = term === "" || a.name.toLowerCase().includes(term);
const matchDept = state.activeDept === 'All' || a.dept === state.activeDept;
const matchHub = state.activeHub === 'All' || a.hub === state.activeHub;
const matchRoles = state.filterRoles.length === 0 || state.filterRoles.includes(a.role);
const matchSkills = state.filterSkills.length === 0 || a.skills.some(s => state.filterSkills.includes(s));
let matchAvailability = true;
if (state.filterByAvailability && state.filterDate) {
const d = new Date(state.filterDate);
const assignment = methods.getAssignment(a.id, d);
if (!state.weekendsAreWorkingDays && methods.isWeekend(d)) matchAvailability = false;
else if (!assignment) matchAvailability = false;
else if (state.filterShiftTypes.length > 0 && !state.filterShiftTypes.includes(assignment.id)) matchAvailability = false;
}
return matchSearch && matchDept && matchHub && matchRoles && matchSkills && matchAvailability;
});
}),
// Other computed properties can be added here following the same pattern.
};
export function usePlannerState() {
const $q = useQuasar();
onMounted(() => methods.loadDataFromDatabase($q));
return { state, methods, getters };
}