258 lines
9.2 KiB
JavaScript
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 };
|
|
} |