feat(z-task-modal): improve employee selection logic and add weekend overlay in timeblock components

This commit is contained in:
Matej Glasnák committed 2026-01-08 01:13:06 +01:00
1 parent 1b666a63a3
commit 198f50dfc1
3 files changed
+81 -20

No files matched your search

@@ -47,6 +47,9 @@
}" }"
></div> ></div>
<!-- Weekend background overlay -->
<div v-if="isWeekend" class="weekend-overlay"></div>
<!-- Existing scheduled blocks --> <!-- Existing scheduled blocks -->
<div <div
v-for="(task, index) in scheduledBlocks" v-for="(task, index) in scheduledBlocks"
@@ -257,6 +260,15 @@ const dragState = ref({
}); });
const resizedCurrentBlock = ref<{ start: Date; end: Date } | null>(null); const resizedCurrentBlock = ref<{ start: Date; end: Date } | null>(null);
// Check if the current date is a weekend (Saturday or Sunday)
const isWeekend = computed(() => {
const date = props.currentTimeblockRange.start;
if (!date) return false;
const dayOfWeek = moment(date).day(); // 0 = Sunday, 6 = Saturday
return dayOfWeek === 0 || dayOfWeek === 6;
});
// Convert other timeblocks to renderable format // Convert other timeblocks to renderable format
const otherTimeblocksRendered = computed(() => { const otherTimeblocksRendered = computed(() => {
return props.otherTimeblocks return props.otherTimeblocks
@@ -728,6 +740,17 @@ defineExpose({
z-index: 1 z-index: 1
pointer-events: none pointer-events: none
.weekend-overlay
position: absolute
top: 0
left: 0
width: 100%
height: 100%
background: rgba(255, 124, 124, 0.3) !important
z-index: 1
pointer-events: none
border-radius: 4px
.scheduled-block .scheduled-block
position: absolute position: absolute
top: 5px top: 5px
@@ -281,15 +281,39 @@ const scheduledBlocks = computed(() => {
); );
}); });
// Recalculate collision using filtered scheduled blocks (not raw timeline tasks) // Convert other unsaved timeblocks to task format for collision detection
// This prevents false collisions with timeblocks being edited const otherTimeblocksAsTasks = computed(() => {
const collision = computed(() => { return otherTimeblocksForSameDate.value
return detectCollision(props.modelValue, scheduledBlocks.value); .map((tb) => {
const { start, end } = timeblockToDateRange(tb);
if (!start || !end) return null;
return {
_real_start: start,
_real_end: end,
_timeblock_id: tb.id || null,
};
})
.filter((task) => task !== null);
}); });
// Recalculate collision segments using filtered scheduled blocks // Combine scheduled blocks with other unsaved timeblocks for collision detection
const allBlocksForCollisionCheck = computed(() => {
return [...scheduledBlocks.value, ...otherTimeblocksAsTasks.value];
});
// Recalculate collision using filtered scheduled blocks AND other unsaved timeblocks
// This prevents false collisions with timeblocks being edited, but catches collisions with other unsaved timeblocks
const collision = computed(() => {
return detectCollision(props.modelValue, allBlocksForCollisionCheck.value);
});
// Recalculate collision segments using filtered scheduled blocks AND other unsaved timeblocks
const collisionSegments = computed(() => { const collisionSegments = computed(() => {
return getCollisionSegmentsUtil(props.modelValue, scheduledBlocks.value); return getCollisionSegmentsUtil(
props.modelValue,
allBlocksForCollisionCheck.value,
);
}); });
// Get current timeblock range // Get current timeblock range
+28 -14
View File
@@ -531,19 +531,20 @@ export default {
const employeeId = itemObject.value; const employeeId = itemObject.value;
if (!this.subtask.id) { if (!this.subtask.id) {
// New subtask with multiple employees support or edit of draft with multiple employees // New subtask with multiple employees support
return ( // Hide employees that are already selected in any slot
shouldShowItem && // The v-autocomplete will still display the currently selected employee
!this.timeblocks.some((tb) => tb.employee_id === employeeId) const isAlreadySelected = this.subtask.employees.some(
(emp) => emp.id && Number(emp.id) === employeeId,
); );
return shouldShowItem && !isAlreadySelected;
} else if (this.subtask.is_draft) { } else if (this.subtask.is_draft) {
// Edit draft subtask with multiple employees support // Edit draft subtask with multiple employees support
return ( // Hide employees that are already selected in any slot
shouldShowItem && const isAlreadySelected = this.subtask.employees.some(
!this.subtask.employees.some( (emp) => emp.id && Number(emp.id) === employeeId,
(employee) => Number(employee.id) === employeeId,
)
); );
return shouldShowItem && !isAlreadySelected;
} else { } else {
// Saved standard subtask, allow "re-select" for better UX // Saved standard subtask, allow "re-select" for better UX
return shouldShowItem; return shouldShowItem;
@@ -1046,8 +1047,9 @@ export default {
// For drafts with multiple employees, include employees array // For drafts with multiple employees, include employees array
if (this.subtask.is_draft && this.isDraftWithMultipleEmployees) { if (this.subtask.is_draft && this.isDraftWithMultipleEmployees) {
const validEmployees = this.subtask.employees.filter( const validEmployees = this._filterValidEmployees(
(employee) => employee.id && employee.duration, this.subtask.employees,
concept,
); );
// Assign timeblocks to each employee // Assign timeblocks to each employee
@@ -1090,9 +1092,9 @@ export default {
this.$store.commit('tm/update_subtask', subtask); this.$store.commit('tm/update_subtask', subtask);
this.$events.emit('on-subtask-edited', this.subtask); this.$events.emit('on-subtask-edited', this.subtask);
} else { } else {
// Filter out employees that don't have both id and duration const validEmployees = this._filterValidEmployees(
const validEmployees = this.subtask.employees.filter( this.subtask.employees,
(employee) => employee.id && employee.duration, concept,
); );
// Assign timeblocks to each employee based on employee_id // Assign timeblocks to each employee based on employee_id
@@ -1198,6 +1200,18 @@ export default {
} }
}, },
_filterValidEmployees(employees, isConcept) {
// Filter employees based on whether it's a concept/draft or published task
// For concepts: only require id (duration can be null/0)
// For published tasks: require both id and duration
return employees.filter((employee) => {
if (isConcept) {
return employee.id; // Only require ID for concepts/drafts
}
return employee.id && employee.duration; // Require both for published tasks
});
},
async _validateEmployees() { async _validateEmployees() {
if (this.subtask.id && !this.subtask.is_draft) { if (this.subtask.id && !this.subtask.is_draft) {
// For existing subtasks, at least one employee is required // For existing subtasks, at least one employee is required