feat(timeblock): enhance timeblock components with color indication for negative free time and improved time constraints for employee schedulingd

This commit is contained in:
Matej Glasnák committed 2026-01-08 00:45:01 +01:00
1 parent 1b7cea85bf
commit 1b666a63a3
3 files changed
+109 -7

No files matched your search

@@ -117,7 +117,9 @@
class="timeblock__time-overview__label timeblock__time-overview__label--mobile"
>Vol. čas:</span
>
<span>{{ freeTime }}h</span>
<span :style="{ color: Number(freeTime) < 0 ? 'red' : undefined }"
>{{ freeTime }}h</span
>
</p>
</div>
</div>
@@ -398,16 +398,32 @@ const handleMouseMove = (event: MouseEvent) => {
// Convert to time and snap
const rawTime = positionToTime(x);
const snappedStart = snapToInterval(rawTime);
const snappedEnd = moment(snappedStart).add(1, 'hour').toDate();
// Only show preview within work hours
// Calculate default end time (1 hour later)
let snappedEnd = moment(snappedStart).add(1, 'hour').toDate();
// Only show preview if start is within work hours
const startHour = snappedStart.getHours() + snappedStart.getMinutes() / 60;
const endHour = snappedEnd.getHours() + snappedEnd.getMinutes() / 60;
if (
startHour >= props.employee.work_start &&
endHour <= props.employee.work_end
startHour < props.employee.work_end
) {
// If the end would exceed work hours, cap it at work_end
const endHour = snappedEnd.getHours() + snappedEnd.getMinutes() / 60;
if (endHour > props.employee.work_end) {
// Create end time at exactly work_end
snappedEnd = moment(snappedStart)
.set({
hour: Math.floor(props.employee.work_end),
minute: (props.employee.work_end % 1) * 60,
second: 0,
millisecond: 0,
})
.toDate();
}
showPreview.value = true;
previewBlock.value = {
start: snappedStart,
@@ -451,19 +467,47 @@ const handleStretchMove = (event: MouseEvent) => {
const x = event.clientX - rect.left + timelineScroll.value.scrollLeft;
const currentTime = positionToTime(x);
const snappedTime = snapToInterval(currentTime);
let snappedTime = snapToInterval(currentTime);
const initialStartTime = dragState.value.initialStart.getTime();
const snappedTimeValue = snappedTime.getTime();
if (snappedTimeValue > initialStartTime) {
// Stretching right (extending end)
// Cap at work_end
const snappedEndHour =
snappedTime.getHours() + snappedTime.getMinutes() / 60;
if (snappedEndHour > props.employee.work_end) {
snappedTime = moment(snappedTime)
.set({
hour: Math.floor(props.employee.work_end),
minute: (props.employee.work_end % 1) * 60,
second: 0,
millisecond: 0,
})
.toDate();
}
previewBlock.value = {
start: dragState.value.initialStart,
end: snappedTime,
};
} else {
// Stretching left (extending start)
// Cap at work_start
const snappedStartHour =
snappedTime.getHours() + snappedTime.getMinutes() / 60;
if (snappedStartHour < props.employee.work_start) {
snappedTime = moment(snappedTime)
.set({
hour: Math.floor(props.employee.work_start),
minute: (props.employee.work_start % 1) * 60,
second: 0,
millisecond: 0,
})
.toDate();
}
previewBlock.value = {
start: snappedTime,
end: dragState.value.initialStart,
@@ -519,10 +563,24 @@ const handleResizeCurrentMove = (event: MouseEvent) => {
const x = event.clientX - rect.left + timelineScroll.value.scrollLeft;
const currentTime = positionToTime(x);
const snappedTime = snapToInterval(currentTime);
let snappedTime = snapToInterval(currentTime);
if (dragState.value.resizeEdge === 'left') {
// Resizing left edge (changing start time)
// Cap at work_start
const snappedStartHour =
snappedTime.getHours() + snappedTime.getMinutes() / 60;
if (snappedStartHour < props.employee.work_start) {
snappedTime = moment(snappedTime)
.set({
hour: Math.floor(props.employee.work_start),
minute: (props.employee.work_start % 1) * 60,
second: 0,
millisecond: 0,
})
.toDate();
}
// Make sure new start is before end
if (snappedTime < dragState.value.initialEnd) {
resizedCurrentBlock.value = {
@@ -532,6 +590,20 @@ const handleResizeCurrentMove = (event: MouseEvent) => {
}
} else if (dragState.value.resizeEdge === 'right') {
// Resizing right edge (changing end time)
// Cap at work_end
const snappedEndHour =
snappedTime.getHours() + snappedTime.getMinutes() / 60;
if (snappedEndHour > props.employee.work_end) {
snappedTime = moment(snappedTime)
.set({
hour: Math.floor(props.employee.work_end),
minute: (props.employee.work_end % 1) * 60,
second: 0,
millisecond: 0,
})
.toDate();
}
// Make sure new end is after start
if (snappedTime > dragState.value.initialStart) {
resizedCurrentBlock.value = {
@@ -426,6 +426,34 @@ const validate = async () => {
defineExpose({
validate,
});
watch(
() => props.modelValue.ends_at_time,
() => {
if (v$.value.ends_at_time.$model !== props.modelValue.ends_at_time) {
v$.value.ends_at_time.$model = props.modelValue.ends_at_time;
}
},
);
watch(
() => props.modelValue.starts_at_time,
() => {
if (v$.value.starts_at_time.$model !== props.modelValue.starts_at_time) {
v$.value.starts_at_time.$model = props.modelValue.starts_at_time;
console.log('starts_at_time changed', v$.value.starts_at_time.$model);
}
},
);
watch(
() => props.modelValue.date,
() => {
if (v$.value.date.$model !== props.modelValue.date) {
v$.value.date.$model = props.modelValue.date;
}
},
);
</script>
<style lang="sass" scoped>