This commit is contained in:
Matej Glasnák committed 2025-12-11 01:11:41 +01:00
1 parent b12e815c57
commit 438d31e254
6 files changed
+237 -71

No files matched your search

@@ -32,7 +32,4 @@ export default {
&:hover
opacity: 1
@media (max-width: 768px)
display: none !important
</style>
@@ -0,0 +1,74 @@
<template>
<div class="actions">
<button class="add-btn" @click="$emit('addTimeblock')">+</button>
<button
:disabled="!props.deletable"
class="delete-btn"
@click="$emit('removeTimeblock')"
>
×
</button>
</div>
</template>
<script setup lang="ts">
interface Props {
deletable: boolean;
}
const props = withDefaults(defineProps<Props>(), {
deletable: false,
});
defineEmits<{
(e: 'addTimeblock'): void;
(e: 'removeTimeblock'): void;
}>();
</script>
<style lang="sass" scoped>
.actions
display: flex
align-items: center
gap: 8px
.add-btn
background: #7f39fb
color: white
border: none
border-radius: 4px
width: 44px
height: 44px
font-size: 28px
cursor: pointer
display: flex
align-items: center
justify-content: center
transition: all 0.2s
line-height: 1
&:hover
background: #6825e0
.delete-btn
background: #ff5252
color: white
border: none
border-radius: 4px
width: 44px
height: 44px
font-size: 28px
cursor: pointer
display: flex
align-items: center
justify-content: center
transition: all 0.2s
line-height: 1
&:disabled
opacity: 0.5
cursor: not-allowed
&:hover
background: #ff1744
</style>
@@ -1,5 +1,15 @@
<template>
<div class="timeblock__header">
<div class="timeblock__header__top">
<p>
Časový blok {{ index + 1 }}
-
{{ employee._fullName }}
</p>
</div>
<div class="timeblock__header__content">
<div class="timeblock__date-wrapper">
<div class="timeblock__date">
<button
class="timeblock__date-button"
@@ -12,8 +22,9 @@
</button>
<Z-datepicker
:key="dateFormat"
:model-value="modelValue.date"
format="dd.MM.yyyy"
:format="dateFormat"
locale="sk"
select-text="Vybrať"
cancel-text="Zrušiť"
@@ -38,7 +49,17 @@
</button>
</div>
<div class="actions actions--mobile">
<a-employee-timeblock-header-actions
:deletable="deletable"
@add-timeblock="$emit('addTimeblock')"
@remove-timeblock="$emit('removeTimeblock')"
/>
</div>
</div>
<div class="timeblock__controls">
<div class="timeblock__controls__time">
<v-text-field
:value="modelValue.starts_at_time"
:class="{
@@ -73,14 +94,29 @@
hide-details
@input="$emit('updateEndTime', $event)"
/>
</div>
<div class="timeblock__time-overview">
<p>
Naplánovaný čas:
<span
class="timeblock__time-overview__label timeblock__time-overview__label--desktop"
>Naplánovaný čas:</span
>
<span
class="timeblock__time-overview__label timeblock__time-overview__label--mobile"
>Nap. čas:</span
>
<span>{{ plannedTime }}h</span>
</p>
<p>
Volný čas:
<span
class="timeblock__time-overview__label timeblock__time-overview__label--desktop"
>Volný čas:</span
>
<span
class="timeblock__time-overview__label timeblock__time-overview__label--mobile"
>Vol. čas:</span
>
<span>{{ freeTime }}h</span>
</p>
</div>
@@ -92,24 +128,23 @@
{{ endTimeErrorMessage || 'Koniec musí byť po začiatku' }}
</div>
<div class="actions">
<button class="add-btn" @click="$emit('addTimeblock')">+</button>
<button
:disabled="!deletable"
class="delete-btn"
@click="$emit('removeTimeblock')"
>
×
</button>
<div class="actions actions--desktop">
<a-employee-timeblock-header-actions
:deletable="deletable"
@add-timeblock="$emit('addTimeblock')"
@remove-timeblock="$emit('removeTimeblock')"
/>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { computed, onMounted, onUnmounted, ref } from 'vue';
import moment from 'moment';
import ZDatepicker from '@/plugins/tm@_components/z-datepicker.vue';
import type { TimeblockData } from './types';
import type { EmployeeFullData, TimeblockData } from './types';
import AEmployeeTimeblockHeaderActions from './a-employee-timeblock-header-actions.vue';
interface Props {
modelValue: TimeblockData;
@@ -117,6 +152,8 @@ interface Props {
deletable: boolean;
isEditable: boolean;
isTimeRangeValid: boolean;
employee: EmployeeFullData;
index: number;
collision: {
hasCollision: boolean;
startCollides: boolean;
@@ -151,10 +188,34 @@ const isCurrentDay = computed(() => {
if (!props.modelValue.date) return false;
return moment(props.modelValue.date).isSame(moment(), 'day');
});
const isMobile = ref(false);
const handleResize = () => {
isMobile.value = window.innerWidth <= 768;
};
onMounted(() => {
handleResize();
window.addEventListener('resize', handleResize);
});
onUnmounted(() => {
window.removeEventListener('resize', handleResize);
});
const dateFormat = computed(() => {
return isMobile.value ? 'dd.MM' : 'dd.MM.yyyy';
});
</script>
<style lang="sass" scoped>
.timeblock__header
.timeblock__header__top
padding: 8px 16px 0 16px
font-weight: 400
font-size: 14px
.timeblock__header__content
display: flex
align-items: center
gap: 15px
@@ -166,12 +227,30 @@ const isCurrentDay = computed(() => {
flex-direction: column
align-items: stretch
.timeblock__date-wrapper
display: flex
align-items: center
justify-content: space-between
@media (max-width: 768px)
width: 100%
.timeblock__date
display: flex
align-items: center
flex: 1
max-width: 220px
@media (max-width: 768px)
max-width: 170px
::v-deep
.dp__input
padding: 0 0 0 29px !important
.dp__input_icon
padding: 6px 9px !important
left: 0 !important
.timeblock__date-button
background: #f5f5f5
border: 1px solid #e0e0e0
@@ -239,26 +318,48 @@ const isCurrentDay = computed(() => {
gap: 10px
flex: 1
&__time
display: flex
gap: 10px
@media (max-width: 768px)
width: 100%
flex: 1
::v-deep .v-input
width: 140px !important
flex-grow: 0
// ::v-deep .v-input
// width: 140px !important
// flex-grow: 0
// @media (max-width: 768px)
// ::v-deep
// .v-input
// width: 58px !important
// .v-label
// margin-inline-start: 5px !important
// .v-field__input
// padding-inline-start: 5px !important
// padding-inline-end: 5px !important
.timeblock__time-overview
height: 44px
display: flex
flex-direction: column
justify-content: space-between
flex-shrink: 0
min-width: max-content
p
font-size: 14px
font-weight: 400
white-space: nowrap
display: flex
gap: 4px
span
span:not(.timeblock__time-overview__label)
font-weight: 500
color: #7f39fb
white-space: nowrap
flex-shrink: 0
.error-input
::v-deep input
@@ -270,48 +371,27 @@ const isCurrentDay = computed(() => {
margin-top: 5px
padding-left: 10px
.actions
.actions--mobile
display: none
@media (max-width: 768px)
display: flex
align-items: center
gap: 8px
.add-btn
background: #7f39fb
color: white
border: none
border-radius: 4px
width: 44px
height: 44px
font-size: 28px
cursor: pointer
display: flex
align-items: center
justify-content: center
transition: all 0.2s
line-height: 1
.actions--desktop
display: block
&:hover
background: #6825e0
@media (max-width: 768px)
display: none
.delete-btn
background: #ff5252
color: white
border: none
border-radius: 4px
width: 44px
height: 44px
font-size: 28px
cursor: pointer
display: flex
align-items: center
justify-content: center
transition: all 0.2s
line-height: 1
.timeblock__time-overview__label--desktop
display: block
&:disabled
opacity: 0.5
cursor: not-allowed
@media (max-width: 768px)
display: none
&:hover
background: #ff1744
.timeblock__time-overview__label--mobile
display: none
@media (max-width: 768px)
display: block
white-space: nowrap
</style>
@@ -197,12 +197,11 @@ defineExpose({
<style lang="sass" scoped>
.timeline
height: 98px
// height: 98px
&__content
position: relative
width: 100%
margin-bottom: 10px
&-loading
height: 100%
@@ -224,10 +223,9 @@ defineExpose({
.timeline-bar
position: relative
height: 60px
height: 30px
width: calc(24 * 60px)
background: #fafafa
border: 1px solid #e0e0e0
border-radius: 4px
@media (max-width: 768px)
@@ -250,7 +248,7 @@ defineExpose({
.scheduled-block
position: absolute
top: 5px
height: 50px
height: 22px
background: #7f39fb
border: 1px solid #6a1bf7
opacity: 0.6
@@ -260,7 +258,7 @@ defineExpose({
.unsaved-timeblock
position: absolute
top: 5px
height: 50px
height: 22px
background: #FFA726
opacity: 0.5
border: 2px dashed #FF9800
@@ -270,7 +268,7 @@ defineExpose({
.current-timeblock
position: absolute
top: 5px
height: 50px
height: 22px
background: #2196F3
opacity: 0.7
border: 2px solid #1976D2
@@ -283,7 +281,7 @@ defineExpose({
.collision-segment
position: absolute
top: 5px
height: 50px
height: 22px
background: #ff5252
opacity: 0.6
border-radius: 2px
@@ -13,6 +13,8 @@
:end-time-error-message="endTimeErrorMessage"
:planned-time="_scheduledTime"
:free-time="_freeTime"
:employee="employee"
:index="index"
@previous-day="previousDay"
@next-day="nextDay"
@update-date="updateDate"
@@ -309,12 +311,25 @@ watch(timelineRef, (newRef) => {
}
});
// Watch for loading state - when data finishes loading, center the timeline
watch(isLoading, (newIsLoading, oldIsLoading) => {
// When loading changes from true to false, timeline is re-rendered
if (oldIsLoading && !newIsLoading) {
// Wait for DOM to update, then center
nextTick(() => {
setTimeout(() => {
centerTimeline();
}, 100); // Small delay to ensure DOM is fully rendered
});
}
});
// Date navigation
const previousDay = () => {
if (!props.modelValue.date) return;
const currentDate = moment(props.modelValue.date);
const newDate = currentDate.subtract(1, 'day').toDate();
const newDate = currentDate.clone().subtract(1, 'day').toDate();
// Don't allow going to past dates
if (moment(newDate).isBefore(moment(), 'day')) return;
@@ -326,7 +341,7 @@ const nextDay = () => {
if (!props.modelValue.date) return;
const currentDate = moment(props.modelValue.date);
const newDate = currentDate.add(1, 'day').toDate();
const newDate = currentDate.clone().add(1, 'day').toDate();
updateDate(newDate);
};
@@ -370,4 +385,5 @@ defineExpose({
border: 1px solid #e5e5e5
border-radius: 8px
margin-bottom: 8px
overflow: hidden
</style>
@@ -164,8 +164,9 @@ defineExpose({
&--has-background
border: 1px solid #e5e5e5
border-bottom: 3px solid #7f39fb
padding: 16px
border-radius: 8px
border-radius: 8px 8px 0 0
&__timeblocks
display: flex