Files
hotline-planner/monolith/hlp quasar initial layout split/js/components/assignment-sidebar.js
2026-02-23 14:02:44 +01:00

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
}
}
};