mirror of
https://bitbucket.org/studiotem/jandl-taskmanager-app.git
synced 2026-10-21 14:29:34 +00:00
feat(timeline): improved ux of time block dragging / creating on timeline
This commit is contained in:
1 parent
cdd7d14d4c
commit
46f578feb9
3 files changed
+163
-47
No files matched your search
+4
@@ -42,6 +42,8 @@ const getHourPosition = (hour: number): number => {
|
|||||||
position: relative
|
position: relative
|
||||||
height: 30px
|
height: 30px
|
||||||
width: calc(24 * 60px)
|
width: calc(24 * 60px)
|
||||||
|
user-select: none
|
||||||
|
cursor: inherit
|
||||||
|
|
||||||
@media (max-width: 768px)
|
@media (max-width: 768px)
|
||||||
width: calc(24 * 50px)
|
width: calc(24 * 50px)
|
||||||
@@ -51,6 +53,7 @@ const getHourPosition = (hour: number): number => {
|
|||||||
top: 0
|
top: 0
|
||||||
height: 30px
|
height: 30px
|
||||||
border-left: 1px solid #e0e0e0
|
border-left: 1px solid #e0e0e0
|
||||||
|
cursor: inherit
|
||||||
|
|
||||||
.hour-label
|
.hour-label
|
||||||
position: absolute
|
position: absolute
|
||||||
@@ -59,5 +62,6 @@ const getHourPosition = (hour: number): number => {
|
|||||||
font-size: 11px
|
font-size: 11px
|
||||||
color: #666
|
color: #666
|
||||||
white-space: nowrap
|
white-space: nowrap
|
||||||
|
cursor: inherit
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
+158
-47
@@ -4,7 +4,13 @@
|
|||||||
<span>Načítavam...</span>
|
<span>Načítavam...</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-else ref="timelineContainer" class="timeline__content">
|
<div v-else ref="timelineContainer" class="timeline__content">
|
||||||
<div ref="timelineScroll" class="timeline-scroll">
|
<div
|
||||||
|
ref="timelineScroll"
|
||||||
|
class="timeline-scroll"
|
||||||
|
@mousemove="handleMouseMove"
|
||||||
|
@mouseleave="handleMouseLeave"
|
||||||
|
@mousedown="handleTimelineMouseDown"
|
||||||
|
>
|
||||||
<a-employee-timeblock-timeline-header
|
<a-employee-timeblock-timeline-header
|
||||||
:start-hour="startHour"
|
:start-hour="startHour"
|
||||||
:end-hour="endHour"
|
:end-hour="endHour"
|
||||||
@@ -14,9 +20,6 @@
|
|||||||
<div
|
<div
|
||||||
ref="timelineBar"
|
ref="timelineBar"
|
||||||
class="timeline-bar"
|
class="timeline-bar"
|
||||||
@mousemove="handleMouseMove"
|
|
||||||
@mouseleave="handleMouseLeave"
|
|
||||||
@mousedown="handleMouseDown"
|
|
||||||
>
|
>
|
||||||
<!-- Non-working hours - before work -->
|
<!-- Non-working hours - before work -->
|
||||||
<div
|
<div
|
||||||
@@ -93,7 +96,6 @@
|
|||||||
left: `${getTimePosition(previewBlock.start)}px`,
|
left: `${getTimePosition(previewBlock.start)}px`,
|
||||||
width: `${getTimeWidth(previewBlock.start, previewBlock.end)}px`,
|
width: `${getTimeWidth(previewBlock.start, previewBlock.end)}px`,
|
||||||
}"
|
}"
|
||||||
@mousedown.stop="handlePreviewMouseDown"
|
|
||||||
>
|
>
|
||||||
<div class="preview-time-label">
|
<div class="preview-time-label">
|
||||||
<div
|
<div
|
||||||
@@ -183,6 +185,15 @@
|
|||||||
width: `${getTimeWidth(segment.start, segment.end)}px`,
|
width: `${getTimeWidth(segment.start, segment.end)}px`,
|
||||||
}"
|
}"
|
||||||
></div>
|
></div>
|
||||||
|
|
||||||
|
<!-- Cursor line (snapped to 15-min intervals) -->
|
||||||
|
<div
|
||||||
|
v-if="showCursor && cursorPosition"
|
||||||
|
class="timeline-cursor"
|
||||||
|
:style="{
|
||||||
|
left: `${getTimePosition(cursorPosition)}px`,
|
||||||
|
}"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -211,6 +222,7 @@ interface CollisionSegment {
|
|||||||
interface Props {
|
interface Props {
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
employee: EmployeeFullData;
|
employee: EmployeeFullData;
|
||||||
|
date: Date | string | null;
|
||||||
scheduledBlocks: TimelineTask[];
|
scheduledBlocks: TimelineTask[];
|
||||||
otherTimeblocks?: TimeblockData[];
|
otherTimeblocks?: TimeblockData[];
|
||||||
currentTimeblockRange: {
|
currentTimeblockRange: {
|
||||||
@@ -260,9 +272,14 @@ const dragState = ref({
|
|||||||
});
|
});
|
||||||
const resizedCurrentBlock = ref<{ start: Date; end: Date } | null>(null);
|
const resizedCurrentBlock = ref<{ start: Date; end: Date } | null>(null);
|
||||||
|
|
||||||
|
// Cursor line state
|
||||||
|
const showCursor = ref(false);
|
||||||
|
const cursorPosition = ref<Date | null>(null);
|
||||||
|
|
||||||
// Check if the current date is a weekend (Saturday or Sunday)
|
// Check if the current date is a weekend (Saturday or Sunday)
|
||||||
const isWeekend = computed(() => {
|
const isWeekend = computed(() => {
|
||||||
const date = props.currentTimeblockRange.start;
|
// Use the explicit date prop first, fallback to currentTimeblockRange.start
|
||||||
|
const date = props.date || props.currentTimeblockRange.start;
|
||||||
if (!date) return false;
|
if (!date) return false;
|
||||||
|
|
||||||
const dayOfWeek = moment(date).day(); // 0 = Sunday, 6 = Saturday
|
const dayOfWeek = moment(date).day(); // 0 = Sunday, 6 = Saturday
|
||||||
@@ -343,8 +360,10 @@ const formatTime = (date: Date): string => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Check if mouse is over an existing block (x is already relative position with scroll)
|
// Check if mouse is over an existing block (x is already relative position with scroll)
|
||||||
|
// Note: This does NOT check the current timeblock because we want to allow
|
||||||
|
// clicking elsewhere to change the current timeblock to a new time
|
||||||
const isOverExistingBlock = (relativeX: number): boolean => {
|
const isOverExistingBlock = (relativeX: number): boolean => {
|
||||||
// Check if over any scheduled block
|
// Check if over any scheduled block (saved tasks from database)
|
||||||
for (const task of props.scheduledBlocks) {
|
for (const task of props.scheduledBlocks) {
|
||||||
const blockLeft = getTimePosition(task._real_start);
|
const blockLeft = getTimePosition(task._real_start);
|
||||||
const blockWidth = getTimeWidth(task._real_start, task._real_end);
|
const blockWidth = getTimeWidth(task._real_start, task._real_end);
|
||||||
@@ -353,7 +372,7 @@ const isOverExistingBlock = (relativeX: number): boolean => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if over any other timeblock
|
// Check if over any other timeblock (from other rows in the modal)
|
||||||
for (const tb of otherTimeblocksRendered.value) {
|
for (const tb of otherTimeblocksRendered.value) {
|
||||||
const blockLeft = getTimePosition(tb._start);
|
const blockLeft = getTimePosition(tb._start);
|
||||||
const blockWidth = getTimeWidth(tb._start, tb._end);
|
const blockWidth = getTimeWidth(tb._start, tb._end);
|
||||||
@@ -362,18 +381,7 @@ const isOverExistingBlock = (relativeX: number): boolean => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if over current timeblock
|
// Don't check current timeblock - allow changing it by clicking elsewhere
|
||||||
if (props.currentTimeblockRange.start && props.currentTimeblockRange.end) {
|
|
||||||
const blockLeft = getTimePosition(props.currentTimeblockRange.start);
|
|
||||||
const blockWidth = getTimeWidth(
|
|
||||||
props.currentTimeblockRange.start,
|
|
||||||
props.currentTimeblockRange.end,
|
|
||||||
);
|
|
||||||
if (relativeX >= blockLeft && relativeX <= blockLeft + blockWidth) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -394,13 +402,38 @@ const handleMouseMove = (event: MouseEvent) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!timelineScroll.value) return;
|
if (!timelineScroll.value || !timelineBar.value) return;
|
||||||
|
|
||||||
// Get position relative to scroll container, accounting for scroll
|
// Get position relative to scroll container
|
||||||
const rect = timelineScroll.value.getBoundingClientRect();
|
const rect = timelineScroll.value.getBoundingClientRect();
|
||||||
const scrollLeft = timelineScroll.value.scrollLeft;
|
const scrollLeft = timelineScroll.value.scrollLeft;
|
||||||
|
|
||||||
|
// Calculate X position relative to timeline
|
||||||
const x = event.clientX - rect.left + scrollLeft;
|
const x = event.clientX - rect.left + scrollLeft;
|
||||||
|
|
||||||
|
// Always calculate cursor position (with 15-min snapping)
|
||||||
|
const rawTime = positionToTime(x);
|
||||||
|
const snappedTime = snapToInterval(rawTime);
|
||||||
|
const cursorHour = snappedTime.getHours() + snappedTime.getMinutes() / 60;
|
||||||
|
|
||||||
|
// Check if cursor is in working hours
|
||||||
|
const isInWorkHours =
|
||||||
|
cursorHour >= props.employee.work_start &&
|
||||||
|
cursorHour < props.employee.work_end;
|
||||||
|
|
||||||
|
// Only show cursor line if in working hours
|
||||||
|
if (isInWorkHours) {
|
||||||
|
cursorPosition.value = snappedTime;
|
||||||
|
showCursor.value = true;
|
||||||
|
} else {
|
||||||
|
showCursor.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update cursor style based on work hours
|
||||||
|
if (timelineScroll.value) {
|
||||||
|
timelineScroll.value.style.cursor = isInWorkHours ? 'crosshair' : 'not-allowed';
|
||||||
|
}
|
||||||
|
|
||||||
// Don't show preview if over existing block (using relative position)
|
// Don't show preview if over existing block (using relative position)
|
||||||
if (isOverExistingBlock(x)) {
|
if (isOverExistingBlock(x)) {
|
||||||
showPreview.value = false;
|
showPreview.value = false;
|
||||||
@@ -408,19 +441,13 @@ const handleMouseMove = (event: MouseEvent) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Convert to time and snap
|
// Convert to time and snap
|
||||||
const rawTime = positionToTime(x);
|
const snappedStart = snappedTime;
|
||||||
const snappedStart = snapToInterval(rawTime);
|
|
||||||
|
|
||||||
// Calculate default end time (1 hour later)
|
// Calculate default end time (1 hour later)
|
||||||
let snappedEnd = moment(snappedStart).add(1, 'hour').toDate();
|
let snappedEnd = moment(snappedStart).add(1, 'hour').toDate();
|
||||||
|
|
||||||
// Only show preview if start is within work hours
|
// Only show preview if start is within work hours
|
||||||
const startHour = snappedStart.getHours() + snappedStart.getMinutes() / 60;
|
if (isInWorkHours) {
|
||||||
|
|
||||||
if (
|
|
||||||
startHour >= props.employee.work_start &&
|
|
||||||
startHour < props.employee.work_end
|
|
||||||
) {
|
|
||||||
// If the end would exceed work hours, cap it at work_end
|
// If the end would exceed work hours, cap it at work_end
|
||||||
const endHour = snappedEnd.getHours() + snappedEnd.getMinutes() / 60;
|
const endHour = snappedEnd.getHours() + snappedEnd.getMinutes() / 60;
|
||||||
|
|
||||||
@@ -450,23 +477,72 @@ const handleMouseMove = (event: MouseEvent) => {
|
|||||||
const handleMouseLeave = () => {
|
const handleMouseLeave = () => {
|
||||||
if (!dragState.value.isStretching && !dragState.value.isResizingCurrent) {
|
if (!dragState.value.isStretching && !dragState.value.isResizingCurrent) {
|
||||||
showPreview.value = false;
|
showPreview.value = false;
|
||||||
|
showCursor.value = false;
|
||||||
|
// Reset cursor style to default crosshair
|
||||||
|
if (timelineScroll.value) {
|
||||||
|
timelineScroll.value.style.cursor = 'crosshair';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mouse down on timeline bar (not on preview or current block)
|
// Handle mousedown on timeline (including header area)
|
||||||
const handleMouseDown = (_event: MouseEvent) => {
|
const handleTimelineMouseDown = (event: MouseEvent) => {
|
||||||
// This will only trigger if not clicking on preview or current block
|
if (isMobile.value) return;
|
||||||
// (those have @mousedown.stop)
|
if (!timelineScroll.value || !cursorPosition.value) return;
|
||||||
};
|
|
||||||
|
|
||||||
// Preview block mouse down - start creating/stretching
|
// Get the target element
|
||||||
const handlePreviewMouseDown = (event: MouseEvent) => {
|
const target = event.target as HTMLElement;
|
||||||
if (!previewBlock.value) return;
|
|
||||||
|
|
||||||
|
// Don't interfere with resize handles on current timeblock
|
||||||
|
if (target.classList.contains('resize-handle')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if clicking on current timeblock (allow resize handles to work)
|
||||||
|
if (target.closest('.current-timeblock')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rect = timelineScroll.value.getBoundingClientRect();
|
||||||
|
const scrollLeft = timelineScroll.value.scrollLeft;
|
||||||
|
const x = event.clientX - rect.left + scrollLeft;
|
||||||
|
|
||||||
|
// Don't start creating if over existing block
|
||||||
|
if (isOverExistingBlock(x)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if cursor is in work hours (cursor line must be visible)
|
||||||
|
if (!showCursor.value || !showPreview.value || !previewBlock.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start creating/stretching block at the snapped cursor position
|
||||||
dragState.value.isStretching = true;
|
dragState.value.isStretching = true;
|
||||||
dragState.value.startX = event.clientX;
|
dragState.value.startX = event.clientX;
|
||||||
dragState.value.initialStart = previewBlock.value.start;
|
dragState.value.initialStart = cursorPosition.value;
|
||||||
dragState.value.initialEnd = previewBlock.value.end;
|
|
||||||
|
// Calculate initial end time (1 hour later, capped at work_end)
|
||||||
|
let initialEnd = moment(cursorPosition.value).add(1, 'hour').toDate();
|
||||||
|
const endHour = initialEnd.getHours() + initialEnd.getMinutes() / 60;
|
||||||
|
if (endHour > props.employee.work_end) {
|
||||||
|
initialEnd = moment(cursorPosition.value)
|
||||||
|
.set({
|
||||||
|
hour: Math.floor(props.employee.work_end),
|
||||||
|
minute: (props.employee.work_end % 1) * 60,
|
||||||
|
second: 0,
|
||||||
|
millisecond: 0,
|
||||||
|
})
|
||||||
|
.toDate();
|
||||||
|
}
|
||||||
|
|
||||||
|
dragState.value.initialEnd = initialEnd;
|
||||||
|
|
||||||
|
// Set initial preview block (already has 1 hour duration from preview)
|
||||||
|
previewBlock.value = {
|
||||||
|
start: cursorPosition.value,
|
||||||
|
end: initialEnd,
|
||||||
|
};
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
};
|
};
|
||||||
@@ -484,13 +560,18 @@ const handleStretchMove = (event: MouseEvent) => {
|
|||||||
const initialStartTime = dragState.value.initialStart.getTime();
|
const initialStartTime = dragState.value.initialStart.getTime();
|
||||||
const snappedTimeValue = snappedTime.getTime();
|
const snappedTimeValue = snappedTime.getTime();
|
||||||
|
|
||||||
if (snappedTimeValue > initialStartTime) {
|
if (snappedTimeValue >= initialStartTime) {
|
||||||
// Stretching right (extending end)
|
// Stretching right (extending end)
|
||||||
|
// Ensure minimum 15-minute block
|
||||||
|
let endTime = snappedTime;
|
||||||
|
if (snappedTimeValue === initialStartTime) {
|
||||||
|
endTime = moment(snappedTime).add(15, 'minutes').toDate();
|
||||||
|
}
|
||||||
|
|
||||||
// Cap at work_end
|
// Cap at work_end
|
||||||
const snappedEndHour =
|
const endHour = endTime.getHours() + endTime.getMinutes() / 60;
|
||||||
snappedTime.getHours() + snappedTime.getMinutes() / 60;
|
if (endHour > props.employee.work_end) {
|
||||||
if (snappedEndHour > props.employee.work_end) {
|
endTime = moment(endTime)
|
||||||
snappedTime = moment(snappedTime)
|
|
||||||
.set({
|
.set({
|
||||||
hour: Math.floor(props.employee.work_end),
|
hour: Math.floor(props.employee.work_end),
|
||||||
minute: (props.employee.work_end % 1) * 60,
|
minute: (props.employee.work_end % 1) * 60,
|
||||||
@@ -502,8 +583,12 @@ const handleStretchMove = (event: MouseEvent) => {
|
|||||||
|
|
||||||
previewBlock.value = {
|
previewBlock.value = {
|
||||||
start: dragState.value.initialStart,
|
start: dragState.value.initialStart,
|
||||||
end: snappedTime,
|
end: endTime,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Update cursor to show at the actual block edge (end)
|
||||||
|
cursorPosition.value = endTime;
|
||||||
|
showCursor.value = true;
|
||||||
} else {
|
} else {
|
||||||
// Stretching left (extending start)
|
// Stretching left (extending start)
|
||||||
// Cap at work_start
|
// Cap at work_start
|
||||||
@@ -524,18 +609,28 @@ const handleStretchMove = (event: MouseEvent) => {
|
|||||||
start: snappedTime,
|
start: snappedTime,
|
||||||
end: dragState.value.initialStart,
|
end: dragState.value.initialStart,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Update cursor to show at the actual block edge (start)
|
||||||
|
cursorPosition.value = snappedTime;
|
||||||
|
showCursor.value = true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle stretch end
|
// Handle stretch end
|
||||||
const handleStretchEnd = () => {
|
const handleStretchEnd = () => {
|
||||||
if (dragState.value.isStretching && previewBlock.value) {
|
if (dragState.value.isStretching && previewBlock.value) {
|
||||||
|
// Only emit if we have a valid block (start != end)
|
||||||
|
const startTime = previewBlock.value.start.getTime();
|
||||||
|
const endTime = previewBlock.value.end.getTime();
|
||||||
|
|
||||||
|
if (endTime > startTime) {
|
||||||
// Emit the new timeblock
|
// Emit the new timeblock
|
||||||
emit('timeblockUpdated', {
|
emit('timeblockUpdated', {
|
||||||
starts_at_time: formatTime(previewBlock.value.start),
|
starts_at_time: formatTime(previewBlock.value.start),
|
||||||
ends_at_time: formatTime(previewBlock.value.end),
|
ends_at_time: formatTime(previewBlock.value.end),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dragState.value.isStretching = false;
|
dragState.value.isStretching = false;
|
||||||
dragState.value.initialStart = null;
|
dragState.value.initialStart = null;
|
||||||
@@ -599,6 +694,9 @@ const handleResizeCurrentMove = (event: MouseEvent) => {
|
|||||||
start: snappedTime,
|
start: snappedTime,
|
||||||
end: dragState.value.initialEnd,
|
end: dragState.value.initialEnd,
|
||||||
};
|
};
|
||||||
|
// Update cursor to show at the actual block edge
|
||||||
|
cursorPosition.value = snappedTime;
|
||||||
|
showCursor.value = true;
|
||||||
}
|
}
|
||||||
} else if (dragState.value.resizeEdge === 'right') {
|
} else if (dragState.value.resizeEdge === 'right') {
|
||||||
// Resizing right edge (changing end time)
|
// Resizing right edge (changing end time)
|
||||||
@@ -622,6 +720,9 @@ const handleResizeCurrentMove = (event: MouseEvent) => {
|
|||||||
start: dragState.value.initialStart,
|
start: dragState.value.initialStart,
|
||||||
end: snappedTime,
|
end: snappedTime,
|
||||||
};
|
};
|
||||||
|
// Update cursor to show at the actual block edge
|
||||||
|
cursorPosition.value = snappedTime;
|
||||||
|
showCursor.value = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -709,6 +810,7 @@ defineExpose({
|
|||||||
position: relative
|
position: relative
|
||||||
width: 100%
|
width: 100%
|
||||||
scrollbar-width: none // Firefox
|
scrollbar-width: none // Firefox
|
||||||
|
cursor: crosshair
|
||||||
|
|
||||||
&::-webkit-scrollbar
|
&::-webkit-scrollbar
|
||||||
display: none // Chrome, Safari, Opera
|
display: none // Chrome, Safari, Opera
|
||||||
@@ -719,7 +821,6 @@ defineExpose({
|
|||||||
width: calc(24 * 60px)
|
width: calc(24 * 60px)
|
||||||
background: #fafafa
|
background: #fafafa
|
||||||
border-radius: 4px
|
border-radius: 4px
|
||||||
cursor: crosshair
|
|
||||||
|
|
||||||
@media (max-width: 768px)
|
@media (max-width: 768px)
|
||||||
width: calc(24 * 50px)
|
width: calc(24 * 50px)
|
||||||
@@ -792,6 +893,7 @@ defineExpose({
|
|||||||
display: flex
|
display: flex
|
||||||
align-items: center
|
align-items: center
|
||||||
justify-content: center
|
justify-content: center
|
||||||
|
pointer-events: none
|
||||||
|
|
||||||
&:hover
|
&:hover
|
||||||
opacity: 0.8
|
opacity: 0.8
|
||||||
@@ -861,6 +963,15 @@ defineExpose({
|
|||||||
z-index: 4
|
z-index: 4
|
||||||
pointer-events: none
|
pointer-events: none
|
||||||
|
|
||||||
|
.timeline-cursor
|
||||||
|
position: absolute
|
||||||
|
top: 0
|
||||||
|
bottom: 0
|
||||||
|
width: 2px
|
||||||
|
background: rgba(0, 0, 0, 0.3)
|
||||||
|
z-index: 10
|
||||||
|
pointer-events: none
|
||||||
|
|
||||||
.timeblock-duration
|
.timeblock-duration
|
||||||
position: absolute
|
position: absolute
|
||||||
top: 50%
|
top: 50%
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
ref="timelineRef"
|
ref="timelineRef"
|
||||||
:is-loading="isLoading"
|
:is-loading="isLoading"
|
||||||
:employee="employee"
|
:employee="employee"
|
||||||
|
:date="modelValue.date"
|
||||||
:scheduled-blocks="scheduledBlocks"
|
:scheduled-blocks="scheduledBlocks"
|
||||||
:other-timeblocks="otherTimeblocksForSameDate"
|
:other-timeblocks="otherTimeblocksForSameDate"
|
||||||
:current-timeblock-range="currentTimeblockRange"
|
:current-timeblock-range="currentTimeblockRange"
|
||||||
|
|||||||
Reference in new issue
Block a user