build(dev): finished timeblocks rework

This commit is contained in:
Matej Glasnák committed 2025-12-08 23:54:35 +01:00
1 parent 1b0a9d5fff
commit 90dd4ee4b6
15 files changed
+194 -166

No files matched your search

+3
View File
@@ -68,6 +68,9 @@ th
.v-card
padding: 35px !important
@media (max-width: 768px)
padding: 16px !important
.cursor-pointer:hover
cursor: pointer
@@ -1,5 +1,5 @@
import { useQuery } from '@tanstack/vue-query';
import { computed, Ref, unref } from 'vue';
import { computed, Ref, unref, watch } from 'vue';
import axios from '@/plugins/w@custom/axios/models/axios';
import moment from 'moment';
import timelineUtils from '@/plugins/tm@dashboard/tasks/_utils/timeline.utils';
@@ -130,7 +130,6 @@ export function useEmployeeTimeline(
const scheduledTime = computed(() => {
const tasks = unref(timelineTasks);
const d = unref(date);
const now = moment();
if (!tasks || !d) return 0;
@@ -145,25 +144,10 @@ export function useEmployeeTimeline(
return;
}
if (now.isAfter(moment(task._real_end))) {
// Ignore elapsed tasks
return;
} else if (
now.isAfter(moment(task._real_start)) &&
now.isBefore(moment(task._real_end))
) {
// Task in progress
totalMinutes += timelineUtils._getDifferenceInMinutes(
task._real_end,
new Date(),
);
} else {
// Future task
totalMinutes += timelineUtils._getDifferenceInMinutes(
task._real_end,
task._real_start,
);
}
totalMinutes += timelineUtils._getDifferenceInMinutes(
task._real_end,
task._real_start,
);
});
return (totalMinutes / 60).toFixed(1);
@@ -177,28 +161,9 @@ export function useEmployeeTimeline(
if (!emp || !d) return '0';
const now = moment();
const dateObj = moment(d);
const dailyCapacityHours = emp.work_end - emp.work_start;
if (dateObj.isBefore(now, 'days')) {
// Past date
return '0';
} else if (dateObj.isSame(now, 'days')) {
// Today - calculate remaining capacity
const elapsedMinutes = now.diff(now.clone().startOf('day'), 'minutes');
const workStart =
emp.work_start * 60 > elapsedMinutes
? emp.work_start
: elapsedMinutes / 60;
const todayCapacityHours = emp.work_end - workStart;
const free = todayCapacityHours - scheduled;
return free < 0 ? '0' : free.toFixed(1);
} else {
// Future date
const free = dailyCapacityHours - scheduled;
return free < 0 ? '0' : free.toFixed(1);
}
return (dailyCapacityHours - scheduled).toFixed(1);
});
// Detect collisions with current timeblock
@@ -237,6 +202,22 @@ export function useEmployeeTimeline(
return calculateTimeblockDuration(timeblock);
});
watch(
scheduledTime,
(newVal) => {
console.log('scheduledTime', newVal);
},
{ immediate: true },
);
watch(
freeTime,
(newVal) => {
console.log('freeTime', newVal);
},
{ immediate: true },
);
return {
// Query states
isLoading,
@@ -316,7 +316,7 @@ export default {
},
taskClicked(subtask) {
// console.log('task clicked')
console.log('task clicked', subtask);
},
eventFocused(event) {
@@ -150,6 +150,20 @@ export default {
};
},
watch: {
ends_at_time() {
this._syncDatesTimes();
},
starts_at_time() {
this._syncDatesTimes();
},
datepicker() {
this._syncDatesTimes();
},
},
methods: {
async showModal({ blocker }) {
this.visible = true;
@@ -252,20 +266,6 @@ export default {
.format('YYYY-MM-DD HH:mm');
},
},
watch: {
ends_at_time() {
this._syncDatesTimes();
},
starts_at_time() {
this._syncDatesTimes();
},
datepicker() {
this._syncDatesTimes();
},
},
} /* c8 ignore start */ /* eslint-disable */;
/* istanbul ignore next */
function oo_cm() {
@@ -430,6 +430,57 @@ export default {
};
},
watch: {
editMode() {
this.$nextTick(() => {
this.$refs['description']?.setHTML(this.mastertask.description || '');
if (!this.mastertask?.id) return;
const date = new Date(this.mastertask.deadline);
const hours = date.getHours();
const minutes = date.getMinutes();
this.mastertask._deadline_time = `${hours
.toString()
.padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;
});
},
mastertask: {
handler(val) {
if (this.editMode && !this.mastertask?.id) {
localStorage.setItem('mastertaskData', JSON.stringify(val));
}
},
deep: true,
},
'mastertask.team_id': {
handler(value, oldValue) {
if (this.editMode && !value && oldValue) {
// Removes only employees from the old team when team_id is removed
const oldTeamEmployeesIds =
this.teams
.find((team) => team.id == oldValue)
?.employees.map((employee) => employee?.id) || [];
const employeesNotInOldTeam = this.mastertask.employees.filter(
(employee) => !oldTeamEmployeesIds.includes(employee?.id),
);
this.mastertask.employees = employeesNotInOldTeam;
}
},
deep: true,
},
'$route.query': {
handler(_, oldValue) {
if (oldValue?.subtask?.length && this.mastertask?.id) {
this.loadMastertask(this.mastertask.id);
}
},
deep: true,
},
},
mounted() {
this.$events.on('close-both-modals', this.closeBothModals);
@@ -693,57 +744,6 @@ export default {
return isValid;
},
},
watch: {
editMode() {
this.$nextTick(() => {
this.$refs['description']?.setHTML(this.mastertask.description || '');
if (!this.mastertask?.id) return;
const date = new Date(this.mastertask.deadline);
const hours = date.getHours();
const minutes = date.getMinutes();
this.mastertask._deadline_time = `${hours
.toString()
.padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;
});
},
mastertask: {
handler(val) {
if (this.editMode && !this.mastertask?.id) {
localStorage.setItem('mastertaskData', JSON.stringify(val));
}
},
deep: true,
},
'mastertask.team_id': {
handler(value, oldValue) {
if (this.editMode && !value && oldValue) {
// Removes only employees from the old team when team_id is removed
const oldTeamEmployeesIds =
this.teams
.find((team) => team.id == oldValue)
?.employees.map((employee) => employee?.id) || [];
const employeesNotInOldTeam = this.mastertask.employees.filter(
(employee) => !oldTeamEmployeesIds.includes(employee?.id),
);
this.mastertask.employees = employeesNotInOldTeam;
}
},
deep: true,
},
'$route.query': {
handler(_, oldValue) {
if (oldValue?.subtask?.length && this.mastertask?.id) {
this.loadMastertask(this.mastertask.id);
}
},
deep: true,
},
},
};
</script>
@@ -392,7 +392,8 @@ export default {
this._update(subtask);
},
deleteTimeblockWithoutId(i) {
deleteTimeblockWithoutId(index) {
console.log(index);
this.$emit('delete-timeblock');
},
@@ -15,25 +15,27 @@
@update:model-value="handleUpdateEmployee"
/>
<v-text-field
:model-value="modelValue.duration"
class="employee-select__duration-field"
variant="solo"
type="number"
label="Časový odhad"
:class="{ error: v$.duration.$error }"
:error-messages="v$.duration.$errors.map((e: any) => String(e.$message))"
append-inner-icon="mdi-clock-outline"
@update:model-value="handleUpdateDuration"
/>
<div class="employee-select__duration-container">
<v-text-field
:model-value="modelValue.duration"
class="employee-select__duration-field"
variant="solo"
type="number"
label="Časový odhad"
:class="{ error: v$.duration.$error }"
:error-messages="v$.duration.$errors.map((e: any) => String(e.$message))"
append-inner-icon="mdi-clock-outline"
@update:model-value="handleUpdateDuration"
/>
<button
class="employee-select__delete-button"
:disabled="!modelValue.id"
@click="handleClearEmployee"
>
<v-icon icon="mdi-close" />
</button>
<button
class="employee-select__delete-button"
:disabled="!modelValue.id"
@click="handleClearEmployee"
>
<v-icon icon="mdi-close" />
</button>
</div>
</div>
</template>
@@ -160,6 +162,16 @@ defineExpose({
align-items: start
gap: 10px
@media (max-width: 768px)
flex-direction: column
gap: 5px
&__duration-container
display: flex
justify-content: space-between
align-items: start
gap: 10px
&__select-field
width: 100%
@@ -73,6 +73,17 @@
hide-details
@input="$emit('updateEndTime', $event)"
/>
<div class="timeblock__time-overview">
<p>
Naplánovaný čas:
<span>{{ plannedTime }}h</span>
</p>
<p>
Volný čas:
<span>{{ freeTime }}h</span>
</p>
</div>
</div>
<div
v-if="!isTimeRangeValid || endTimeErrorMessage"
@@ -115,10 +126,14 @@ interface Props {
hasStartTimeError: boolean;
hasEndTimeError: boolean;
endTimeErrorMessage?: string;
plannedTime: string | number;
freeTime: string | number;
}
const props = withDefaults(defineProps<Props>(), {
endTimeErrorMessage: '',
plannedTime: '',
freeTime: '',
});
defineEmits<{
@@ -231,6 +246,20 @@ const isCurrentDay = computed(() => {
width: 140px !important
flex-grow: 0
.timeblock__time-overview
height: 44px
display: flex
flex-direction: column
justify-content: space-between
p
font-size: 14px
font-weight: 400
span
font-weight: 500
color: #7f39fb
.error-input
::v-deep input
border-bottom: 2px solid #ff5252 !important
@@ -180,13 +180,13 @@ const getTimePosition = (date: Date): number => {
const hour = date.getHours();
const minute = date.getMinutes();
const totalHours = hour + minute / 60;
return (totalHours - props.startHour) * props.hourWidth;
return (totalHours - props.startHour) * props.hourWidth + 1;
};
const getTimeWidth = (start: Date, end: Date): number => {
const startHour = start.getHours() + start.getMinutes() / 60;
const endHour = end.getHours() + end.getMinutes() / 60;
return (endHour - startHour) * props.hourWidth;
return (endHour - startHour) * props.hourWidth - 2;
};
// Expose refs for parent component
@@ -252,6 +252,7 @@ defineExpose({
top: 5px
height: 50px
background: #7f39fb
border: 1px solid #6a1bf7
opacity: 0.6
border-radius: 2px
z-index: 2
@@ -299,4 +300,3 @@ defineExpose({
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3)
pointer-events: none
</style>
@@ -11,6 +11,8 @@
:has-start-time-error="hasStartTimeError"
:has-end-time-error="hasEndTimeError"
:end-time-error-message="endTimeErrorMessage"
:planned-time="_scheduledTime"
:free-time="_freeTime"
@previous-day="previousDay"
@next-day="nextDay"
@update-date="updateDate"
+13 -13
View File
@@ -143,6 +143,19 @@ export default {
};
},
watch: {
employeeSelect(employeeId) {
if (!employeeId) return;
const employee = this.enrichedEmployees.find(
(employee) => employee.id == employeeId,
);
this.team.employees.push(employee);
this.employeeSelect = null;
this.$refs.autocomplete.search = '';
},
},
methods: {
async showModal({ team = null }) {
this.visible = true;
@@ -200,18 +213,5 @@ export default {
return team;
},
},
watch: {
employeeSelect(employeeId) {
if (!employeeId) return;
const employee = this.enrichedEmployees.find(
(employee) => employee.id == employeeId,
);
this.team.employees.push(employee);
this.employeeSelect = null;
this.$refs.autocomplete.search = '';
},
},
};
</script>
@@ -80,6 +80,12 @@ export default {
};
},
watch: {
user() {
this._setDates();
},
},
mounted() {
this._setDates();
},
@@ -122,20 +128,14 @@ export default {
// },
decimalHoursToTime(decimalHours) {
var hours = Math.floor(decimalHours);
var mins = Math.floor((decimalHours * 60) % 60);
let hours = Math.floor(decimalHours);
let mins = Math.floor((decimalHours * 60) % 60);
hours = (hours < 10 ? '0' : '') + hours;
mins = (mins < 10 ? '0' : '') + mins;
return hours + ':' + mins;
},
},
watch: {
user() {
this._setDates();
},
},
};
</script>