Added new modules and updated existing logic
This commit is contained in:
@@ -0,0 +1,507 @@
|
||||
import AppHeader from '../app-header/app-header.js';
|
||||
import LeftDrawer from '../left-drawer/left-drawer.js';
|
||||
import FilterDrawer from '../filter-drawer/filter-drawer.js';
|
||||
import DetailDrawer from '../detail-drawer/detail-drawer.js';
|
||||
import PlannerPage from '../planner-page/planner-page.js';
|
||||
import {
|
||||
HUBS,
|
||||
DEPARTMENTS,
|
||||
ROLES,
|
||||
SKILLS,
|
||||
SHIFTS,
|
||||
agents,
|
||||
loading,
|
||||
lockedCells,
|
||||
assignments,
|
||||
comments,
|
||||
notes,
|
||||
holidays,
|
||||
specialDays,
|
||||
loadDataFromDatabase,
|
||||
simulateWssLock,
|
||||
onlineUsers,
|
||||
startOnlineUsersSimulation
|
||||
} from '../../services/socket-service.js';
|
||||
|
||||
const { ref, computed, nextTick, onMounted, onUnmounted, provide } = Vue;
|
||||
const { useQuasar } = Quasar;
|
||||
|
||||
export default {
|
||||
name: 'AppShell',
|
||||
components: {
|
||||
AppHeader,
|
||||
LeftDrawer,
|
||||
FilterDrawer,
|
||||
DetailDrawer,
|
||||
PlannerPage
|
||||
},
|
||||
setup() {
|
||||
const $q = useQuasar();
|
||||
const leftDrawer = ref(false);
|
||||
const filterDrawer = ref(false);
|
||||
const rightDrawer = ref(false);
|
||||
const editMode = ref('assignment');
|
||||
const viewport = ref(null);
|
||||
|
||||
const isCompact = ref(false);
|
||||
const weekendsAreWorkingDays = ref(false);
|
||||
const viewScope = ref(8);
|
||||
const pickerStartDay = ref(1);
|
||||
const search = ref('');
|
||||
const activeDept = ref('All');
|
||||
const activeHub = ref('All');
|
||||
const filterRoles = ref([]);
|
||||
const filterSkills = ref([]);
|
||||
const groupingSelection = ref(['hub', 'dept']);
|
||||
const groupingOptions = [
|
||||
{ label: 'Hub > Division (Default)', value: ['hub', 'dept'] },
|
||||
{ label: 'Division > Role', value: ['dept', 'role'] },
|
||||
{ label: 'Hub > Role', value: ['hub', 'role'] },
|
||||
{ label: 'Division (Flat)', value: ['dept'] },
|
||||
{ label: 'Role (Flat)', value: ['role'] },
|
||||
{ label: 'No Grouping (Flat)', value: [] }
|
||||
];
|
||||
|
||||
const hubOptions = [
|
||||
{ label: 'All Hubs', value: 'All' },
|
||||
...HUBS.map(h => ({ label: h.name, value: h.id }))
|
||||
];
|
||||
|
||||
const showEodTargets = ref(false);
|
||||
const showAvailability = ref(false);
|
||||
|
||||
const selectedAgent = ref(null);
|
||||
const selectedDate = ref(null);
|
||||
const pendingShift = ref(null);
|
||||
const dateMenu = ref(false);
|
||||
const proxyDate = ref(null);
|
||||
|
||||
const highlightedRowId = ref(null);
|
||||
const highlightedDateStr = ref(null);
|
||||
const crosshairActive = ref(true);
|
||||
const hoveredDateStr = ref(null);
|
||||
|
||||
const filterByAvailability = ref(false);
|
||||
|
||||
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 parseDateId = (dateStr) => {
|
||||
if (!dateStr || typeof dateStr !== 'string') return null;
|
||||
const normalized = dateStr.replace(/\//g, '-');
|
||||
const [y, m, d] = normalized.split('-').map(Number);
|
||||
if (!y || Number.isNaN(y) || Number.isNaN(m) || Number.isNaN(d)) return null;
|
||||
const parsed = new Date(y, (m || 1) - 1, d || 1);
|
||||
return Number.isNaN(parsed.getTime()) ? null : parsed;
|
||||
};
|
||||
|
||||
const filterDate = ref(formatDateForId(new Date()));
|
||||
const proxyFilterDate = ref('');
|
||||
const filterShiftTypes = ref([]);
|
||||
|
||||
const getStartOfWeek = (date) => {
|
||||
const d = new Date(date);
|
||||
const day = d.getDay();
|
||||
const diff = (day < 1 ? 7 : 0) + day - 1;
|
||||
d.setDate(d.getDate() - diff);
|
||||
d.setHours(0, 0, 0, 0);
|
||||
return d;
|
||||
};
|
||||
|
||||
const startDate = ref(getStartOfWeek(new Date()));
|
||||
|
||||
const isWeekend = (date) => {
|
||||
const day = date.getDay();
|
||||
return day === 0 || day === 6;
|
||||
};
|
||||
|
||||
const dates = computed(() => {
|
||||
const res = [];
|
||||
const now = new Date(startDate.value);
|
||||
for (let i = 0; i < viewScope.value * 7; i++) {
|
||||
const d = new Date(now);
|
||||
d.setDate(now.getDate() + i);
|
||||
res.push(d);
|
||||
}
|
||||
return res;
|
||||
});
|
||||
|
||||
const notify = (payload) => $q.notify(payload);
|
||||
|
||||
const simulateWssLockAction = () => {
|
||||
simulateWssLock(notify);
|
||||
};
|
||||
|
||||
const isCellLocked = (agentId, date) => {
|
||||
const key = `${agentId}:${formatDateForId(date)}`;
|
||||
return lockedCells.value.has(key);
|
||||
};
|
||||
|
||||
const getAssignment = (agentId, date) => {
|
||||
const dateStr = formatDateForId(date);
|
||||
if (assignments[agentId] && assignments[agentId][dateStr] !== undefined) {
|
||||
return SHIFTS.find(s => s.id === assignments[agentId][dateStr]);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const getHoliday = (date) => holidays[formatDateForId(date)] || null;
|
||||
const getSpecialDay = (date) => specialDays[formatDateForId(date)] || null;
|
||||
|
||||
const hasComment = (agentId, date) => {
|
||||
const dStr = formatDateForId(date);
|
||||
return comments[agentId] && comments[agentId][dStr];
|
||||
};
|
||||
const hasNote = (agentId, date) => {
|
||||
const dStr = formatDateForId(date);
|
||||
return notes[agentId] && notes[agentId][dStr];
|
||||
};
|
||||
const getCommentText = (agentId, date) => {
|
||||
const dStr = formatDateForId(date);
|
||||
return comments[agentId] ? comments[agentId][dStr] : '';
|
||||
};
|
||||
const getNoteText = (agentId, date) => {
|
||||
const dStr = formatDateForId(date);
|
||||
return notes[agentId] ? notes[agentId][dStr] : '';
|
||||
};
|
||||
|
||||
const isFilterActive = computed(() =>
|
||||
filterRoles.value.length > 0 ||
|
||||
filterSkills.value.length > 0 ||
|
||||
activeDept.value !== 'All' ||
|
||||
activeHub.value !== 'All' ||
|
||||
(search.value && search.value.length > 0) ||
|
||||
filterByAvailability.value
|
||||
);
|
||||
|
||||
const activeFilterCount = computed(() => {
|
||||
let count = 0;
|
||||
if (search.value && search.value.length > 0) count++;
|
||||
if (activeDept.value !== 'All') count++;
|
||||
if (activeHub.value !== 'All') count++;
|
||||
if (filterRoles.value.length > 0) count++;
|
||||
if (filterSkills.value.length > 0) count++;
|
||||
if (filterByAvailability.value) count++;
|
||||
return count;
|
||||
});
|
||||
|
||||
const filteredAgents = computed(() => {
|
||||
return agents.value.filter(a => {
|
||||
const term = (search.value || '').toLowerCase();
|
||||
const matchSearch = term === '' || a.name.toLowerCase().includes(term);
|
||||
const matchDept = activeDept.value === 'All' || a.dept === activeDept.value;
|
||||
const matchHub = activeHub.value === 'All' || a.hub === activeHub.value;
|
||||
const matchRoles = filterRoles.value.length === 0 || filterRoles.value.includes(a.role);
|
||||
const matchSkills = filterSkills.value.length === 0 || a.skills.some(s => filterSkills.value.includes(s));
|
||||
|
||||
let matchAvailability = true;
|
||||
if (filterByAvailability.value && filterDate.value) {
|
||||
const d = parseDateId(filterDate.value);
|
||||
const assignment = getAssignment(a.id, d);
|
||||
|
||||
if (!weekendsAreWorkingDays.value && isWeekend(d)) {
|
||||
matchAvailability = false;
|
||||
} else if (!assignment) {
|
||||
matchAvailability = false;
|
||||
} else if (
|
||||
filterShiftTypes.value.length > 0 &&
|
||||
!filterShiftTypes.value.includes(assignment.id)
|
||||
) {
|
||||
matchAvailability = false;
|
||||
}
|
||||
}
|
||||
|
||||
return matchSearch && matchDept && matchHub && matchRoles && matchSkills && matchAvailability;
|
||||
});
|
||||
});
|
||||
|
||||
const flattenedList = computed(() => {
|
||||
const result = [];
|
||||
const list = [...filteredAgents.value];
|
||||
const keys = groupingSelection.value;
|
||||
|
||||
const getLabel = (key, value) => {
|
||||
if (key === 'hub') return HUBS.find(h => h.id === value)?.name || value;
|
||||
if (key === 'dept') return value.toUpperCase() + ' DIVISION';
|
||||
if (key === 'role') return value + 's';
|
||||
return value;
|
||||
};
|
||||
|
||||
if (keys.length === 0) {
|
||||
return list.map(a => ({ type: 'agent', data: a, id: a.id }));
|
||||
}
|
||||
|
||||
list.sort((a, b) => {
|
||||
for (const key of keys) {
|
||||
if (a[key] < b[key]) return -1;
|
||||
if (a[key] > b[key]) return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
const currentValues = keys.map(() => null);
|
||||
|
||||
list.forEach(agent => {
|
||||
let changedLevel = -1;
|
||||
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
if (agent[key] !== currentValues[i]) {
|
||||
changedLevel = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (changedLevel !== -1) {
|
||||
for (let i = changedLevel; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
const val = agent[key];
|
||||
currentValues[i] = val;
|
||||
const type = i === 0 ? 'header-l1' : 'header-l2';
|
||||
const path = keys
|
||||
.slice(0, i + 1)
|
||||
.map((k, idx) => `${k}:${currentValues[idx]}`)
|
||||
.join('|');
|
||||
result.push({
|
||||
type: type,
|
||||
label: getLabel(key, val),
|
||||
id: `hdr-${path}`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
result.push({ type: 'agent', data: agent, id: agent.id });
|
||||
});
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
const currentAssignmentLabel = computed(() => {
|
||||
if (pendingShift.value) return pendingShift.value.label;
|
||||
if (!selectedAgent.value || !selectedDate.value) return null;
|
||||
const a = getAssignment(selectedAgent.value.id, selectedDate.value);
|
||||
return a ? a.label : null;
|
||||
});
|
||||
|
||||
const clearFilters = () => {
|
||||
search.value = '';
|
||||
activeDept.value = 'All';
|
||||
activeHub.value = 'All';
|
||||
filterRoles.value = [];
|
||||
filterSkills.value = [];
|
||||
filterByAvailability.value = false;
|
||||
filterShiftTypes.value = [];
|
||||
filterDate.value = formatDateForId(new Date());
|
||||
};
|
||||
|
||||
const applySavedFilter = (key) => {
|
||||
clearFilters();
|
||||
if (key === 'high_potential') {
|
||||
filterSkills.value = ['VIP Concierge', 'Technical Training'];
|
||||
} else if (key === 'remote') {
|
||||
filterRoles.value = ['Specialist'];
|
||||
}
|
||||
};
|
||||
|
||||
const syncProxyDate = () => {
|
||||
proxyDate.value = formatDateForId(selectedDate.value || new Date());
|
||||
};
|
||||
|
||||
const applyDateSelection = () => {
|
||||
if (!proxyDate.value) return;
|
||||
const target = parseDateId(proxyDate.value);
|
||||
if (!target) return;
|
||||
startDate.value = getStartOfWeek(target);
|
||||
if (rightDrawer.value && editMode.value === 'assignment' && selectedAgent.value) {
|
||||
selectedDate.value = target;
|
||||
pendingShift.value = null;
|
||||
}
|
||||
dateMenu.value = false;
|
||||
nextTick(() => {
|
||||
if (viewport.value) viewport.value.scrollLeft = 0;
|
||||
});
|
||||
};
|
||||
|
||||
const resetToToday = () => {
|
||||
const today = new Date();
|
||||
startDate.value = getStartOfWeek(today);
|
||||
if (rightDrawer.value && editMode.value === 'assignment') {
|
||||
selectedDate.value = today;
|
||||
pendingShift.value = null;
|
||||
}
|
||||
if (viewport.value) viewport.value.scrollLeft = 0;
|
||||
leftDrawer.value = false;
|
||||
};
|
||||
|
||||
const updateFilterDateProxy = () => {
|
||||
proxyFilterDate.value = filterDate.value;
|
||||
};
|
||||
|
||||
const applyFilterDate = () => {
|
||||
filterDate.value = proxyFilterDate.value;
|
||||
};
|
||||
|
||||
const openAssignment = (agent, date) => {
|
||||
if (isCellLocked(agent.id, date) || (!weekendsAreWorkingDays.value && isWeekend(date))) return;
|
||||
editMode.value = 'assignment';
|
||||
selectedAgent.value = agent;
|
||||
selectedDate.value = date;
|
||||
pendingShift.value = null;
|
||||
rightDrawer.value = true;
|
||||
};
|
||||
|
||||
const openProfile = (agent) => {
|
||||
editMode.value = 'profile';
|
||||
selectedAgent.value = { ...agent };
|
||||
selectedDate.value = null;
|
||||
rightDrawer.value = true;
|
||||
};
|
||||
|
||||
const setPendingShift = (shift) => {
|
||||
pendingShift.value = shift;
|
||||
};
|
||||
|
||||
const saveAssignment = () => {
|
||||
if (!selectedAgent.value || !selectedDate.value) return;
|
||||
const agentId = selectedAgent.value.id;
|
||||
const dateStr = formatDateForId(selectedDate.value);
|
||||
if (!assignments[agentId]) assignments[agentId] = {};
|
||||
assignments[agentId][dateStr] = pendingShift.value ? pendingShift.value.id : null;
|
||||
pendingShift.value = null;
|
||||
rightDrawer.value = false;
|
||||
};
|
||||
|
||||
const toggleRowHighlight = (agentId) => {
|
||||
highlightedRowId.value = highlightedRowId.value === agentId ? null : agentId;
|
||||
};
|
||||
|
||||
const toggleColHighlight = (date) => {
|
||||
const str = formatDateForId(date);
|
||||
highlightedDateStr.value = highlightedDateStr.value === str ? null : str;
|
||||
};
|
||||
|
||||
const clearHighlights = () => {
|
||||
highlightedRowId.value = null;
|
||||
highlightedDateStr.value = null;
|
||||
};
|
||||
|
||||
const hasHighlights = computed(() => Boolean(highlightedRowId.value || highlightedDateStr.value));
|
||||
|
||||
const toggleLeftDrawer = () => {
|
||||
leftDrawer.value = !leftDrawer.value;
|
||||
};
|
||||
|
||||
let stopOnlineUsersSimulation = null;
|
||||
|
||||
onMounted(() => {
|
||||
loadDataFromDatabase(startDate.value, notify);
|
||||
stopOnlineUsersSimulation = startOnlineUsersSimulation(notify);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (stopOnlineUsersSimulation) stopOnlineUsersSimulation();
|
||||
});
|
||||
|
||||
const appState = {
|
||||
leftDrawer,
|
||||
filterDrawer,
|
||||
rightDrawer,
|
||||
editMode,
|
||||
viewport,
|
||||
isCompact,
|
||||
weekendsAreWorkingDays,
|
||||
viewScope,
|
||||
pickerStartDay,
|
||||
search,
|
||||
activeDept,
|
||||
activeHub,
|
||||
filterRoles,
|
||||
filterSkills,
|
||||
onlineUsers,
|
||||
agents,
|
||||
loading,
|
||||
assignments,
|
||||
comments,
|
||||
notes,
|
||||
holidays,
|
||||
specialDays,
|
||||
lockedCells,
|
||||
shifts: SHIFTS,
|
||||
depts: DEPARTMENTS,
|
||||
roles: ROLES,
|
||||
allSkills: SKILLS,
|
||||
groupingSelection,
|
||||
groupingOptions,
|
||||
hubOptions,
|
||||
showEodTargets,
|
||||
showAvailability,
|
||||
selectedAgent,
|
||||
selectedDate,
|
||||
pendingShift,
|
||||
dateMenu,
|
||||
proxyDate,
|
||||
highlightedRowId,
|
||||
highlightedDateStr,
|
||||
crosshairActive,
|
||||
hoveredDateStr,
|
||||
filterByAvailability,
|
||||
filterDate,
|
||||
proxyFilterDate,
|
||||
filterShiftTypes,
|
||||
dates,
|
||||
formatDateForId,
|
||||
isWeekend,
|
||||
isCellLocked,
|
||||
getAssignment,
|
||||
getHoliday,
|
||||
getSpecialDay,
|
||||
hasComment,
|
||||
hasNote,
|
||||
getCommentText,
|
||||
getNoteText,
|
||||
isFilterActive,
|
||||
activeFilterCount,
|
||||
filteredAgents,
|
||||
flattenedList,
|
||||
currentAssignmentLabel,
|
||||
clearFilters,
|
||||
applySavedFilter,
|
||||
syncProxyDate,
|
||||
applyDateSelection,
|
||||
resetToToday,
|
||||
updateFilterDateProxy,
|
||||
applyFilterDate,
|
||||
openAssignment,
|
||||
openProfile,
|
||||
setPendingShift,
|
||||
saveAssignment,
|
||||
toggleRowHighlight,
|
||||
toggleColHighlight,
|
||||
clearHighlights,
|
||||
hasHighlights,
|
||||
simulateWssLockAction,
|
||||
toggleLeftDrawer
|
||||
};
|
||||
|
||||
provide('appState', appState);
|
||||
|
||||
return appState;
|
||||
},
|
||||
template: `
|
||||
<div class="app-shell-root">
|
||||
<q-layout view="hHh Lpr fFf">
|
||||
<app-header></app-header>
|
||||
<left-drawer></left-drawer>
|
||||
<filter-drawer></filter-drawer>
|
||||
<detail-drawer></detail-drawer>
|
||||
<planner-page></planner-page>
|
||||
</q-layout>
|
||||
</div>
|
||||
`
|
||||
};
|
||||
Reference in New Issue
Block a user