Added new modules and updated existing logic

This commit is contained in:
Dieter Neumann
2026-02-24 13:32:01 +01:00
parent 2a4b4ed5fe
commit ad734273ce
694 changed files with 27935 additions and 610 deletions

View File

@@ -0,0 +1,66 @@
/* grid-cell — individual shift cell */
.grid-cell-root {
width: var(--cell-width);
min-width: var(--cell-width);
height: 58px;
border-right: 1px solid #f1f5f9;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
transition: background 0.1s ease;
}
.grid-cell-root:hover:not(.cursor-not-allowed) {
background-color: #f8fafc;
}
.grid-cell-compact {
height: 42px;
}
.grid-cell-shift-badge {
font-size: 9px;
font-weight: 800;
padding: 2px 4px;
border-radius: 3px;
width: 90%;
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-bottom: 2px;
border: 1px solid rgba(0,0,0,0.06);
}
.grid-cell-locked-overlay {
position: absolute;
inset: 0;
background: rgba(241, 245, 249, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 5;
}
.grid-cell-tooltip {
padding: 10px 12px;
max-width: 220px;
border: 1px solid #e2e8f0;
}
/* Weekend + highlight helpers (applied via dynamic class) */
.grid-cell-bg-weekend { background-color: #f1f5f9 !important; }
.grid-cell-bg-reading-mode {
background-color: #fff9c4 !important;
}
.grid-cell-bg-reading-mode-intersection {
background-color: #fff176 !important;
}
.grid-cell-col-hovered {
background-color: var(--highlight-bg) !important;
z-index: 1;
}

View File

@@ -0,0 +1,112 @@
/**
* grid-cell.js
* =============
* A single shift cell inside an agent row.
* Props: agentId, date
*/
import {
formatDateForId,
isWeekend,
weekendsAreWorkingDays,
isCompact,
crosshairActive,
hoveredDateStr,
highlightedRowId,
highlightedDateStr
} from '../../services/planner-state.js';
import {
getAssignment,
hasComment,
hasNote,
getCommentText,
getNoteText
} from '../../services/data-service.js';
import { isCellLocked } from '../../services/socket-service.js';
export default {
name: 'GridCell',
props: {
agentId: { type: Number, required: true },
date: { type: Date, required: true }
},
emits: ['open-assignment'],
setup(props, { emit }) {
const cellClass = Vue.computed(() => {
const agentId = props.agentId;
const date = props.date;
const isRow = highlightedRowId.value === agentId;
const isCol = highlightedDateStr.value === formatDateForId(date);
const isHoverCol = crosshairActive.value && hoveredDateStr.value === formatDateForId(date);
const isWknd = isWeekend(date) && !weekendsAreWorkingDays.value;
const locked = isCellLocked(agentId, date);
const classes = ['grid-cell-root'];
if (isCompact.value) classes.push('grid-cell-compact');
if (isWknd) classes.push('grid-cell-bg-weekend');
if (isWknd && !weekendsAreWorkingDays.value) classes.push('cursor-not-allowed');
else if (locked) classes.push('cursor-not-allowed');
else classes.push('cursor-pointer');
if (isRow && isCol) classes.push('grid-cell-bg-reading-mode-intersection');
else if (isRow || isCol) classes.push('grid-cell-bg-reading-mode');
if (isHoverCol) classes.push('grid-cell-col-hovered');
return classes.join(' ');
});
const assignment = Vue.computed(() => getAssignment(props.agentId, props.date));
const locked = Vue.computed(() => isCellLocked(props.agentId, props.date));
const showShift = Vue.computed(() => weekendsAreWorkingDays.value || !isWeekend(props.date));
const comment = Vue.computed(() => hasComment(props.agentId, props.date));
const note = Vue.computed(() => hasNote(props.agentId, props.date));
const commentTxt = Vue.computed(() => getCommentText(props.agentId, props.date));
const noteTxt = Vue.computed(() => getNoteText(props.agentId, props.date));
const onEnter = () => { hoveredDateStr.value = formatDateForId(props.date); };
const onLeave = () => { hoveredDateStr.value = null; };
const onClick = () => { emit('open-assignment', props.date); };
return {
cellClass, assignment, locked, showShift,
comment, note, commentTxt, noteTxt,
onEnter, onLeave, onClick
};
},
template: `
<div :class="cellClass"
@mouseenter="onEnter"
@mouseleave="onLeave"
@click="onClick">
<template v-if="showShift">
<div v-if="assignment"
class="grid-cell-shift-badge"
:class="assignment.badgeClass">{{ assignment.label }}</div>
</template>
<div v-if="locked" class="grid-cell-locked-overlay">
<q-icon name="lock" color="blue-grey-3" size="14px">
<q-tooltip class="bg-grey-9 text-white shadow-4 q-pa-sm">This cell is currently being edited by another user.</q-tooltip>
</q-icon>
</div>
<div class="absolute-bottom-right q-pa-xs row no-wrap" style="gap: 2px">
<q-icon v-if="comment" name="chat_bubble" size="8px" color="blue-grey-3" class="cursor-help opacity-60">
<q-tooltip class="bg-white text-grey-9 border grid-cell-tooltip" anchor="top middle" self="bottom middle">
<div class="text-weight-bold text-caption text-grey-8 q-mb-xs">User Comment</div>
<div class="text-caption text-grey-7">{{ commentTxt }}</div>
</q-tooltip>
</q-icon>
<q-icon v-if="note" name="info" size="8px" color="orange-4" class="cursor-help opacity-70">
<q-tooltip class="bg-white text-grey-9 border grid-cell-tooltip" anchor="top middle" self="bottom middle">
<div class="text-weight-bold text-caption text-grey-8 q-mb-xs">Technical Note</div>
<div class="text-caption text-grey-7">{{ noteTxt }}</div>
</q-tooltip>
</q-icon>
</div>
</div>
`
};