200 lines
9.2 KiB
JavaScript
200 lines
9.2 KiB
JavaScript
const AssignmentSidebar = {
|
|
template: `
|
|
<q-drawer :model-value="isOpen" @update:model-value="close" side="right" bordered :width="360" overlay elevated>
|
|
<div class="column full-height bg-white">
|
|
<q-toolbar class="bg-grey-2">
|
|
<q-toolbar-title class="text-subtitle2 text-weight-bold">
|
|
{{ title }}
|
|
</q-toolbar-title>
|
|
<q-btn flat round dense icon="close" @click="close"></q-btn>
|
|
</q-toolbar>
|
|
|
|
<q-scroll-area class="col q-pa-md">
|
|
<!-- Header: Agent Info -->
|
|
<div v-if="agent" class="row items-center q-mb-md q-pa-sm bg-indigo-1 rounded-borders border-indigo-2 border">
|
|
<q-avatar size="40px">
|
|
<img :src="agent.avatar">
|
|
</q-avatar>
|
|
<div class="q-ml-sm">
|
|
<div class="text-weight-bold text-indigo-10">{{ agent.name }}</div>
|
|
<div class="text-caption text-indigo-7">{{ agent.dept }} Division • {{ agent.role }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Assignment Mode: Date Header -->
|
|
<div v-if="isAssignment && date" class="q-mb-lg q-pa-md bg-white border rounded-borders shadow-1">
|
|
<div class="row items-center justify-between">
|
|
<div class="column">
|
|
<div class="text-overline text-grey-6 leading-none">Selected Date</div>
|
|
<div class="text-h6 text-weight-bolder text-slate-8">
|
|
{{ dateFormatted }}
|
|
</div>
|
|
<div class="text-subtitle2 text-grey-7">
|
|
{{ dateFull }}
|
|
</div>
|
|
</div>
|
|
<q-icon name="event" size="md" color="indigo-3"></q-icon>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Mode: Assignment -->
|
|
<template v-if="isAssignment">
|
|
<div class="text-overline q-mb-sm text-grey-6 text-weight-bold">Shift & Activity Selection</div>
|
|
|
|
<div class="q-mb-md">
|
|
<q-select
|
|
filled
|
|
v-model="selectedShifts"
|
|
multiple
|
|
:options="shiftOptions"
|
|
label="Assign Shifts / Groups"
|
|
option-value="id"
|
|
option-label="label"
|
|
emit-value
|
|
map-options
|
|
use-chips
|
|
stack-label
|
|
>
|
|
<template v-slot:option="scope">
|
|
<q-item v-bind="scope.itemProps">
|
|
<q-item-section avatar>
|
|
<q-icon name="circle" :color="scope.opt.color.split('-')[0]" size="xs" />
|
|
</q-item-section>
|
|
<q-item-section>
|
|
<q-item-label>{{ scope.opt.label }}</q-item-label>
|
|
<q-item-label caption>{{ scope.opt.desc }}</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
</template>
|
|
</q-select>
|
|
</div>
|
|
|
|
<!-- Validation / Conflict Warning -->
|
|
<div v-if="hasConflict" class="q-mb-md q-pa-sm bg-red-1 text-red-9 rounded-borders border-red-2 border flex items-center">
|
|
<q-icon name="warning" class="q-mr-sm"></q-icon>
|
|
<div class="text-caption text-weight-bold">Conflict: {{ conflictMessage }}</div>
|
|
</div>
|
|
|
|
<div class="text-overline q-mb-sm text-grey-6 text-weight-bold">Custom Time Override</div>
|
|
<div class="row q-col-gutter-sm q-mb-md">
|
|
<div class="col-6"><q-input filled dense label="Start" mask="time" v-model="customStart"></q-input></div>
|
|
<div class="col-6"><q-input filled dense label="End" mask="time" v-model="customEnd"></q-input></div>
|
|
</div>
|
|
</template>
|
|
|
|
<!-- Mode: Profile -->
|
|
<template v-else-if="isProfile">
|
|
<div class="text-overline q-mb-sm text-grey-6 text-weight-bold">Basic Information</div>
|
|
<div class="column q-gutter-y-md q-mb-lg">
|
|
<q-input filled dense v-model="agent.name" label="Full Name"></q-input>
|
|
<q-select filled dense v-model="agent.role" :options="roles" label="Primary Role"></q-select>
|
|
<q-select filled dense v-model="agent.dept" :options="depts" label="Division / Department"></q-select>
|
|
</div>
|
|
|
|
<div class="text-overline q-mb-sm text-grey-6 text-weight-bold">Skills & Expertise</div>
|
|
<div class="column q-gutter-y-sm">
|
|
<q-select
|
|
filled
|
|
dense
|
|
v-model="agent.skills"
|
|
multiple
|
|
use-chips
|
|
:options="skills"
|
|
label="Select Skills"
|
|
></q-select>
|
|
</div>
|
|
</template>
|
|
</q-scroll-area>
|
|
|
|
<div class="q-pa-md border-t row q-gutter-sm">
|
|
<q-btn outline label="Discard" class="col" @click="close"></q-btn>
|
|
<q-btn unelevated color="indigo-9" label="Save Changes" class="col" @click="save"></q-btn>
|
|
</div>
|
|
</div>
|
|
</q-drawer>
|
|
`,
|
|
setup() {
|
|
const { computed, ref, watch } = Vue;
|
|
|
|
const isOpen = computed(() => store.viewSettings.rightDrawerOpen);
|
|
const agent = computed(() => store.selection.agent);
|
|
const date = computed(() => store.selection.date);
|
|
const mode = computed(() => store.selection.mode);
|
|
|
|
const isAssignment = computed(() => mode.value === 'assignment');
|
|
const isProfile = computed(() => mode.value === 'profile');
|
|
|
|
// Local state for assignment
|
|
const selectedShifts = ref([]);
|
|
const customStart = ref('');
|
|
const customEnd = ref('');
|
|
|
|
// Watch for selection change to load data
|
|
watch([agent, date, isOpen], () => {
|
|
if (isOpen.value && isAssignment.value && agent.value && date.value) {
|
|
// Load existing shifts from store
|
|
selectedShifts.value = store.getAgentShifts(agent.value.id, date.value);
|
|
customStart.value = ''; // Reset custom times for now
|
|
customEnd.value = '';
|
|
}
|
|
});
|
|
|
|
// Basic Conflict Check
|
|
const hasConflict = computed(() => {
|
|
return !!ValidationUtils.hasConflict(selectedShifts.value);
|
|
});
|
|
|
|
const conflictMessage = computed(() => {
|
|
return ValidationUtils.getConflictMessage(selectedShifts.value);
|
|
});
|
|
|
|
const close = () => {
|
|
store.viewSettings.rightDrawerOpen = false;
|
|
};
|
|
|
|
const save = () => {
|
|
if (isAssignment.value && agent.value && date.value) {
|
|
store.assignShifts(agent.value.id, date.value, selectedShifts.value, {
|
|
start: customStart.value,
|
|
end: customEnd.value
|
|
});
|
|
}
|
|
// For profile, it's already bound to agent object (which is ref from store)
|
|
close();
|
|
};
|
|
|
|
const dateFormatted = computed(() => {
|
|
if (!date.value) return '';
|
|
return date.value.toLocaleDateString('en-US', { weekday: 'long' });
|
|
});
|
|
|
|
const dateFull = computed(() => {
|
|
if (!date.value) return '';
|
|
return date.value.toLocaleDateString('en-US', { day: 'numeric', month: 'long', year: 'numeric' });
|
|
});
|
|
|
|
// Extended Shift Options
|
|
const shiftOptions = [
|
|
{ id: 'm', label: 'Morning Shift', desc: '08:00 - 14:00', color: 'green-7' },
|
|
{ id: 'a', label: 'Afternoon Shift', desc: '14:00 - 22:00', color: 'blue-7' },
|
|
{ id: 'e', label: 'EOD Support', desc: '18:00 - 20:00', color: 'pink-7' },
|
|
{ id: 't', label: 'Training', desc: 'All Day', color: 'orange-7' },
|
|
{ id: 'v', label: 'Vacation', desc: 'Approved', color: 'purple-7' }
|
|
];
|
|
|
|
return {
|
|
isOpen, close, save,
|
|
agent, date,
|
|
isAssignment, isProfile,
|
|
title: computed(() => isAssignment.value ? 'Assignment Entry' : 'Agent Profile'),
|
|
dateFormatted, dateFull,
|
|
shiftOptions,
|
|
selectedShifts, customStart, customEnd,
|
|
hasConflict, conflictMessage,
|
|
roles: store.consts.ROLES,
|
|
depts: store.consts.DEPARTMENTS,
|
|
skills: store.consts.SKILLS
|
|
}
|
|
}
|
|
};
|